diff --git a/backend/app/api/endpoints/public.py b/backend/app/api/endpoints/public.py index 63847d2..04fe39e 100644 --- a/backend/app/api/endpoints/public.py +++ b/backend/app/api/endpoints/public.py @@ -59,28 +59,36 @@ async def get_public_arrivals(db: Session = Depends(get_db)): 'isLocalFlight': False }) - # Add local flights with DEPARTED status that were booked out today - local_flights = crud_local_flight.get_multi( - db, - status=LocalFlightStatus.DEPARTED, - limit=1000 - ) - # Get today's date boundaries today = date.today() today_start = datetime.combine(today, datetime.min.time()) today_end = datetime.combine(today + timedelta(days=1), datetime.min.time()) + + # Add airborne local flights that were booked out today. + # Admin now moves local flights from GROUND to LOCAL/CIRCUIT rather than DEPARTED. + airborne_local_statuses = { + LocalFlightStatus.DEPARTED, + LocalFlightStatus.LOCAL, + LocalFlightStatus.CIRCUIT, + LocalFlightStatus.CIRCUIT_DOWNWIND, + LocalFlightStatus.CIRCUIT_BASE, + LocalFlightStatus.CIRCUIT_FINAL, + } + local_flights = crud_local_flight.get_multi(db, limit=1000) # Convert local flights to match the PPR format for display for flight in local_flights: # Only include flights booked out today if not (today_start <= flight.created_dt < today_end): continue + if flight.status not in airborne_local_statuses: + continue - # Calculate ETA from departed_dt + duration (if both are available) - eta = flight.departed_dt - if flight.departed_dt and flight.duration: - eta = flight.departed_dt + timedelta(minutes=flight.duration) + # Calculate ETA from actual takeoff/departure + duration, falling back to ETD. + departure_time = flight.takeoff_dt or flight.departed_dt or flight.etd + eta = departure_time + if departure_time and flight.duration: + eta = departure_time + timedelta(minutes=flight.duration) arrivals_list.append({ 'ac_call': flight.callsign or flight.registration, @@ -89,7 +97,7 @@ async def get_public_arrivals(db: Session = Depends(get_db)): 'in_from': None, 'eta': eta, 'landed_dt': None, - 'status': 'DEPARTED', + 'status': flight.status.value, 'isLocalFlight': True, 'flight_type': flight.flight_type.value }) @@ -143,23 +151,26 @@ async def get_public_departures(db: Session = Depends(get_db)): 'isDeparture': False }) - # Add local flights with BOOKED_OUT status that were booked out today - local_flights = crud_local_flight.get_multi( - db, - status=LocalFlightStatus.BOOKED_OUT, - limit=1000 - ) - # Get today's date boundaries today = date.today() today_start = datetime.combine(today, datetime.min.time()) today_end = datetime.combine(today + timedelta(days=1), datetime.min.time()) + + # Add local flights awaiting takeoff that were booked out today. + # Admin-created flights start at GROUND, while public pilot submissions start at BOOKED_OUT. + local_departure_statuses = { + LocalFlightStatus.BOOKED_OUT, + LocalFlightStatus.GROUND, + } + local_flights = crud_local_flight.get_multi(db, limit=1000) # Convert local flights to match the PPR format for display for flight in local_flights: # Only include flights booked out today if not (today_start <= flight.created_dt < today_end): continue + if flight.status not in local_departure_statuses: + continue departures_list.append({ 'ac_call': flight.callsign or flight.registration, 'ac_reg': flight.registration, @@ -167,7 +178,7 @@ async def get_public_departures(db: Session = Depends(get_db)): 'out_to': None, 'etd': flight.etd or flight.created_dt, 'departed_dt': None, - 'status': 'BOOKED_OUT', + 'status': 'CONTACT' if flight.status == LocalFlightStatus.GROUND else 'BOOKED_OUT', 'isLocalFlight': True, 'flight_type': flight.flight_type.value, 'isDeparture': False @@ -247,4 +258,4 @@ async def get_ui_config(): "top_bar_gradient_end": lighten_color(base_color, 0.4), # Lighten for gradient end "footer_color": darken_color(base_color, 0.2), # Darken for footer "environment": settings.environment - } \ No newline at end of file + } diff --git a/backend/tests/test_public_api.py b/backend/tests/test_public_api.py index f9b768d..c358e08 100644 --- a/backend/tests/test_public_api.py +++ b/backend/tests/test_public_api.py @@ -81,6 +81,71 @@ def test_public_boards_include_todays_flights(client, db): } +def test_public_boards_include_current_local_flight_statuses(client, db): + now = datetime.now().replace(microsecond=0) + ground_local = LocalFlight( + registration="G-GRND", + type="C152", + callsign="GGRND", + pob=1, + flight_type=LocalFlightType.LOCAL, + status=LocalFlightStatus.GROUND, + created_dt=now, + etd=now, + ) + airborne_local = LocalFlight( + registration="G-AIR1", + type="PA28", + callsign="GAIR1", + pob=2, + flight_type=LocalFlightType.LOCAL, + status=LocalFlightStatus.LOCAL, + created_dt=now, + etd=now, + takeoff_dt=now, + duration=45, + ) + circuit_local = LocalFlight( + registration="G-CCT1", + type="C152", + callsign="GCCT1", + pob=1, + flight_type=LocalFlightType.CIRCUITS, + status=LocalFlightStatus.CIRCUIT, + created_dt=now, + etd=now, + takeoff_dt=now, + duration=30, + ) + landed_local = LocalFlight( + registration="G-LND1", + type="C172", + callsign="GLND1", + pob=1, + flight_type=LocalFlightType.LOCAL, + status=LocalFlightStatus.LANDED, + created_dt=now, + etd=now, + takeoff_dt=now, + landed_dt=now, + ) + db.add_all([ground_local, airborne_local, circuit_local, landed_local]) + db.commit() + + arrivals = client.get("/api/v1/public/arrivals") + departures = client.get("/api/v1/public/departures") + + assert arrivals.status_code == 200 + assert { + item["ac_reg"] for item in arrivals.json() if item.get("isLocalFlight") + } == {"G-AIR1", "G-CCT1"} + + assert departures.status_code == 200 + assert { + item["ac_reg"] for item in departures.json() if item.get("isLocalFlight") + } == {"G-GRND"} + + def test_public_reference_lookups_return_seeded_records(client, db): db.add( Airport( diff --git a/web/admin.html b/web/admin.html index 651e53a..45b041c 100644 --- a/web/admin.html +++ b/web/admin.html @@ -48,8 +48,47 @@