# Daten: Überblick ## Zweck Erklärt, wie Daten im Netzwerk modelliert und lokal gespeichert sind. ## Wichtige Dateien - `app/src/main/kotlin/de/traewelling/app/data/model/Models.kt` - `app/src/main/kotlin/de/traewelling/app/data/model/SevModels.kt` - `app/src/main/kotlin/de/traewelling/app/data/model/RoadRouteModels.kt` - `app/src/main/kotlin/de/traewelling/app/data/model/TransitRouteModels.kt` - `app/src/main/kotlin/de/traewelling/app/data/local/StatusEntity.kt` - `app/src/main/kotlin/de/traewelling/app/service/GpsJourneyTimeEstimator.kt` - `app/src/main/kotlin/de/traewelling/app/service/JourneyTimeResolver.kt` ## Modelle (Retrofit / Gson) Die Modelle verwenden Gson. `@SerializedName` legt abweichende JSON-Feldnamen fest; gleich benannte Felder werden direkt zugeordnet. ### Auth-Modelle | Modell | Beschreibung | | -------------------- | ----------------------------------------------------------------- | | `OAuthTokenResponse` | Token-Antwort mit accessToken, refreshToken, tokenType, expiresIn | `AuthSession` in `util/AuthSession.kt` ist dagegen ein lokaler atomarer Snapshot aus Server-URL, optionalem nicht leerem Access-Token und Revisionskennung. Er ist kein Retrofit-/OAuth-Antwortmodell. Die Revision trennt auch Abmeldung und erneute Anmeldung mit denselben Zugangsdaten; vergleichende Persistenzoperationen und der Feature-ViewModel-Store verwenden diesen Zustand. [Auth](../module/auth.md) beschreibt seinen Lebenszyklus. ### User-Modelle | Modell | Beschreibung | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `UserResponse` | Wrapper für User-Daten | | `User` | Nutzerdaten: id, uuid, username, displayName, profilePicture, bio, totalDistance, totalDuration, points, following, muted, blocked | ### Status-Modelle | Modell | Beschreibung | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `StatusListResponse` | Wrapper für Liste von Statusen mit PaginationLinks | | `SingleStatusResponse` | Wrapper für einzelnen Status | | `Status` | Check-in-Status: id, body, createdAt, likes, liked, visibility, business, user, checkin, event, tags | | `StatusUser` | User-Kurzform für Status | | `CheckinInfo` | Check-in-Details mit origin, destination, operator, numerischem trip, tripUuid, Plan-/Echtzeitdaten und manuellen Zeitkorrekturen | | `StopOperator` | Betreiber: id als String (Legacy-Zahl oder UUID), bevorzugte uuid, name, identifiers | ### Station-Modelle | Modell | Beschreibung | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `StationIdentifier` | Kennung mit type, identifier sowie optional name und origin | | `TrainStation` | Bahnhof: interne id, uuid, name, latitude, longitude, identifiers; ibnr und rilIdentifier werden aus identifiers berechnet | | `StopStation` | Konkreter Halt: id, uuid, verschachtelte station, arrivalPlanned/arrivalReal, departurePlanned/departureReal, Gleis- und Ausfallinformationen | | `StationSearchResponse` | Liste von TrainStation | | `DepartureResponse` | Liste von DepartureTrip | ### Trip-Modelle | Modell | Beschreibung | | ------------------- | ----------------------------------------------------------------------------------- | | `DepartureTrip` | Abfahrt: tripId, line, direction, station, plannedWhen, realWhen (JSON when), platform, cancelled; berechnete delayMinutes | | `HafasLine` | Linieninfo: name, fahrtNr, product, mode; kein veraltetes line.operator | | `TripResponse` | Wrapper für TripDetails | | `TripDetails` | Trip: numerische id, uuid, Provider-tripId, lineName, category, operator, stopovers | | `StopoversResponse` | Map von tripId zu StopStation-Liste | ### Check-in-Modelle | Modell | Beschreibung | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `TravelReason` | Enum für den Reisegrund beim Check-in: PRIVATE=0, BUSINESS=1, COMMUTE=2 | | `CheckInRequest` | Request für Check-in: tripId, lineName, startStationId, destinationStationId, departure, arrival, body, business, visibility | | `CheckInResponse` | Antwort mit Status und Points | | `CheckInResult` | Enthält Status und CheckInPoints | | `CheckInPoints` | Gesamtpunkte und calculation mit base, reason, distance, factor | | `CheckInConflictResponse` / `CheckInConflictData` | HTTP-409-Envelope mit data.conflicts als Status-Liste | | `CheckInConflictException` | Konfliktliste und verständliche Meldung mit Linie, Ziel und Status-ID | | `UpdateStatusRequest` | PUT-Request für Status-Updates; Zielwechsel mit destinationId und destinationArrivalPlanned gemeinsam | ### Statistik-Modelle | Modell | Beschreibung | | -------------------- | ------------------------------------------------- | | `StatisticsResponse` | Wrapper für StatisticsData | | `StatisticsData` | Statistiken: categories, operators, time, purpose | | `StatEntry` | Kategorie/Betreiber-Stat: name, count, duration | | `StatDay` | Tagesstatistik: date, count, duration | ### Notification-Modelle | Modell | Beschreibung | | -------------------------- | --------------------------------------------------------------------------------- | | `NotificationListResponse` | Liste mit Pagination | | `Notification` | notification: id, type, lead, notice, link, readAt, createdAt, createdAtForHumans | ### Pagination | Modell | Beschreibung | | ----------------- | ------------------------------------- | | `PaginationLinks` | first, last, prev, next URLs | | `PaginationMeta` | currentPage, lastPage, total, perPage | ## Wichtige Extensions ### Stationsdaten und Haltidentität `StopStation.stationId` liest ausschließlich `station.id`. Stopover-`id` wird nie für Stationsanfragen oder Check-ins verwendet: Träwelling widmet dieses Feld nach dem 30.11.2026 zur Halt-ID um. `stationName` liest `station.name`; nur für die Anzeige alter gecachter Statusdaten bleibt ein privater Rückfall auf das frühere JSON-Feld `name`. `stationIdentifier(type)` liest ausschließlich `station.identifiers`. `TrainStation.ibnr` nutzt `de_db_ibnr`, `TrainStation.rilIdentifier` nutzt `de_db_ril100`. `matchesStopover(other)` vergleicht bevorzugt die Stopover-UUID. Falls eine UUID fehlt, müssen interne Station-ID und eine geplante Ankunft oder Abfahrt übereinstimmen. ISO-Zeitstempel werden als Zeitpunkte verglichen. Wiederholte Besuche derselben Station auf einer Rundfahrt bleiben dadurch unterscheidbar. ### Effektive Zeiten `effectiveArrival` ist `arrivalReal ?: arrivalPlanned`, `effectiveDeparture` ist `departureReal ?: departurePlanned`. Diese Modellhelfer wählen vorhandene Felder, prüfen aber deren Parsebarkeit nicht. Die Legacy-Felder `arrival` und `departure` werden nicht gelesen. Der Tracking-Service berücksichtigt manuelle Einstieg-/Zielzeiten in seiner internen Route. Das Fahrtdetail behält API-Halte und manuelle Check-in-Zeiten getrennt; die Live-Anzeige verwendet `JourneyTimeResolver` statt GPS-Prognosen in Echtzeitfelder zu schreiben. `DepartureTrip.delayMinutes` berechnet die Differenz zwischen `when` und `plannedWhen` in Minuten. Bei fehlender oder ungültiger Zeit ist der Wert `null`; das Legacy-Feld `delay` wird nicht verwendet. ### Lokale Tracking- und Zeitmodelle | Modell/Feld | Bedeutung und Lebensdauer | | --- | --- | | `TrackingStop.plannedDepartureMillis` | Geplante Abfahrt des konkreten Besuchs, getrennt von `effectiveDepartureMillis`; optional für alte Cache-Routen. | | `TrackingLiveState.gpsTimes` | Optionales prozesslokales Ergebnis der GPS-Zeitauswertung; nur passende eigene aktive Fahrt, nicht serialisiert. | | `TrackingLiveState.sessionRevision` | Bindet den RAM-Livezustand an die Zugangsgeneration; gleicher numerischer Status eines anderen Kontos darf ihn nicht übernehmen. Kein Fahrtcache-Feld und keine Zugangsdaten. | | `TrackingLiveState.gpsGeometrySource` | Optional `TRIP_POLYLINE` beziehungsweise `ROAD_MODEL` aus der aktuellen Zeitauswertung, sonst frischer aktiver Railprojektion der Stationsengine. Kein Prefetchmarker oder ETA-Beleg; nicht persistiert. | | `RideRecognitionState.authSessionRevision` | Bindet prozesslokale Fahrtvorschläge an dieselbe aktuelle Zugangsgeneration; das Check-in-ViewModel verwirft fremde oder frühere Revisionen. Nicht persistiert. | | `GpsStopTime` | Besuchsschlüssel, Station-ID, Plan-Ankunft/-Abfahrt und optionale lokale Ankunft/-Abfahrt; Flags unterscheiden beobachtet und geschätzt. | | `GpsJourneyTimes` | Liste besuchsbezogener GPS-Zeiten, Unterstützungs-Fixzeitpunkt und Gültigkeitsende; spätestens nach 30 Sekunden unbrauchbar. Geeignete Folgefixes ohne neue Prognose dürfen das Ende nicht verlängern. | | `JourneyTime` | Aufgelöster Anzeigezeitpunkt mit `JourneyTimeSource`, Planzeit, Quellenlabel und optionaler positiver/negativer Abweichung in Minuten. | `JourneyTimeResolver` prüft je Ereignis gültige GPS-Zeit, manuelle Zeit, parsebare API-Echtzeit und Planzeit in dieser Reihenfolge. GPS-Matching bevorzugt UUID und verlangt in beiden Zuordnungswegen das exakt gleiche nullable Planpaar einschließlich Markeranwesenheit. Ohne UUID sind zusätzlich gleiche vorhandene Station-ID, mindestens ein parsebarer Planmarker und ein eindeutiger Treffer erforderlich. Diese strengere GPS-Zuordnung ersetzt nicht den allgemeinen API-Helfer `matchesStopover`. Die GPS-Erweiterung führt keine neuen Retrofit-, Room- oder Status-PUT-Felder ein. Prognosen und Standortbeobachtungen bleiben RAM-Zustand; Details stehen unter [GPS-Zeiten](../module/gps-zeiten.md). ### Lokale SEV-Quellen und Besuchshinweise Die Modelle in `SevModels.kt` ergänzen die öffentliche Bahnhofskarte, ohne den Träwelling-Vertrag zu verändern: | Modell | Bedeutung | | --- | --- | | `SevMap` | Stations-Slug, öffentliche Quellen-URL, Bahnhofkoordinate zur Plausibilitätsprüfung, SEV-Punkte, Weg-/Maßnahmenhinweise und Abrufzeit. | | `SevPoint` | Öffentliche `sev.*`-Kennung, physische Koordinate, optionale Richtungsbezeichnung und Quellen-Bearbeitungsstand. | | `SevStopInfo` | Quellenlink, Bezeichnung, Wegbeschreibung und optionales eindeutig zugeordnetes Koordinatenpaar für einen Haltbesuch; bei Rückfall zusätzlich ein Grund. | `SevStopInfo.hasCoordinates` unterscheidet einen nutzbaren Punkt von einem reinen Hinweis. GeoJSON liefert Längengrad vor Breitengrad; das App-Modell hält beide benannt. Der Bearbeitungsstand eines Punkts ersetzt kein Gültigkeitsintervall. Ein allgemeiner Kartenmittelpunkt wird niemals als SEV-Punkt ausgegeben. Die Zuordnung bleibt lokal. API-Stations-ID, Stopover-UUID, Plan-/Echtzeit und Check-in-Werte behalten ihre Identität und Bedeutung; es werden keine SEV-Koordinaten per Status-PUT übertragen. Die Auflösungsregeln und Lebensdauer stehen unter [SEV-Ersatzhaltestellen](../module/sev-haltestellen.md). ### Lokale Linienzug-Geometrien `RoadRouteModels.kt` definiert gemeinsame Geometrieformen; `TransitRouteModels.kt` ergänzt die native Fahrtbindung. Quellen bleiben ausdrücklich getrennt von Gerätefixes: | Modell | Bedeutung und Lebensdauer | | --- | --- | | `RoutePoint` | Benannter Breitengrad/Längengrad eines öffentlichen Haltpunkts oder Geometrieknotens; kein Gerätefix für Routinganfragen. | | `RouteGeometry` / Alias `RoadRouteGeometry` | Geordnete Endpunkte `from`/`to`, Kandidatenlinienzüge in `alternatives` und `fetchedAtMillis`; keine Provider-ETA. Road erlaubt bis zu drei, native Abschnitte genau einen Kandidaten. | | `GpsGeometrySource` | `ROAD_MODEL` für approximative SEV-Straßenwege beziehungsweise `TRIP_POLYLINE` für einen nativen Träwelling-Streckenverlauf. | | `GpsSegmentGeometry` | Bindet `geometry` über `fromKey`, `toKey` und `source` an zwei konkrete geordnete Haltbesuche, auch bei wiederholten Stationsbesuchen. Bestehende Road-Aufrufer behalten `ROAD_MODEL` als Standard. | | `TransitRouteVisit` | Besuchsschlüssel, Stations-ID/-UUID, nullable Planpaar, öffentliche Stationskoordinate und Ausfallstatus; keine Echtzeit, Gleise oder Geräteposition. | | `TransitRouteRequest` | Status-ID, Tripidentität und vollständige geordnete Check-in-Besuchsfolge inklusive gestrichener Zwischenbesuche. | | `TransitRouteGeometry` | Derselbe Request, eindeutig gebundene verwendbare Abschnitte und Abrufzeit; native Form höchstens 15 Minuten verwendbar. | Die lokale [GPS-Zeitauswertung](../module/gps-zeiten.md) projiziert auf geeignete Formen. Native Abschnitte können zusätzlich gerichtete Trackinghilfen stützen; physische Ankunft und Zielabschluss bleiben Stationsbelege. Das Pkw-Modell ist keine offizielle SEV-Busroute; der native Backend-Linienzug ist ebenfalls keine garantierte aktuelle Gleisführung. Weder eine Routingdauer noch ein Netzabschluss erzeugt eine GPS-Istzeit. Gerätepositionen werden nicht an diese Quellen gesendet. Geometrien bleiben in Service-/Repository-RAM und erweitern weder Room noch `trip_tracking_state`; ein Neustart übernimmt keine persistierte Geometrie. Ein tatsächlicher Formwechsel verwirft Zukunftsprognose und Bewegungsfenster, erhält aber bestätigte tatsächliche Ereignisse bei unveränderter Besuchs-/Haltbasis. ### List.deduplicate() ```kotlin fun List.deduplicate(): List ``` Dedupliziert aufeinanderfolgende Halte derselben Station mit gleichen geplanten Zeiten. Unterschiedliche Stationen oder unterschiedliche vorhandene Stopover-UUIDs bleiben auch bei identischen Zeiten erhalten. Bei doppelten Einträgen wird eine vorhandene Gleisinformation beziehungsweise der kürzere Stationsname bevorzugt. ### StopoversResponse.allStopovers() ```kotlin fun StopoversResponse.allStopovers(): List ``` Flacht die Map von tripId zu StopStation-Liste in eine flache Liste. ## Manuelle Zeitedits Die API unterstützt manuelle Korrekturen von Abfahrts-/Ankunftszeiten: - `manualDeparture`: Manuell korrigierte Abfahrtszeit - `manualArrival`: Manuell korrigierte Ankunftszeit Diese werden im CheckInInfo-Modell gespeichert und beim Auflösen der Anzeige berücksichtigt. Eine frische eindeutige GPS-Zeit hat in der eigenen aktiven Begleitung Vorrang; außerhalb dieser Beobachtung bleiben manuelle Zeiten vor API-/Planzeit. Das Bearbeitungsformular wird ohne GPS-Werte initialisiert. Bei einem Zielwechsel über `UpdateStatusRequest` wird zusätzlich zur neuen internen Station-ID deren geplante Ankunft als `destinationArrivalPlanned` gesendet. Der Upstream-Request verlangt beide Felder gemeinsam. Für reine Text-, Sichtbarkeits- oder Zeitänderungen werden sie weggelassen. ## Reisegrund Der Reisegrund wird beim Check-in im Feld `business` übertragen. Das Android-Modell verwendet dafür `TravelReason` mit den API-Werten `0` (Privat), `1` (Geschäftlich) und `2` (Arbeitsweg). Standard ist `TravelReason.PRIVATE`. ## Verwandte Seiten - [Datenbank](./datenbank.md) - [Schemas](./schemas.md) - [Check-in](../module/checkin.md) - [GPS-Zeiten](../module/gps-zeiten.md) - [SEV-Ersatzhaltestellen](../module/sev-haltestellen.md) - [Träwelling-API-Kompatibilität](../api/traewelling-kompatibilitaet.md)