Various improvements
This commit is contained in:
parent
b7d95f26ec
commit
442ae98463
3 changed files with 100 additions and 3 deletions
|
|
@ -37,6 +37,8 @@ class BookingConfig:
|
||||||
booking_type: str
|
booking_type: str
|
||||||
yaml_filename: str
|
yaml_filename: str
|
||||||
spec_heading: str
|
spec_heading: str
|
||||||
|
start_field: str
|
||||||
|
excluded_top_level_keys: tuple[str, ...]
|
||||||
json_key: str = "booking"
|
json_key: str = "booking"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -45,11 +47,22 @@ BOOKING_CONFIGS: dict[str, BookingConfig] = {
|
||||||
booking_type="flight",
|
booking_type="flight",
|
||||||
yaml_filename="flights.yaml",
|
yaml_filename="flights.yaml",
|
||||||
spec_heading="flights.yaml",
|
spec_heading="flights.yaml",
|
||||||
|
start_field="depart",
|
||||||
|
excluded_top_level_keys=("trip",),
|
||||||
),
|
),
|
||||||
"train": BookingConfig(
|
"train": BookingConfig(
|
||||||
booking_type="train",
|
booking_type="train",
|
||||||
yaml_filename="trains.yaml",
|
yaml_filename="trains.yaml",
|
||||||
spec_heading="trains.yaml",
|
spec_heading="trains.yaml",
|
||||||
|
start_field="depart",
|
||||||
|
excluded_top_level_keys=("trip",),
|
||||||
|
),
|
||||||
|
"accommodation": BookingConfig(
|
||||||
|
booking_type="accommodation",
|
||||||
|
yaml_filename="accommodation.yaml",
|
||||||
|
spec_heading="accommodation.yaml",
|
||||||
|
start_field="from",
|
||||||
|
excluded_top_level_keys=("trip", "latitude", "longitude"),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,6 +122,7 @@ def build_prompt(
|
||||||
bookings = current_bookings
|
bookings = current_bookings
|
||||||
if bookings is None:
|
if bookings is None:
|
||||||
bookings = read_existing_bookings(config)
|
bookings = read_existing_bookings(config)
|
||||||
|
excluded_keys = ", ".join(f'"{key}"' for key in config.excluded_top_level_keys)
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
I keep a record of all my {config.booking_type} bookings in a YAML file.
|
I keep a record of all my {config.booking_type} bookings in a YAML file.
|
||||||
|
|
@ -131,7 +145,7 @@ Rules:
|
||||||
- Wrap the response in a JSON object with a single key "{config.json_key}" that
|
- Wrap the response in a JSON object with a single key "{config.json_key}" that
|
||||||
contains the booking in YAML.
|
contains the booking in YAML.
|
||||||
- The value of "{config.json_key}" must be YAML text, not JSON.
|
- The value of "{config.json_key}" must be YAML text, not JSON.
|
||||||
- Exclude the top-level "trip" key from the YAML.
|
- Exclude these top-level keys from the YAML: {excluded_keys}.
|
||||||
- Do not invent details that are not present in the booking text.
|
- Do not invent details that are not present in the booking text.
|
||||||
- Quote prices and identifiers that might otherwise be parsed as numbers.
|
- Quote prices and identifiers that might otherwise be parsed as numbers.
|
||||||
|
|
||||||
|
|
@ -218,7 +232,7 @@ def first_departure(booking: dict[str, typing.Any], config: BookingConfig) -> da
|
||||||
assert isinstance(first_flight, dict)
|
assert isinstance(first_flight, dict)
|
||||||
return datetime_from_yaml_value(first_flight["depart"])
|
return datetime_from_yaml_value(first_flight["depart"])
|
||||||
|
|
||||||
return datetime_from_yaml_value(booking["depart"])
|
return datetime_from_yaml_value(booking[config.start_field])
|
||||||
|
|
||||||
|
|
||||||
def comparable_departure(
|
def comparable_departure(
|
||||||
|
|
@ -246,6 +260,13 @@ def trip_key_position(booking: dict[str, typing.Any], config: BookingConfig) ->
|
||||||
return keys.index("booking_reference") + 1
|
return keys.index("booking_reference") + 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
if config.booking_type == "accommodation":
|
||||||
|
if "country" in booking:
|
||||||
|
return keys.index("country") + 1
|
||||||
|
if "location" in booking:
|
||||||
|
return keys.index("location") + 1
|
||||||
|
return 0
|
||||||
|
|
||||||
if "to" in booking:
|
if "to" in booking:
|
||||||
return keys.index("to") + 1
|
return keys.index("to") + 1
|
||||||
if "from" in booking:
|
if "from" in booking:
|
||||||
|
|
@ -455,3 +476,8 @@ def train_main(argv: list[str] | None = None) -> int:
|
||||||
def flight_main(argv: list[str] | None = None) -> int:
|
def flight_main(argv: list[str] | None = None) -> int:
|
||||||
"""CLI entrypoint for flight booking YAML generation."""
|
"""CLI entrypoint for flight booking YAML generation."""
|
||||||
return main_for_type("flight", argv)
|
return main_for_type("flight", argv)
|
||||||
|
|
||||||
|
|
||||||
|
def accommodation_main(argv: list[str] | None = None) -> int:
|
||||||
|
"""CLI entrypoint for accommodation booking YAML generation."""
|
||||||
|
return main_for_type("accommodation", argv)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ This document describes the YAML files read from `../personal-data/`. It is inte
|
||||||
- Currencies must be in `config.CURRENCIES` or `GBP`.
|
- Currencies must be in `config.CURRENCIES` or `GBP`.
|
||||||
- Travel and trip-related entries are grouped by the `trip` date. That date should match an entry in `trips.yaml` when a named trip is needed, but trip groups can also be created from travel/accommodation/conference entries.
|
- Travel and trip-related entries are grouped by the `trip` date. That date should match an entry in `trips.yaml` when a named trip is needed, but trip groups can also be created from travel/accommodation/conference entries.
|
||||||
- Keep chronological files sorted by their natural start field. `validate_yaml.py` checks ordering for trips, flights, trains, ferries, conferences, and accommodation.
|
- Keep chronological files sorted by their natural start field. `validate_yaml.py` checks ordering for trips, flights, trains, ferries, conferences, and accommodation.
|
||||||
|
- Preserve the existing whitespace style. Long top-level list files such as `accommodation.yaml`, `buses.yaml`, `car_journeys.yaml`, `coaches.yaml`, `conferences.yaml`, `ferries.yaml`, `flights.yaml`, `stations.yaml`, `trains.yaml`, and `trips.yaml` use one blank line between top-level items. Mapping files such as `airports.yaml` do not use this list-item spacing.
|
||||||
- Coordinates are `latitude` then `longitude`, both numeric.
|
- Coordinates are `latitude` then `longitude`, both numeric.
|
||||||
|
|
||||||
## Cross-File References
|
## Cross-File References
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,25 @@ def test_build_prompt_uses_spec_and_keeps_trip_exclusion() -> None:
|
||||||
|
|
||||||
assert "Use this YAML format specification" in prompt
|
assert "Use this YAML format specification" in prompt
|
||||||
assert "## `trains.yaml`" in prompt
|
assert "## `trains.yaml`" in prompt
|
||||||
assert 'Exclude the top-level "trip" key' in prompt
|
assert 'Exclude these top-level keys from the YAML: "trip"' in prompt
|
||||||
assert "Eurostar booking details" in prompt
|
assert "Eurostar booking details" in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_prompt_for_accommodation_excludes_coordinates() -> None:
|
||||||
|
"""Accommodation prompt should exclude trip and coordinate fields."""
|
||||||
|
prompt = generate_booking_yaml.build_prompt(
|
||||||
|
"Airbnb booking details",
|
||||||
|
generate_booking_yaml.BOOKING_CONFIGS["accommodation"],
|
||||||
|
current_bookings="- type: airbnb\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "## `accommodation.yaml`" in prompt
|
||||||
|
assert (
|
||||||
|
'Exclude these top-level keys from the YAML: "trip", "latitude", "longitude"'
|
||||||
|
in prompt
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_booking_text_from_args_fetches_url(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_booking_text_from_args_fetches_url(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""URL arguments should be fetched instead of reading stdin."""
|
"""URL arguments should be fetched instead of reading stdin."""
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
|
|
@ -185,3 +200,58 @@ flights:
|
||||||
assert [item["booking_reference"] for item in written] == ["OLD", "MID", "NEWER"]
|
assert [item["booking_reference"] for item in written] == ["OLD", "MID", "NEWER"]
|
||||||
assert written[1]["trip"] == date(2026, 2, 6)
|
assert written[1]["trip"] == date(2026, 2, 6)
|
||||||
assert list(written[1]).index("trip") == 1
|
assert list(written[1]).index("trip") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_accommodation_booking_adds_trip_and_inserts_chronologically(
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""Generated accommodation should be written into accommodation.yaml in order."""
|
||||||
|
(tmp_path / "accommodation.yaml").write_text("""---
|
||||||
|
- type: hotel
|
||||||
|
name: Earlier Hotel
|
||||||
|
location: Brussels
|
||||||
|
country: be
|
||||||
|
trip: 2026-02-01
|
||||||
|
from: 2026-02-01 15:00:00+01:00
|
||||||
|
to: 2026-02-02 11:00:00+01:00
|
||||||
|
|
||||||
|
- type: hotel
|
||||||
|
name: Later Hotel
|
||||||
|
location: Paris
|
||||||
|
country: fr
|
||||||
|
trip: 2026-02-10
|
||||||
|
from: 2026-02-10 15:00:00+01:00
|
||||||
|
to: 2026-02-11 11:00:00+01:00
|
||||||
|
""")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
generate_booking_yaml,
|
||||||
|
"matching_trip_date",
|
||||||
|
lambda depart, data_dir: date(2026, 2, 6),
|
||||||
|
)
|
||||||
|
|
||||||
|
count = generate_booking_yaml.import_booking_yaml(
|
||||||
|
"""type: airbnb
|
||||||
|
name: Paris Airbnb
|
||||||
|
location: Paris
|
||||||
|
country: fr
|
||||||
|
from: 2026-02-06 15:00:00+01:00
|
||||||
|
to: 2026-02-09 11:00:00+01:00
|
||||||
|
price: '300.00'
|
||||||
|
currency: EUR
|
||||||
|
""",
|
||||||
|
generate_booking_yaml.BOOKING_CONFIGS["accommodation"],
|
||||||
|
data_dir=tmp_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
text = (tmp_path / "accommodation.yaml").read_text()
|
||||||
|
written = yaml.safe_load(text)
|
||||||
|
assert count == 1
|
||||||
|
assert [item["name"] for item in written] == [
|
||||||
|
"Earlier Hotel",
|
||||||
|
"Paris Airbnb",
|
||||||
|
"Later Hotel",
|
||||||
|
]
|
||||||
|
assert written[1]["trip"] == date(2026, 2, 6)
|
||||||
|
assert list(written[1]).index("trip") == 4
|
||||||
|
assert "\n\n- type: airbnb\n" in text
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue