From 8d2ffe30141196e5ecb2655e1e0096b5d6624487 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Tue, 6 Feb 2024 08:01:56 +0100 Subject: [PATCH] Manage trip additions and cancels --- sncf-station/src/TrainsTable.js | 47 +++- sncfgtfs/admin.py | 5 +- sncfgtfs/locale/fr/LC_MESSAGES/django.po | 230 ++++++++++-------- .../management/commands/update_sncf_gtfs.py | 2 +- .../commands/update_sncf_gtfs_rt.py | 75 +++++- ..._trip_options_tripupdate_stoptimeupdate.py | 11 +- sncfgtfs/models.py | 26 +- 7 files changed, 247 insertions(+), 149 deletions(-) diff --git a/sncf-station/src/TrainsTable.js b/sncf-station/src/TrainsTable.js index e2ab7f1..de1cd05 100644 --- a/sncf-station/src/TrainsTable.js +++ b/sncf-station/src/TrainsTable.js @@ -131,12 +131,14 @@ function TrainRow({train, tableType, date, time}) { let headline = stops[tableType === "departures" ? stops.length - 1 : 0]?.stop ?? {name: "Chargement…"} - let stopsFilter - if (tableType === "departures") - stopsFilter = (stop_time) => stop_time.stop_sequence > train.stop_sequence && stop_time.drop_off_type === 0 - else - stopsFilter = (stop_time) => stop_time.stop_sequence < train.stop_sequence && stop_time.pickup_type === 0 - let stopsNames = stops.filter(stopsFilter).map(stopTime => stopTime?.stop.name ?? "").join(", ") ?? "" + const realtimeTripQuery = useQuery({ + queryKey: ['realtimeTrip', trip.id, date, time], + queryFn: () => fetch(`/api/gtfs-rt/trip_update/${trip.id}/`) + .then(response => response.json()), + enabled: !!trip.id, + }) + const realtimeTripData = realtimeTripQuery.data ?? {} + const scheduleRelationship = realtimeTripData.schedule_relationship ?? 0 const realtimeQuery = useQuery({ queryKey: ['realtime', train.id, date, time], @@ -145,11 +147,20 @@ function TrainRow({train, tableType, date, time}) { enabled: !!train.id, }) const realtimeData = realtimeQuery.data ?? {} - if (!realtimeQuery.isError) - console.log(realtimeData) + const delay = tableType === "departures" ? realtimeData.departure_delay : realtimeData.arrival_delay - const prettyDelay = delay ? getPrettyDelay(delay) : "" - const visibleDelay = delay ? `${prettyDelay}` : "" + const prettyDelay = delay && scheduleRelationship !== 3 ? getPrettyDelay(delay) : "" + const [prettyScheduleRelationship, scheduleRelationshipColor] = getPrettyScheduleRelationship(scheduleRelationship) + console.log(realtimeTripData) + + let stopsFilter + if (scheduleRelationship === 3) + stopsFilter = (stop_time) => true + else if (tableType === "departures") + stopsFilter = (stop_time) => stop_time.stop_sequence > train.stop_sequence && stop_time.drop_off_type === 0 + else + stopsFilter = (stop_time) => stop_time.stop_sequence < train.stop_sequence && stop_time.pickup_type === 0 + let stopsNames = stops.filter(stopsFilter).map(stopTime => stopTime?.stop.name ?? "").join(", ") ?? "" return <> @@ -184,7 +195,10 @@ function TrainRow({train, tableType, date, time}) { {getDisplayTime(train, tableType)} - {visibleDelay} + {prettyDelay} + + + {prettyScheduleRelationship} @@ -255,4 +269,15 @@ function getPrettyDelay(delay) { return full_minutes ? `+${full_minutes} min` : "À l'heure" } +function getPrettyScheduleRelationship(scheduledRelationship) { + switch (scheduledRelationship) { + case 1: + return ["Ajouté", "#3ebb18"] + case 3: + return ["Supprimé", "#ff8701"] + default: + return ["", ""] + } +} + export default TrainsTable; \ No newline at end of file diff --git a/sncfgtfs/admin.py b/sncfgtfs/admin.py index a625a4f..6faad63 100644 --- a/sncfgtfs/admin.py +++ b/sncfgtfs/admin.py @@ -86,13 +86,14 @@ class StopTimeUpdateAdmin(admin.ModelAdmin): list_display = ('trip_update', 'stop_time', 'arrival_delay', 'arrival_time', 'departure_delay', 'departure_time', 'schedule_relationship',) list_filter = ('schedule_relationship',) - search_fields = ('trip_update__trip_id', 'stop_time__stop__name', 'arrival_time', 'departure_time',) + search_fields = ('trip_update__trip__id', 'stop_time__stop__name', 'arrival_time', 'departure_time',) ordering = ('trip_update', 'stop_time',) @admin.register(TripUpdate) class TripUpdateAdmin(admin.ModelAdmin): list_display = ('trip_id', 'start_date', 'start_time',) - search_fields = ('trip_id', 'start_date', 'start_time',) + list_filter = ('start_date', 'schedule_relationship',) + search_fields = ('trip__id', 'start_date', 'start_time',) ordering = ('trip_id', 'start_date', 'start_time',) autocomplete_fields = ('trip',) diff --git a/sncfgtfs/locale/fr/LC_MESSAGES/django.po b/sncfgtfs/locale/fr/LC_MESSAGES/django.po index ae06903..c9a37c0 100644 --- a/sncfgtfs/locale/fr/LC_MESSAGES/django.po +++ b/sncfgtfs/locale/fr/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-04 22:16+0100\n" +"POT-Creation-Date: 2024-02-06 08:00+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Emmy D'Anello \n" "Language-Team: LANGUAGE \n" @@ -128,7 +128,7 @@ msgstr "Correspondance programmée" msgid "Minimum time" msgstr "Temps de correspondance minimum requis" -#: sncfgtfs/models.py:57 +#: sncfgtfs/models.py:57 sncfgtfs/models.py:63 msgid "Added" msgstr "Ajouté" @@ -136,395 +136,411 @@ msgstr "Ajouté" msgid "Removed" msgstr "Supprimé" -#: sncfgtfs/models.py:62 +#: sncfgtfs/models.py:62 sncfgtfs/models.py:72 msgid "Scheduled" msgstr "Planifié" -#: sncfgtfs/models.py:63 -msgid "Skipped" -msgstr "Sauté" - -#: sncfgtfs/models.py:64 -msgid "No data" -msgstr "Pas de données" - -#: sncfgtfs/models.py:65 +#: sncfgtfs/models.py:64 sncfgtfs/models.py:75 msgid "Unscheduled" msgstr "Non planifié" -#: sncfgtfs/models.py:72 sncfgtfs/models.py:229 +#: sncfgtfs/models.py:65 +msgid "Canceled" +msgstr "Annulé" + +#: sncfgtfs/models.py:66 +msgid "Replacement" +msgstr "Remplacé" + +#: sncfgtfs/models.py:67 +msgid "Duplicated" +msgstr "Dupliqué" + +#: sncfgtfs/models.py:68 +msgid "Deleted" +msgstr "Supprimé" + +#: sncfgtfs/models.py:73 +msgid "Skipped" +msgstr "Sauté" + +#: sncfgtfs/models.py:74 +msgid "No data" +msgstr "Pas de données" + +#: sncfgtfs/models.py:82 sncfgtfs/models.py:233 msgid "Agency ID" msgstr "ID de l'agence" -#: sncfgtfs/models.py:78 +#: sncfgtfs/models.py:88 msgid "Agency name" msgstr "Nom de l'agence" -#: sncfgtfs/models.py:82 +#: sncfgtfs/models.py:92 msgid "Agency URL" msgstr "URL de l'agence" -#: sncfgtfs/models.py:87 +#: sncfgtfs/models.py:97 msgid "Agency timezone" msgstr "Fuseau horaire de l'agence" -#: sncfgtfs/models.py:92 +#: sncfgtfs/models.py:102 msgid "Agency language" msgstr "Langue de l'agence" -#: sncfgtfs/models.py:98 +#: sncfgtfs/models.py:108 msgid "Agency phone" msgstr "Téléphone de l'agence" -#: sncfgtfs/models.py:103 +#: sncfgtfs/models.py:113 msgid "Agency email" msgstr "Adresse email de l'agence" -#: sncfgtfs/models.py:111 +#: sncfgtfs/models.py:121 msgid "Agency" msgstr "Agence" -#: sncfgtfs/models.py:112 +#: sncfgtfs/models.py:122 msgid "Agencies" msgstr "Agences" -#: sncfgtfs/models.py:120 sncfgtfs/models.py:420 +#: sncfgtfs/models.py:130 sncfgtfs/models.py:424 msgid "Stop ID" msgstr "ID de l'arrêt" -#: sncfgtfs/models.py:125 +#: sncfgtfs/models.py:135 msgid "Stop code" msgstr "Code de l'arrêt" -#: sncfgtfs/models.py:131 +#: sncfgtfs/models.py:141 msgid "Stop name" msgstr "Nom de l'arrêt" -#: sncfgtfs/models.py:136 +#: sncfgtfs/models.py:146 msgid "Stop description" msgstr "Description de l'arrêt" -#: sncfgtfs/models.py:141 +#: sncfgtfs/models.py:151 msgid "Stop longitude" msgstr "Longitude de l'arrêt" -#: sncfgtfs/models.py:145 +#: sncfgtfs/models.py:155 msgid "Stop latitude" msgstr "Latitude de l'arrêt" -#: sncfgtfs/models.py:150 +#: sncfgtfs/models.py:160 msgid "Zone ID" msgstr "ID de la zone" -#: sncfgtfs/models.py:154 +#: sncfgtfs/models.py:164 msgid "Stop URL" msgstr "URL de l'arrêt" -#: sncfgtfs/models.py:159 +#: sncfgtfs/models.py:169 msgid "Location type" msgstr "Type de localisation" -#: sncfgtfs/models.py:168 +#: sncfgtfs/models.py:178 msgid "Parent station" msgstr "Gare parente" -#: sncfgtfs/models.py:176 +#: sncfgtfs/models.py:186 msgid "Stop timezone" msgstr "Fuseau horaire de l'arrêt" -#: sncfgtfs/models.py:182 +#: sncfgtfs/models.py:192 msgid "Level ID" msgstr "ID du niveau" -#: sncfgtfs/models.py:187 +#: sncfgtfs/models.py:197 msgid "Wheelchair boarding" msgstr "Embarquement en fauteuil roulant" -#: sncfgtfs/models.py:195 +#: sncfgtfs/models.py:205 msgid "Platform code" msgstr "Code du quai" -#: sncfgtfs/models.py:214 +#: sncfgtfs/models.py:218 msgid "Stop" msgstr "Arrêt" -#: sncfgtfs/models.py:215 +#: sncfgtfs/models.py:219 msgid "Stops" msgstr "Arrêts" -#: sncfgtfs/models.py:223 sncfgtfs/models.py:399 sncfgtfs/models.py:538 -#: sncfgtfs/models.py:576 +#: sncfgtfs/models.py:227 sncfgtfs/models.py:403 sncfgtfs/models.py:542 +#: sncfgtfs/models.py:580 msgid "ID" msgstr "Identifiant" -#: sncfgtfs/models.py:235 +#: sncfgtfs/models.py:239 msgid "Route short name" msgstr "Nom court de la ligne" -#: sncfgtfs/models.py:240 +#: sncfgtfs/models.py:244 msgid "Route long name" msgstr "Nom long de la ligne" -#: sncfgtfs/models.py:245 +#: sncfgtfs/models.py:249 msgid "Route description" msgstr "Description de la ligne" -#: sncfgtfs/models.py:250 +#: sncfgtfs/models.py:254 msgid "Route type" msgstr "Type de ligne" -#: sncfgtfs/models.py:255 +#: sncfgtfs/models.py:259 msgid "Route URL" msgstr "URL de la ligne" -#: sncfgtfs/models.py:261 +#: sncfgtfs/models.py:265 msgid "Route color" msgstr "Couleur de la ligne" -#: sncfgtfs/models.py:267 +#: sncfgtfs/models.py:271 msgid "Route text color" msgstr "Couleur du texte de la ligne" -#: sncfgtfs/models.py:275 sncfgtfs/models.py:290 +#: sncfgtfs/models.py:279 sncfgtfs/models.py:294 msgid "Route" msgstr "Ligne" -#: sncfgtfs/models.py:276 +#: sncfgtfs/models.py:280 msgid "Routes" msgstr "Lignes" -#: sncfgtfs/models.py:284 +#: sncfgtfs/models.py:288 msgid "Trip ID" msgstr "ID du trajet" -#: sncfgtfs/models.py:297 sncfgtfs/models.py:544 +#: sncfgtfs/models.py:301 sncfgtfs/models.py:548 msgid "Service" msgstr "Service" -#: sncfgtfs/models.py:303 +#: sncfgtfs/models.py:307 msgid "Trip headsign" msgstr "Destination du trajet" -#: sncfgtfs/models.py:309 +#: sncfgtfs/models.py:313 msgid "Trip short name" msgstr "Nom court du trajet" -#: sncfgtfs/models.py:314 +#: sncfgtfs/models.py:318 msgid "Direction" msgstr "Direction" -#: sncfgtfs/models.py:321 +#: sncfgtfs/models.py:325 msgid "Block ID" msgstr "ID du bloc" -#: sncfgtfs/models.py:327 +#: sncfgtfs/models.py:331 msgid "Shape ID" msgstr "ID de la forme" -#: sncfgtfs/models.py:332 +#: sncfgtfs/models.py:336 msgid "Wheelchair accessible" msgstr "Accessible en fauteuil roulant" -#: sncfgtfs/models.py:339 +#: sncfgtfs/models.py:343 msgid "Bikes allowed" msgstr "Vélos autorisés" -#: sncfgtfs/models.py:391 sncfgtfs/models.py:405 sncfgtfs/models.py:648 +#: sncfgtfs/models.py:395 sncfgtfs/models.py:409 sncfgtfs/models.py:652 msgid "Trip" msgstr "Trajet" -#: sncfgtfs/models.py:392 +#: sncfgtfs/models.py:396 msgid "Trips" msgstr "Trajets" -#: sncfgtfs/models.py:410 sncfgtfs/models.py:698 +#: sncfgtfs/models.py:414 sncfgtfs/models.py:702 msgid "Arrival time" msgstr "Heure d'arrivée" -#: sncfgtfs/models.py:414 sncfgtfs/models.py:706 +#: sncfgtfs/models.py:418 sncfgtfs/models.py:710 msgid "Departure time" msgstr "Heure de départ" -#: sncfgtfs/models.py:425 +#: sncfgtfs/models.py:429 msgid "Stop sequence" msgstr "Séquence de l'arrêt" -#: sncfgtfs/models.py:430 +#: sncfgtfs/models.py:434 msgid "Stop headsign" msgstr "Destination de l'arrêt" -#: sncfgtfs/models.py:435 +#: sncfgtfs/models.py:439 msgid "Pickup type" msgstr "Type de prise en charge" -#: sncfgtfs/models.py:442 +#: sncfgtfs/models.py:446 msgid "Drop off type" msgstr "Type de dépose" -#: sncfgtfs/models.py:449 +#: sncfgtfs/models.py:453 msgid "Timepoint" msgstr "Ponctualité" -#: sncfgtfs/models.py:472 sncfgtfs/models.py:688 +#: sncfgtfs/models.py:476 sncfgtfs/models.py:692 msgid "Stop time" msgstr "Heure d'arrêt" -#: sncfgtfs/models.py:473 +#: sncfgtfs/models.py:477 msgid "Stop times" msgstr "Heures d'arrêt" -#: sncfgtfs/models.py:480 +#: sncfgtfs/models.py:484 msgid "Service ID" msgstr "ID du service" -#: sncfgtfs/models.py:484 +#: sncfgtfs/models.py:488 msgid "Monday" msgstr "Lundi" -#: sncfgtfs/models.py:488 +#: sncfgtfs/models.py:492 msgid "Tuesday" msgstr "Mardi" -#: sncfgtfs/models.py:492 +#: sncfgtfs/models.py:496 msgid "Wednesday" msgstr "Mercredi" -#: sncfgtfs/models.py:496 +#: sncfgtfs/models.py:500 msgid "Thursday" msgstr "Jeudi" -#: sncfgtfs/models.py:500 +#: sncfgtfs/models.py:504 msgid "Friday" msgstr "Vendredi" -#: sncfgtfs/models.py:504 +#: sncfgtfs/models.py:508 msgid "Saturday" msgstr "Samedi" -#: sncfgtfs/models.py:508 +#: sncfgtfs/models.py:512 msgid "Sunday" msgstr "Dimanche" -#: sncfgtfs/models.py:512 sncfgtfs/models.py:654 +#: sncfgtfs/models.py:516 sncfgtfs/models.py:658 msgid "Start date" msgstr "Date de début" -#: sncfgtfs/models.py:516 +#: sncfgtfs/models.py:520 msgid "End date" msgstr "Date de fin" -#: sncfgtfs/models.py:521 sncfgtfs/models.py:559 +#: sncfgtfs/models.py:525 sncfgtfs/models.py:563 msgid "Transport type" msgstr "Type de transport" -#: sncfgtfs/models.py:529 +#: sncfgtfs/models.py:533 msgid "Calendar" msgstr "Calendrier" -#: sncfgtfs/models.py:530 +#: sncfgtfs/models.py:534 msgid "Calendars" msgstr "Calendriers" -#: sncfgtfs/models.py:549 +#: sncfgtfs/models.py:553 msgid "Date" msgstr "Date" -#: sncfgtfs/models.py:553 +#: sncfgtfs/models.py:557 msgid "Exception type" msgstr "Type d'exception" -#: sncfgtfs/models.py:567 +#: sncfgtfs/models.py:571 msgid "Calendar date" msgstr "Date du calendrier" -#: sncfgtfs/models.py:568 +#: sncfgtfs/models.py:572 msgid "Calendar dates" msgstr "Dates du calendrier" -#: sncfgtfs/models.py:582 +#: sncfgtfs/models.py:586 msgid "From stop" msgstr "Depuis l'arrêt" -#: sncfgtfs/models.py:589 +#: sncfgtfs/models.py:593 msgid "To stop" msgstr "Jusqu'à l'arrêt" -#: sncfgtfs/models.py:594 +#: sncfgtfs/models.py:598 msgid "Transfer type" msgstr "Type de correspondance" -#: sncfgtfs/models.py:600 +#: sncfgtfs/models.py:604 msgid "Minimum transfer time" msgstr "Temps de correspondance minimum" -#: sncfgtfs/models.py:605 +#: sncfgtfs/models.py:609 msgid "Transfer" msgstr "Correspondance" -#: sncfgtfs/models.py:606 +#: sncfgtfs/models.py:610 msgid "Transfers" msgstr "Correspondances" -#: sncfgtfs/models.py:613 +#: sncfgtfs/models.py:617 msgid "Feed publisher name" msgstr "Nom de l'éditeur du flux" -#: sncfgtfs/models.py:617 +#: sncfgtfs/models.py:621 msgid "Feed publisher URL" msgstr "URL de l'éditeur du flux" -#: sncfgtfs/models.py:622 +#: sncfgtfs/models.py:626 msgid "Feed language" msgstr "Langue du flux" -#: sncfgtfs/models.py:626 +#: sncfgtfs/models.py:630 msgid "Feed start date" msgstr "Date de début du flux" -#: sncfgtfs/models.py:630 +#: sncfgtfs/models.py:634 msgid "Feed end date" msgstr "Date de fin du flux" -#: sncfgtfs/models.py:635 +#: sncfgtfs/models.py:639 msgid "Feed version" msgstr "Version du flux" -#: sncfgtfs/models.py:639 +#: sncfgtfs/models.py:643 msgid "Feed info" msgstr "Information du flux" -#: sncfgtfs/models.py:640 +#: sncfgtfs/models.py:644 msgid "Feed infos" msgstr "Informations du flux" -#: sncfgtfs/models.py:658 +#: sncfgtfs/models.py:662 msgid "Start time" msgstr "Heure de début" -#: sncfgtfs/models.py:662 sncfgtfs/models.py:710 +#: sncfgtfs/models.py:666 sncfgtfs/models.py:714 msgid "Schedule relationship" msgstr "Relation de la planification" -#: sncfgtfs/models.py:671 sncfgtfs/models.py:681 +#: sncfgtfs/models.py:675 sncfgtfs/models.py:685 msgid "Trip update" msgstr "Mise à jour du trajet" -#: sncfgtfs/models.py:672 +#: sncfgtfs/models.py:676 msgid "Trip updates" msgstr "Mises à jour des trajets" -#: sncfgtfs/models.py:694 +#: sncfgtfs/models.py:698 msgid "Arrival delay" msgstr "Retard à l'arrivée" -#: sncfgtfs/models.py:702 +#: sncfgtfs/models.py:706 msgid "Departure delay" msgstr "Retard au départ" -#: sncfgtfs/models.py:719 +#: sncfgtfs/models.py:723 msgid "Stop time update" msgstr "Mise à jour du temps d'arrêt" -#: sncfgtfs/models.py:720 +#: sncfgtfs/models.py:724 msgid "Stop time updates" msgstr "Mises à jour des temps d'arrêt" diff --git a/sncfgtfs/management/commands/update_sncf_gtfs.py b/sncfgtfs/management/commands/update_sncf_gtfs.py index f80ff28..2d54ece 100644 --- a/sncfgtfs/management/commands/update_sncf_gtfs.py +++ b/sncfgtfs/management/commands/update_sncf_gtfs.py @@ -265,7 +265,7 @@ class Command(BaseCommand): dep_time = stop_time_dict['departure_time'] dep_time = int(dep_time[:2]) * 3600 + int(dep_time[3:5]) * 60 + int(dep_time[6:]) st = StopTime( - id=f"{stop_time_dict['trip_id']}-{stop_time_dict['stop_sequence']}", + id=f"{stop_time_dict['trip_id']}-{stop_time_dict['stop_id']}", trip_id=stop_time_dict['trip_id'], arrival_time=timedelta(seconds=arr_time), departure_time=timedelta(seconds=dep_time), diff --git a/sncfgtfs/management/commands/update_sncf_gtfs_rt.py b/sncfgtfs/management/commands/update_sncf_gtfs_rt.py index b66bbdc..0cd7d34 100644 --- a/sncfgtfs/management/commands/update_sncf_gtfs_rt.py +++ b/sncfgtfs/management/commands/update_sncf_gtfs_rt.py @@ -3,6 +3,7 @@ from zoneinfo import ZoneInfo import requests from django.core.management import BaseCommand +from django.db.models import Q, Max from sncfgtfs.gtfs_realtime_pb2 import FeedMessage from sncfgtfs.models import Calendar, CalendarDate, StopTime, StopTimeUpdate, Trip, TripUpdate, Stop @@ -41,6 +42,11 @@ class Command(BaseCommand): headsign = trip_id[5:-1] trip_qs = Trip.objects.all() trip_ids = trip_qs.values_list('id', flat=True) + + last_stop_queryset = StopTime.objects.filter( + stop__parent_station_id=trip_update.stop_time_update[-1].stop_id, + ).values('trip_id') + trip_ids = trip_ids.intersection(last_stop_queryset) for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update): stop_id = stop_time_update.stop_id st_queryset = StopTime.objects.filter(stop__parent_station_id=stop_id) @@ -49,14 +55,19 @@ class Command(BaseCommand): trip_ids_restrict = trip_ids.intersection(st_queryset.values('trip_id')) if trip_ids_restrict: trip_ids = trip_ids_restrict + else: + stop = Stop.objects.get(id=stop_id) + self.stdout.write(self.style.WARNING(f"Warning: No trip is found passing by stop " + f"{stop.name} ({stop_id})")) + trip_ids = set(trip_ids) route_ids = set(Trip.objects.filter(id__in=trip_ids).values_list('route_id', flat=True)) - self.stdout.write(f"{len(route_ids)} routes found on trip for train {headsign}") + self.stdout.write(f"{len(route_ids)} routes found on trip for new train {headsign}") if not route_ids: self.stdout.write(f"Route not found for trip {trip_id}.") continue elif len(route_ids) > 1: self.stdout.write(f"Multiple routes found for trip {trip_id}.") - continue + self.stderr.write(", ".join(route_ids)) route_id = route_ids.pop() Calendar.objects.update_or_create( @@ -98,15 +109,19 @@ class Command(BaseCommand): stop_id = stop_time_update.stop_id stop = Stop.objects.get(id=stop_id) if stop.location_type == 1: - if stop_sequence == 0: - stop = sample_trip.stop_times.get(stop_sequence=0).stop + if not StopTime.objects.filter(trip_id=trip_id).exists(): + stop = StopTime.objects.get(trip_id=sample_trip.id, + stop__parent_station_id=stop_id).stop + elif StopTime.objects.filter(trip_id=trip_id, stop__parent_station_id=stop_id).exists(): + stop = StopTime.objects.get(trip_id=trip_id, stop__parent_station_id=stop_id).stop else: - previous_stop = sample_trip.stop_times.get(stop_sequence=stop_sequence - 1).stop - stop = next(s for s in stop.children.all() \ - if s.location_type == 0 and s.stop_type == previous_stop.stop_type) + stop = next(s for s in Stop.objects.filter(parent_station_id=stop_id).all() + for s2 in StopTime.objects.filter(trip_id=trip_id).all() + if s.stop_type in s2.stop.stop_type + or s2.stop.stop_type in s.stop_type) stop_id = stop.id StopTime.objects.update_or_create( - id=f"{trip_id}-{stop_sequence}", + id=f"{trip_id}-{stop_id}", trip_id=trip_id, defaults={ "stop_id": stop_id, @@ -130,12 +145,46 @@ class Command(BaseCommand): start_time=trip_update.trip.start_time, schedule_relationship=trip_update.trip.schedule_relationship, ) - for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update): - if not StopTime.objects.filter(trip_id=trip_id, stop_sequence=stop_sequence).exists(): - self.stdout.write(f"Stop {stop_sequence} does not exist in GTFS feed for trip {trip_id}.") - continue - st = StopTime.objects.get(trip_id=trip_id, stop_sequence=stop_sequence) + for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update): + stop_id = stop_time_update.stop_id + if stop_id.startswith('StopArea:'): + if StopTime.objects.filter(trip_id=trip_id, stop__parent_station_id=stop_id).exists(): + stop = StopTime.objects.get(trip_id=trip_id, stop__parent_station_id=stop_id).stop + else: + stop = next(s for s in Stop.objects.filter(parent_station_id=stop_id).all() + for s2 in StopTime.objects.filter(trip_id=trip_id).all() + if s.stop_type in s2.stop.stop_type + or s2.stop.stop_type in s.stop_type) + + st, _created = StopTime.objects.update_or_create( + id=f"{trip_id}-{stop.id}", + trip_id=trip_id, + stop_id=stop.id, + defaults={ + "stop_sequence": stop_sequence, + "arrival_time": datetime.fromtimestamp(stop_time_update.arrival.time, + tz=ZoneInfo("Europe/Paris")) - start_dt, + "departure_time": datetime.fromtimestamp(stop_time_update.departure.time, + tz=ZoneInfo("Europe/Paris")) - start_dt, + "pickup_type": 0 if stop_time_update.departure.time else 1, + "drop_off_type": 0 if stop_time_update.arrival.time else 1, + } + ) + elif stop_time_update.schedule_relationship == 1: + st = StopTime.objects.get(Q(stop=stop_id) | Q(stop__parent_station_id=stop_id), + trip_id=trip_id) + if st.pickup_type != 0 or st.drop_off_type != 0: + st.pickup_type = 0 + st.drop_off_type = 0 + st.save() + else: + st = StopTime.objects.get(Q(stop=stop_id) | Q(stop__parent_station_id=stop_id), + trip_id=trip_id) + if st.stop_sequence != stop_sequence: + st.stop_sequence = stop_sequence + st.save() + st_update = StopTimeUpdate( trip_update=tu, stop_time=st, diff --git a/sncfgtfs/migrations/0002_alter_trip_options_tripupdate_stoptimeupdate.py b/sncfgtfs/migrations/0002_alter_trip_options_tripupdate_stoptimeupdate.py index 1be058f..e770d5c 100644 --- a/sncfgtfs/migrations/0002_alter_trip_options_tripupdate_stoptimeupdate.py +++ b/sncfgtfs/migrations/0002_alter_trip_options_tripupdate_stoptimeupdate.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.1 on 2024-02-04 19:58 +# Generated by Django 5.0.1 on 2024-02-06 06:59 import django.db.models.deletion from django.db import migrations, models @@ -35,9 +35,12 @@ class Migration(migrations.Migration): models.IntegerField( choices=[ (0, "Scheduled"), - (1, "Skipped"), - (2, "No data"), - (3, "Unscheduled"), + (1, "Added"), + (2, "Unscheduled"), + (3, "Canceled"), + (5, "Replacement"), + (6, "Duplicated"), + (7, "Deleted"), ], default=0, verbose_name="Schedule relationship", diff --git a/sncfgtfs/models.py b/sncfgtfs/models.py index c531b95..36fe593 100644 --- a/sncfgtfs/models.py +++ b/sncfgtfs/models.py @@ -58,7 +58,17 @@ class ExceptionType(models.IntegerChoices): REMOVED = 2, _("Removed") -class ScheduleRelationship(models.IntegerChoices): +class TripScheduleRelationship(models.IntegerChoices): + SCHEDULED = 0, _("Scheduled") + ADDED = 1, _("Added") + UNSCHEDULED = 2, _("Unscheduled") + CANCELED = 3, _("Canceled") + REPLACEMENT = 5, _("Replacement") + DUPLICATED = 6, _("Duplicated") + DELETED = 7, _("Deleted") + + +class StopScheduleRelationship(models.IntegerChoices): SCHEDULED = 0, _("Scheduled") SKIPPED = 1, _("Skipped") NO_DATA = 2, _("No data") @@ -199,12 +209,6 @@ class Stop(models.Model): @property def stop_type(self): train_type = self.id.split('StopPoint:OCE')[1].split('-')[0] - if train_type == "Train TER": - train_type = "TER" - elif train_type == "INTERCITES": - train_type = "INTER-CITÉS" - elif train_type == "INTERCITES de nuit": - train_type = "INTER-CITÉS de nuit" return train_type def __str__(self): @@ -660,8 +664,8 @@ class TripUpdate(models.Model): schedule_relationship = models.IntegerField( verbose_name=_("Schedule relationship"), - choices=ScheduleRelationship, - default=ScheduleRelationship.SCHEDULED, + choices=TripScheduleRelationship, + default=TripScheduleRelationship.SCHEDULED, ) def __str__(self): @@ -708,8 +712,8 @@ class StopTimeUpdate(models.Model): schedule_relationship = models.IntegerField( verbose_name=_("Schedule relationship"), - choices=ScheduleRelationship, - default=ScheduleRelationship.SCHEDULED, + choices=StopScheduleRelationship, + default=StopScheduleRelationship.SCHEDULED, ) def __str__(self):