airtrail_client: pair every date field with its required Time companion
This commit is contained in:
+36
-2
@@ -91,17 +91,43 @@ def downsample_track(
|
|||||||
return new_coords, new_times
|
return new_coords, new_times
|
||||||
|
|
||||||
|
|
||||||
|
def _hhmm(iso_str: str | None) -> str | None:
|
||||||
|
"""Extract 'HH:MM' from one of our seconds-precision ISO datetime
|
||||||
|
strings (e.g. '2026-09-13T13:57:00+10:00' -> '13:57').
|
||||||
|
|
||||||
|
AirTrail does NOT read the time-of-day out of departure/arrival/
|
||||||
|
departureScheduled/etc. at all - confirmed against its source
|
||||||
|
(src/lib/server/utils/flight.ts + src/lib/utils/datetime/parse.ts's
|
||||||
|
mergeTimeWithDate()): it takes only the DATE portion of that field and
|
||||||
|
merges it with a separate companion '*Time' field (departureTime,
|
||||||
|
arrivalTime, departureScheduledTime, ...) for the actual clock time,
|
||||||
|
interpreted in the airport's own timezone. Sending a fully-populated
|
||||||
|
ISO datetime without its companion Time field means AirTrail silently
|
||||||
|
drops the time for departure/departureScheduled/etc., and outright
|
||||||
|
rejects arrival with "Cannot have arrival date without time" - both
|
||||||
|
confirmed live. Every date field this module sets below needs its
|
||||||
|
matching *Time companion or the value doesn't actually land."""
|
||||||
|
if not iso_str:
|
||||||
|
return None
|
||||||
|
return iso_str[11:16]
|
||||||
|
|
||||||
|
|
||||||
def build_save_payload(row: dict, existing_id: int | None) -> dict:
|
def build_save_payload(row: dict, existing_id: int | None) -> dict:
|
||||||
flight_number = f"{row.get('operating_carrier_iata') or ''}{row.get('flight_number') or ''}"
|
flight_number = f"{row.get('operating_carrier_iata') or ''}{row.get('flight_number') or ''}"
|
||||||
flight_date_str = row["flight_date"]
|
flight_date_str = row["flight_date"]
|
||||||
|
|
||||||
|
departure_value = row.get("gate_departure_actual") or f"{flight_date_str}T00:00:00.000+00:00"
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"from": row["from_iata"],
|
"from": row["from_iata"],
|
||||||
"to": row["to_iata"],
|
"to": row["to_iata"],
|
||||||
# Falls back to the bare date (day precision, no time) exactly like
|
# Falls back to the bare date (day precision, no time) exactly like
|
||||||
# before if AeroDataBox never returned a gate-actual time - e.g. no
|
# before if AeroDataBox never returned a gate-actual time - e.g. no
|
||||||
# AERODATABOX_API_KEY configured, or the lookup came up empty.
|
# AERODATABOX_API_KEY configured, or the lookup came up empty. No
|
||||||
"departure": row.get("gate_departure_actual") or f"{flight_date_str}T00:00:00.000+00:00",
|
# departureTime is set in that case, which AirTrail treats the same
|
||||||
|
# way (silently date-only) rather than erroring - unlike arrival.
|
||||||
|
"departure": departure_value,
|
||||||
|
"departureTime": _hhmm(row.get("gate_departure_actual")),
|
||||||
"datePrecision": "day",
|
"datePrecision": "day",
|
||||||
"flightNumber": flight_number or None,
|
"flightNumber": flight_number or None,
|
||||||
"airline": row.get("operating_carrier_icao"),
|
"airline": row.get("operating_carrier_icao"),
|
||||||
@@ -128,14 +154,22 @@ def build_save_payload(row: dict, existing_id: int | None) -> dict:
|
|||||||
# manual Search remains the fallback, same as before this change.
|
# manual Search remains the fallback, same as before this change.
|
||||||
if row.get("gate_departure_scheduled"):
|
if row.get("gate_departure_scheduled"):
|
||||||
payload["departureScheduled"] = row["gate_departure_scheduled"]
|
payload["departureScheduled"] = row["gate_departure_scheduled"]
|
||||||
|
payload["departureScheduledTime"] = _hhmm(row["gate_departure_scheduled"])
|
||||||
if row.get("gate_arrival_scheduled"):
|
if row.get("gate_arrival_scheduled"):
|
||||||
payload["arrivalScheduled"] = row["gate_arrival_scheduled"]
|
payload["arrivalScheduled"] = row["gate_arrival_scheduled"]
|
||||||
|
payload["arrivalScheduledTime"] = _hhmm(row["gate_arrival_scheduled"])
|
||||||
if row.get("gate_arrival_actual"):
|
if row.get("gate_arrival_actual"):
|
||||||
|
# Unlike departure, AirTrail hard-rejects an arrival date with no
|
||||||
|
# arrivalTime ("Cannot have arrival date without time") rather than
|
||||||
|
# silently dropping it - this pairing is not optional.
|
||||||
payload["arrival"] = row["gate_arrival_actual"]
|
payload["arrival"] = row["gate_arrival_actual"]
|
||||||
|
payload["arrivalTime"] = _hhmm(row["gate_arrival_actual"])
|
||||||
if row.get("takeoff_actual"):
|
if row.get("takeoff_actual"):
|
||||||
payload["takeoffActual"] = row["takeoff_actual"]
|
payload["takeoffActual"] = row["takeoff_actual"]
|
||||||
|
payload["takeoffActualTime"] = _hhmm(row["takeoff_actual"])
|
||||||
if row.get("landing_actual"):
|
if row.get("landing_actual"):
|
||||||
payload["landingActual"] = row["landing_actual"]
|
payload["landingActual"] = row["landing_actual"]
|
||||||
|
payload["landingActualTime"] = _hhmm(row["landing_actual"])
|
||||||
|
|
||||||
coordinates = row.get("track_coordinates")
|
coordinates = row.get("track_coordinates")
|
||||||
if coordinates:
|
if coordinates:
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
diff --git a/airtrail_client.py b/airtrail_client.py
|
||||||
|
index 962f989..afc19b8 100644
|
||||||
|
--- a/airtrail_client.py
|
||||||
|
+++ b/airtrail_client.py
|
||||||
|
@@ -91,17 +91,43 @@ def downsample_track(
|
||||||
|
return new_coords, new_times
|
||||||
|
|
||||||
|
|
||||||
|
+def _hhmm(iso_str: str | None) -> str | None:
|
||||||
|
+ """Extract 'HH:MM' from one of our seconds-precision ISO datetime
|
||||||
|
+ strings (e.g. '2026-09-13T13:57:00+10:00' -> '13:57').
|
||||||
|
+
|
||||||
|
+ AirTrail does NOT read the time-of-day out of departure/arrival/
|
||||||
|
+ departureScheduled/etc. at all - confirmed against its source
|
||||||
|
+ (src/lib/server/utils/flight.ts + src/lib/utils/datetime/parse.ts's
|
||||||
|
+ mergeTimeWithDate()): it takes only the DATE portion of that field and
|
||||||
|
+ merges it with a separate companion '*Time' field (departureTime,
|
||||||
|
+ arrivalTime, departureScheduledTime, ...) for the actual clock time,
|
||||||
|
+ interpreted in the airport's own timezone. Sending a fully-populated
|
||||||
|
+ ISO datetime without its companion Time field means AirTrail silently
|
||||||
|
+ drops the time for departure/departureScheduled/etc., and outright
|
||||||
|
+ rejects arrival with "Cannot have arrival date without time" - both
|
||||||
|
+ confirmed live. Every date field this module sets below needs its
|
||||||
|
+ matching *Time companion or the value doesn't actually land."""
|
||||||
|
+ if not iso_str:
|
||||||
|
+ return None
|
||||||
|
+ return iso_str[11:16]
|
||||||
|
+
|
||||||
|
+
|
||||||
|
def build_save_payload(row: dict, existing_id: int | None) -> dict:
|
||||||
|
flight_number = f"{row.get('operating_carrier_iata') or ''}{row.get('flight_number') or ''}"
|
||||||
|
flight_date_str = row["flight_date"]
|
||||||
|
|
||||||
|
+ departure_value = row.get("gate_departure_actual") or f"{flight_date_str}T00:00:00.000+00:00"
|
||||||
|
+
|
||||||
|
payload = {
|
||||||
|
"from": row["from_iata"],
|
||||||
|
"to": row["to_iata"],
|
||||||
|
# Falls back to the bare date (day precision, no time) exactly like
|
||||||
|
# before if AeroDataBox never returned a gate-actual time - e.g. no
|
||||||
|
- # AERODATABOX_API_KEY configured, or the lookup came up empty.
|
||||||
|
- "departure": row.get("gate_departure_actual") or f"{flight_date_str}T00:00:00.000+00:00",
|
||||||
|
+ # AERODATABOX_API_KEY configured, or the lookup came up empty. No
|
||||||
|
+ # departureTime is set in that case, which AirTrail treats the same
|
||||||
|
+ # way (silently date-only) rather than erroring - unlike arrival.
|
||||||
|
+ "departure": departure_value,
|
||||||
|
+ "departureTime": _hhmm(row.get("gate_departure_actual")),
|
||||||
|
"datePrecision": "day",
|
||||||
|
"flightNumber": flight_number or None,
|
||||||
|
"airline": row.get("operating_carrier_icao"),
|
||||||
|
@@ -128,14 +154,22 @@ def build_save_payload(row: dict, existing_id: int | None) -> dict:
|
||||||
|
# manual Search remains the fallback, same as before this change.
|
||||||
|
if row.get("gate_departure_scheduled"):
|
||||||
|
payload["departureScheduled"] = row["gate_departure_scheduled"]
|
||||||
|
+ payload["departureScheduledTime"] = _hhmm(row["gate_departure_scheduled"])
|
||||||
|
if row.get("gate_arrival_scheduled"):
|
||||||
|
payload["arrivalScheduled"] = row["gate_arrival_scheduled"]
|
||||||
|
+ payload["arrivalScheduledTime"] = _hhmm(row["gate_arrival_scheduled"])
|
||||||
|
if row.get("gate_arrival_actual"):
|
||||||
|
+ # Unlike departure, AirTrail hard-rejects an arrival date with no
|
||||||
|
+ # arrivalTime ("Cannot have arrival date without time") rather than
|
||||||
|
+ # silently dropping it - this pairing is not optional.
|
||||||
|
payload["arrival"] = row["gate_arrival_actual"]
|
||||||
|
+ payload["arrivalTime"] = _hhmm(row["gate_arrival_actual"])
|
||||||
|
if row.get("takeoff_actual"):
|
||||||
|
payload["takeoffActual"] = row["takeoff_actual"]
|
||||||
|
+ payload["takeoffActualTime"] = _hhmm(row["takeoff_actual"])
|
||||||
|
if row.get("landing_actual"):
|
||||||
|
payload["landingActual"] = row["landing_actual"]
|
||||||
|
+ payload["landingActualTime"] = _hhmm(row["landing_actual"])
|
||||||
|
|
||||||
|
coordinates = row.get("track_coordinates")
|
||||||
|
if coordinates:
|
||||||
Reference in New Issue
Block a user