Zum Inhalt

Tachographendaten: Parsing, Speicherung und Zugriff

1. Zweck und Geltungsbereich

Dieses Dokument beschreibt den technischen Datenweg von Fahrerkarte-C-Dateien und Massenspeicher-M-Dateien: vom unveränderten DDD-Original über Klassifizierung, Parsing und Signaturprüfung bis zur Speicherung, archivübergreifenden Deduplizierung und Abfrage durch UI, Reporting und Compliance. Es richtet sich an Entwickler und Administratoren.

Die wichtigsten Garantien sind:

  • Das komprimierte DDD-Original bleibt die revisionssichere Beweisquelle.
  • Archiv-, Signatur-, Abschnitts- und Rohdatensatznachweise bleiben archivbezogen.
  • Erneutes Parsen ersetzt nur abgeleitete Daten des betreffenden Archivs.
  • Fahrer- und Fahrzeuggesamtsichten deduplizieren über alle zugeordneten Archive.
  • Eine Einzelarchivansicht zeigt weiterhin ausschließlich Beobachtungen dieser Datei.
  • Parser- und Signaturstatus werden nicht durch die Deduplizierung verändert.
flowchart LR
    A["DDD-Eingang"] --> B["Lokales GZIP-Original"]
    B --> C["Archivmetadaten und SHA-256"]
    B --> D{"Binäre Klassifizierung"}
    D -->|"C-Datei"| E["DriverCardFullParser"]
    D -->|"M-Datei"| F["VehicleUnitParser"]
    E --> G["Signaturprüfung C"]
    F --> H["Signaturprüfung M"]
    G --> I["Archivbezogene C-Tabellen"]
    H --> J["Archivbezogene M-Tabellen"]
    I --> K["Kanonische Gesamtsicht"]
    J --> K
    K --> L["Repository"]
    L --> M["UI / Reporting / Compliance"]
    I --> N["Einzelarchivansicht"]
    J --> N

2. Archivierung und gemeinsame Importpipeline

ddd_archive ist der Einstiegspunkt für jede archivierte Datei. Die Tabelle enthält unter anderem Archiv-ID, Dateiname, Original- und Speicherhash, Ablagepfad, Größen, Quelle, Zeitstempel, DDD-Art, Generation, Parserstatus, Signaturstatus, Identitätsfelder und fachlichen Datenzeitraum.

Der normale Import läuft über TachographImportService.import_archive_id():

  1. Archivzeile laden und logischen Löschstatus prüfen.
  2. GZIP-Datei aus stored_path lesen.
  3. Binärinhalt klassifizieren; Dateiname oder Präfix allein entscheidet nicht.
  4. Klassifizierung und Audit mit einer neuen Korrelations-ID speichern.
  5. C- oder M-Parser ausführen.
  6. Signaturen gegen den Trust Store prüfen.
  7. Abgeleitete Archivdaten ersetzen und Archivstatus aktualisieren.
  8. Fahrzeug-/Personenverknüpfungen, Downloadfristen und Auswertungscaches nachführen.

Die beiden Hashes in ddd_archive haben unterschiedliche Aufgaben:

Feld Inhalt Verwendung
file_hash_sha256 SHA-256 der unkomprimierten DDD-Bytes Beweist den unveränderten fachlichen Dateiinhalt
stored_hash_sha256 SHA-256 der tatsächlich abgelegten GZIP-Datei Erkennt Änderungen oder Beschädigungen der Archivdatei

Die Wartungsmigration prüft bei jedem aktiven C-/M-Archiv beide Ebenen: erst die gespeicherte Datei, dann den dekomprimierten Originalinhalt.

sequenceDiagram
    participant Import as TachographImportService
    participant Archive as ddd_archive
    participant File as GZIP-Original
    participant Parser as C-/M-Parser
    participant Verify as Signaturdienst
    participant DB as Fachtabellen

    Import->>Archive: Archiv-ID und Metadaten laden
    Import->>File: GZIP öffnen und DDD-Bytes lesen
    Import->>Import: Inhalt klassifizieren
    Import->>Parser: Exakte Bytes parsen
    Parser-->>Import: Struktur, Identität, Beobachtungen, Rohbezüge
    Import->>Verify: Signierte Blöcke/Sektionen prüfen
    Verify-->>Import: VALID / INVALID / UNVERIFIABLE / MISSING
    Import->>DB: Archivbezogene Daten ersetzen
    Import->>Archive: Generation, Zeitraum und Status aktualisieren
    Import->>Archive: Audit mit Korrelations-ID schreiben

3. Parsing von C-Dateien

3.1 Segmentierung

DriverCardFullParser liest die Datei vollständig in Bytes und zerlegt sie ohne Verlust der EF- und Signaturgrenzen. Jedes Segment besitzt einen 5-Byte-Kopf:

  1. zwei Byte file_id;
  2. ein Byte kind (0 G1-Daten, 1 G1-Signatur, 2 G2-Daten, 3 G2-Signatur);
  3. zwei Byte Payloadlänge in Big-Endian;
  4. exakt length Payloadbytes.

Eine Payload, die über das Dateiende hinausläuft, oder Restbytes nach dem letzten Segment führen zum Parsefehler. Für jedes Segment werden Offset, Länge und SHA-256 der Payload ermittelt. Der Parser hält zusätzlich die exakten Segmentbytes für die spätere kryptografische Prüfung vor.

3.2 EF-Zuordnung und Generation

Bekannte EFs werden über ihre Datei-ID einem Decoder zugeordnet. Wesentliche EFs sind:

EF Inhalt
0501, 0525 Anwendungsidentifikation, Generation und Recordanzahlen
0520, 0521 Karteninhaber- und Führerscheinidentität
0504 tägliche Fahreraktivitätsdaten
0502, 0503 Ereignisse und Störungen
0505 verwendete Fahrzeuge und Kilometerstände
0506, 0524 Orte und GNSS-Positionen
0507, 050E aktuelle Nutzung und letzter Download
05260530 G2V2-Authentifizierung, Grenzen und Be-/Entladung
C100, C101, C108, C109 Karten-, CA-, CardSign- und Linkzertifikate

Die Generation wird aus vorhandenen Anwendungs- und G2V2-EFs abgeleitet und intern als G1, G2 oder G2V2 geführt. In ddd_archive wird daraus G1, G2_V1 oder G2_V2.

3.3 Fachliche Dekodierung

  • Identitäts- und Kartendaten werden aus den vorgesehenen EFs zusammengesetzt; es werden keine Werte aus benachbarten Bytebereichen geschätzt.
  • EF.Driver_Activity_Data wird anhand der Ringpufferzeiger gelesen. Header und Nutzdaten eines Tagesrecords werden zyklisch gelesen: Ein Record darf in den letzten Bytes des EF beginnen und bei Offset null fortgesetzt werden. Der Durchlauf endet erst nach dem durch activityPointerNewestRecord bezeichneten Record. Falls die Zeiger nicht verwendbar sind, erfolgt ein längengeführter linearer Durchlauf. Tagesrecords werden über Datum und Presence Counter dedupliziert.
  • Aktivitätswechsel werden nach Minute, Slot, Crew und Aktivitätscode dekodiert und zu Intervallen BREAK_REST, AVAILABILITY, WORK oder DRIVING aufgebaut.
  • Ereignis- und Störungsrecords werden anhand der in der Anwendungsidentifikation angegebenen Gruppengrößen geschnitten. Leere Records werden verworfen.
  • Fahrzeugeinsätze enthalten Nutzungsbeginn/-ende, Kilometerstände, Kennzeichen, Land, optional FIN und VU-Blockzähler.
  • Orte und GNSS-Records liefern UTC-Zeit, Länder-/Regionscodes, Kilometerstand, Koordinaten, Genauigkeit und gegebenenfalls Authentifizierungsstatus.
  • Texte werden codepagebewusst dekodiert, von Steuerzeichen bereinigt und mit stabiler Leerzeichennormalisierung gespeichert.
flowchart TD
    A["C-Datei als Bytes"] --> B["5-Byte-Köpfe und Payloadgrenzen prüfen"]
    B --> C["EF-Segmente mit Offset und SHA-256 bilden"]
    C --> D["Anwendungsidentifikation lesen"]
    D --> E["Generation und Recordanzahlen bestimmen"]
    E --> F["Identität und Kartenmetadaten"]
    E --> G["Ringpuffer der Aktivitäten"]
    E --> H["Ereignisse / Störungen"]
    E --> I["Fahrzeuge / Orte / GNSS"]
    F --> J["Normalisierte C-Beobachtungen"]
    G --> J
    H --> J
    I --> J
    J --> K["Signierte EF-Blöcke prüfen"]
    K --> L["Archivbezogene Tabellen ersetzen"]

4. Parsing von M-Dateien

4.1 TREP-Klassifizierung

Jede Sektion beginnt mit 0x76 und einem TREP-Code. Unterstützt werden:

Generation TREP-Codes Bereiche
G1 0105 Übersicht, Aktivitäten, Ereignisse/Störungen, Geschwindigkeit, Technik
G2 v1 2125 dieselben fünf logischen Bereiche als RecordArrays
G2 v2 3135 G2-v2-Struktur einschließlich zusätzlicher GNSS-/Grenz-/Ladedaten

Gemischte Generationen, ungültige Längen, Daten hinter einer Signatur oder unvollständige Pflichtstrukturen führen zum Parsefehler. Eine fehlende detaillierte Geschwindigkeitssektion ist als optionaler Downloadumfang eine Warnung, kein Signaturfehler.

4.2 G1

G1-Sektionen haben feste beziehungsweise zählergesteuerte Strukturen und am Ende eine 128-Byte-Signatur. In der Übersicht werden zwei 194-Byte-Zertifikate gelesen. Diese Zertifikate gehören gemäß Annex IB nicht zum VU-signierten Bereich; die signierten Daten beginnen deshalb erst nach den 388 Zertifikatsbytes.

Wichtige G1-Regeln:

  • FIN: exakt 17 Zeichen aus der Übersicht.
  • Kennzeichen: Staat und Codepage werden übersprungen; gespeichert werden die 13 Zeichen der Registration Number.
  • Datenzeitraum: VuDownloadablePeriod, nicht CurrentDateTime.
  • Kartensitzungen: Namen, Kartennummer, Generation, Ablauf, Einstecken, Entnehmen und Slot.
  • Aktivitätswechsel: 2-Byte-Wort mit Minute, Slot, Crew, Kartenzustand und Aktivität.
  • Ereignisse, Störungen und Überschreitungen: zählergesteuerte Records mit UTC-Zeiten.
  • Geschwindigkeit: Startzeit plus exakt 60 Geschwindigkeitsbytes.
  • Technik und Kalibrierung: VU-Identität, Werkstatt, FIN, Kennzeichen und Kalibrierzeiten.

4.3 G2 v1 und G2 v2

G2-Sektionen bestehen nach dem TREP-Kopf aus RecordArrays. Jedes Array enthält Recordtyp, Recordgröße und Recordanzahl. Der Parser prüft record_size × record_count vor dem Lesen. Zertifikate (0x04, 0x0F) und Signatur (0x08) werden separat gehalten; pro Sektion ist genau ein Signaturrecord zulässig.

Jeder Fachrecord erhält section_index, section_code, record_index und einen SHA-256 des exakten Recordinhalts. Dekodiert werden unter anderem Identität, Zeitraum, Tages-/Kilometerdaten, Aktivitäten, Kartensitzungen, Ereignisse, Störungen, Überschreitungen, Geschwindigkeit, technische Daten, Kalibrierungen, Positionen, Grenzübertritte und Be-/Entladungen. Unbekannte begrenzte Recordtypen werden als Rohbezug und Warnung erhalten.

4.4 Geschwindigkeitsblöcke

G1, G2 v1 und G2 v2 werden identisch gespeichert:

  • start_epoch: Unix-Zeit der ersten Sekunde;
  • speeds_blob: exakt 60 Bytes, ein Byte pro Sekunde;
  • 0xFF: fehlender Messwert, bleibt im Block erhalten;
  • raw_sha256: einmalig als 32-Byte-BLOB über den Rohblock;
  • Primärschlüssel: Archiv, Startzeit, Sektion und Recordindex.

Die sekundengenaue Ansicht expandiert nur passende Blöcke. 0xFF wird nicht ausgegeben; Datumsfilter, Offset und Limit werden auf die expandierten gültigen Sekundenwerte angewendet.

flowchart TD
    A["M-Datei als Bytes"] --> B{"TREP-Generation"}
    B -->|"G1 01-05"| C["Feste/zählergesteuerte Sektionen"]
    B -->|"G2 v1 21-25"| D["RecordArrays"]
    B -->|"G2 v2 31-35"| D
    C --> E["Signierten Bereich und 128-Byte-Signatur trennen"]
    D --> F["Recordgröße und Anzahl validieren"]
    F --> G["Zertifikate, Fachrecords und Signatur trennen"]
    E --> H["Fachrecords normalisieren"]
    G --> H
    H --> I["SHA-256 je Rohrecord"]
    H --> J["60-Sekunden-Geschwindigkeitsblöcke"]
    I --> K["Sektionen einzeln verifizieren"]
    J --> K
    K --> L["M-Tabellen archivbezogen ersetzen"]

5. Signaturen und Trust Store

Der Trust Store liegt in tachograph_trust_anchor. Er speichert Generation, Schlüssel-ID, SHA-256-Fingerprint, Originalmaterial, Gültigkeitszeitraum, Widerruf, Quelle und Aktivstatus. Offizielle Trustanker werden ausschließlich beim zentralen Datenbank-Bootstrap beziehungsweise bei einer administrativen Aktualisierung eingespielt.

C-Dateien

  • G1 verwendet Karten- und CA-Zertifikate sowie RSA PKCS#1 mit SHA-1 über die exakten Datenblockbytes.
  • G2/G2V2 verwendet CardSign-/MSCA- und gegebenenfalls Linkzertifikate sowie ECDSA über die exakten signierten EFs.
  • Das Ergebnis jedes Blocks wird in ddd_drivercard_signature gespeichert.

M-Dateien

  • Jede Sektion wird getrennt über die ERCA/MSCA/VU-Kette geprüft.
  • G1 verwendet RSA PKCS#1/SHA-1; G2 verwendet die im Zertifikat beschriebene ECC-/Hashkombination.
  • Das Ergebnis jeder Sektion wird in ddd_vu_signature gespeichert.

Als Zertifikatsreferenzzeit wird der jüngste plausible signierte Fachzeitpunkt verwendet. Fehlt dieser, wird mit Warnung auf den Archivzeitpunkt zurückgefallen. Gesamtpriorität: INVALID, dann UNVERIFIABLE, dann MISSING, dann VALID. Ein fehlender Trustanker oder eine technisch nicht mögliche Prüfung wird niemals als VALID behandelt.

Parserstatus Bedeutung
PARSED_VERIFIED Struktur und alle erforderlichen Signaturen gültig
PARSED_VERIFIED_WITH_WARNINGS gültig, aber nichtfatale Parser-/Zeitwarnung
QUARANTINED_INVALID_SIGNATURE kryptografisch ungültig
QUARANTINED_UNVERIFIABLE Prüfung nicht vollständig möglich
PARSE_ERROR Struktur oder Verarbeitung fehlgeschlagen

6. Physisches Datenmodell

6.1 Archiv- und Nachweistabellen

Tabelle Zweck
ddd_archive unveränderliche Archivmetadaten, Hashes und Gesamtstatus
ddd_audit_log, fleet_audit_log Import-, Prüf-, Verknüpfungs- und Administrationsaudit
tachograph_trust_anchor aktive und historische Trustanker
app_schema_migration komponentenbezogene Schemaversionen

6.2 C-Tabellen

Tabelle Wesentliche Inhalte
ddd_drivercard_import Parser, Kartenidentität, Generation, Gültigkeit und Signaturgesamtstatus
ddd_drivercard_activity Intervalle, Aktivität, Slot, Crew, Karte
ddd_drivercard_event Ereignisart, UTC-Intervall, Fahrzeugbezug
ddd_drivercard_fault Störungsart, UTC-Intervall, Fahrzeugbezug
ddd_drivercard_vehicle_used Nutzung, Kilometer, Kennzeichen, FIN
ddd_drivercard_position Ort/GNSS, Zeit, Kilometer, Koordinate und Authentifizierung
ddd_drivercard_signature Blockergebnisse, Algorithmen und Zertifikatsfingerprints

Die historisch vorhandenen Spalten raw_json und source_json bleiben aus Kompatibilitätsgründen im Schema, neue Imports schreiben dort jedoch NULL. Die Werte sind aus Original-DDD und regulären Fachspalten reproduzierbar und werden nicht als Cache dupliziert.

6.3 M-Tabellen

Tabelle Wesentliche Inhalte
ddd_vehicle_unit_import Generation, Gesamtstatus, FIN, Kennzeichen, Seriennummer, Zeitraum und Warnungen
ddd_vu_section Sektionscode, Generation, Byteoffsets und SHA-256 des signierten Bereichs
ddd_vu_signature Signaturstatus je Sektion
ddd_vu_raw_record Rohrecordposition, Größe, SHA-256 und optional Payload
ddd_vu_activity, ddd_vu_card_session Aktivitätswechsel und Kartensitzungen
ddd_vu_event, ddd_vu_fault, ddd_vu_overspeed Ereignisse, Störungen und Überschreitungen
ddd_vu_speed_block kompakte 60-Sekunden-Blöcke als WITHOUT ROWID
ddd_vu_technical technische Werte; details_json enthält die vollständige strukturierte VU-Identifikation für die Feldbaumdarstellung
ddd_vu_calibration Kalibrierzeit, Zweck, Werkstatt, FIN, Kennzeichen und nächste Kalibrierung
ddd_vu_position, ddd_vu_border_crossing, ddd_vu_load_operation GNSS-, Grenz- und Ladevorgänge
ddd_vu_odometer_midnight Tageskilometerstand am folgenden UTC-Mitternachtspunkt
fleet_vehicle_vu_link Zuordnung aller passenden M-Archive zu einem Fahrzeugstammsatz
erDiagram
    DDD_ARCHIVE ||--o| DDD_DRIVERCARD_IMPORT : "klassifiziert als C"
    DDD_ARCHIVE ||--o| DDD_VEHICLE_UNIT_IMPORT : "klassifiziert als M"
    DDD_DRIVERCARD_IMPORT ||--o{ DDD_DRIVERCARD_ACTIVITY : enthält
    DDD_DRIVERCARD_IMPORT ||--o{ DDD_DRIVERCARD_SIGNATURE : belegt
    DDD_VEHICLE_UNIT_IMPORT ||--o{ DDD_VU_SECTION : enthält
    DDD_VEHICLE_UNIT_IMPORT ||--o{ DDD_VU_ACTIVITY : enthält
    DDD_VEHICLE_UNIT_IMPORT ||--o{ DDD_VU_SPEED_BLOCK : enthält
    DDD_VEHICLE_UNIT_IMPORT ||--o{ DDD_VU_CALIBRATION : enthält
    DDD_ARCHIVE ||--o{ DDD_FACT_PROVENANCE : liefert
    DDD_CANONICAL_FACT ||--o{ DDD_FACT_PROVENANCE : wird_belegt_durch
    DDD_FACT_KIND ||--o{ DDD_CANONICAL_FACT : typisiert
    FLEET_VEHICLE ||--o{ FLEET_VEHICLE_VU_LINK : besitzt
    DDD_ARCHIVE ||--o| FLEET_VEHICLE_VU_LINK : zugeordnet

7. Indizes und SQLite-Konfiguration

Zentrale Indizes sind:

Index Abfragepfad
idx_ddd_archive_name, idx_ddd_archive_archived_at Archivsuche und Chronologie
idx_ddd_archive_type DDD-Art und Archivdatum
idx_ddd_archive_parse_status Parser-/Signaturfilter
idx_ddd_drivercard_import_normalized Kartennummer + Generation + Archiv
idx_ddd_drivercard_activity_archive_time C-Aktivitäten eines Archivs im Zeitraum
idx_ddd_drivercard_*_card globale Kartenabfragen nach Fachzeit
ix_ddd_vu_activity_normalized_time C-/M-Abgleich nach normalisierter Kartennummer
ix_ddd_vu_activity_archive_time M-Aktivitäten eines Archivs
ix_ddd_vu_session_archive_time Kartensitzungen eines Archivs
ix_fleet_vehicle_vu_vehicle alle M-Archive eines Fahrzeugs
ix_ddd_fact_time, ix_ddd_fact_cluster materialisierte Fakten nach Typ/Zeit beziehungsweise Konfliktcluster
ix_ddd_provenance_fact Quellen eines materialisierten Fakts

ddd_vu_speed_block benötigt keinen zusätzlichen Zeitindex: Der zusammengesetzte Primärschlüssel beginnt mit archive_id,start_epoch und ist als WITHOUT ROWID bereits die physische Suchstruktur.

Alle Runtime-Verbindungen verwenden foreign_keys=ON, busy_timeout=30000, synchronous=NORMAL und einen begrenzten WAL-Autocheckpoint von 1.000 Seiten. Beim exklusiven Start wird journal_mode=WAL gesetzt. Schemaänderungen laufen vor dem Hauptfenster genau einmal über app_schema_migration; Runtime-Repositories werden mit ensure_schema=False geöffnet.

8. Archivübergreifende Deduplizierung

8.1 Zwei Zugriffspfade

Die archivbezogenen C-/M-Tabellen bleiben der genaue Herkunftsnachweis. Für Gesamtsichten existieren zwei kompatible Deduplizierungspfade:

  1. Materialisierte Fakten: ddd_canonical_fact plus ddd_fact_provenance speichert einen normalisierten Fakt einmal und verbindet ihn mit beliebig vielen Archiven.
  2. Kompakter SQL-Fallback: Ist für eine Datenart keine materialisierte Quellenzuordnung vorhanden, dedupliziert das Repository direkt über die typisierten Archivtabellen mittels ROW_NUMBER(), COUNT() und GROUP_CONCAT().

Die kompakte Produktivmigration verwendet den zweiten Pfad, weil eine zusätzliche vollständige Payloadmaterialisierung die Datenbank vergrößern würde. Die Schnittstellen und Ergebnisse sind gleich: source_count, source_archive_ids, preferred_source_archive_id und has_conflict.

8.2 Normalisierung und Hashes

Für materialisierte Fakten gilt:

  • Text: Unicode NFC, trimmen und mehrfache Zwischenräume reduzieren.
  • FIN, Kennzeichen und Kartennummer: Großschreibung; Leerzeichen, Bindestriche, Doppelpunkte und sonstige Nicht-Alphanumerik entfernen.
  • reine Platzhalter aus ?, 0, F, Trennzeichen oder leeren Werten: fehlende Information.
  • Zeit: UTC auf Sekundengenauigkeit.
  • Zahlen, Koordinaten und Binärdaten: exakt, ohne Toleranz.
  • fact_hash: binärer SHA-256 aus Datenart und vollständig normalisiertem Fachinhalt; bei Geschwindigkeit einschließlich speeds_blob.
  • cluster_hash: binärer SHA-256 aus den identitätsbildenden Clusterfeldern, etwa Kalibrierzeit und Zweck.
  • source_sha256: binärer 32-Byte-Hash des Quellrecords.

Bei Kartenbeobachtungen ist ausschließlich die vollständig normalisierte Kartennummer die archiv- und quellenübergreifende Identität. card_generation bleibt als Herkunftsmerkmal im Rohdatensatz und in der Quellenzuordnung erhalten, ist aber weder Bestandteil des Faktenhashes noch ein Filter für den C-/M-Abgleich. Damit werden G1-Karte/G1-Fahrzeug, G1-Karte/G2-Fahrzeug, G2-Karte/G1-Fahrzeug und G2-Karte/G2-Fahrzeug gleich behandelt.

ddd_canonical_fact besitzt UNIQUE(kind_id,fact_hash). ddd_fact_provenance ist WITHOUT ROWID und verwendet (archive_ref,fact_id,source_ordinal) als Primärschlüssel. Varianten mit gleichem cluster_hash, aber verschiedenem fact_hash, bleiben getrennt und werden bei materialisiertem Zugriff als Konflikt markiert.

8.3 Deduplizierung im SQL-Fallback

Der Fallback partitioniert nach allen fachlichen Feldern der jeweiligen Datenart. Innerhalb einer Partition wird genau eine Navigationsquelle gewählt:

  1. PARSED_VERIFIED und VALID;
  2. höhere Generation: G2 v2, G2 v1, G1;
  3. jüngstes Archivdatum.

Diese Reihenfolge entscheidet nur, welches Archiv beim Öffnen angeboten wird. Sie verändert keine fachlichen Werte. COUNT(*) OVER (...) liefert die Quellenanzahl, GROUP_CONCAT(...) alle Archiv-IDs. Die C-Views ddd_drivercard_*_dedup verwenden dasselbe Prinzip für globale Kartenabfragen.

Exakte Geschwindigkeitsduplikate werden nach start_epoch und dem vollständigen 60-Byte-Blob gruppiert. Erst danach werden die Blöcke zu Sekundenwerten expandiert.

Technische Fahrzeugdaten werden anhand von record_type, Wert und der vollständig normalisierten Detailstruktur dedupliziert. Ältere Bestandszeilen ohne details_json werden im Hintergrund aus dem unveränderten, komprimierten M-Original rekonstruiert. Die Fahrzeugakte zeigt jede identische Variante einmal und nennt die Anzahl ihrer Quellarchive; Archiv-ID und Dateiname bleiben ausschließlich als Navigationsmetadaten erhalten. Neue Imports schreiben details_json direkt und beziehen es in den technischen Faktenhash ein.

flowchart TD
    A["Fahrzeug oder Person"] --> B["Zugeordnete aktive Archive ermitteln"]
    B --> C{"Materialisierte Quellenzuordnung vorhanden?"}
    C -->|"Ja"| D["fact_hash auswählen und Quellen gruppieren"]
    C -->|"Nein"| E["Archivtabellen per Window Function partitionieren"]
    D --> F["Konfliktvarianten über cluster_hash bestimmen"]
    E --> G["Exakte Dubletten und Quellenanzahl bestimmen"]
    F --> H["Datumsfilter auf kanonische Fakten"]
    G --> H
    H --> I["Sortierung"]
    I --> J["Limit und Offset"]
    J --> K["DTOs an UI/Reporting"]

9. Datenzugriff

9.1 Repository-Schnittstellen

Schnittstelle Semantik
list_vehicle_facts(kind, vehicle_id, ...) deduplizierte Gesamtsicht aller verknüpften M-Archive
count_vehicle_facts(...) Anzahl nach Deduplizierung und Datumsfilter
list_vehicle_speed_seconds(...) deduplizierte Blöcke, danach sekundengenaue Expansion
list_person_facts(kind, person_id, ...) deduplizierte C-Gesamtsicht über verknüpfte Kartennummern
list_archive_observations(kind, archive_id, ...) genaue Beobachtungen eines einzelnen Archivs
list_rows(table, archive_id, ...) kompatibler M-Einzelarchivzugriff
replace_archive_facts(...) materialisierte Fakten und Quellenzuordnungen eines Archivs ersetzen

Fahrzeugarchive werden ausschließlich über fleet_vehicle_vu_link ermittelt. Personenarchive sowie passende M-Beobachtungen werden über fleet_drivercard_link und die normalisierte Kartennummer bestimmt. Die Kartengeneration dient nur der Anzeige und dem Herkunftsnachweis. Logisch gelöschte Archive sind aus Gesamtsichten ausgeschlossen.

9.2 Filter und Pagination

Gesamtsichten führen die Operationen in dieser Reihenfolge aus:

  1. Entität und aktive Quellarchive bestimmen.
  2. Fachlich deduplizieren.
  3. Datumsbereich auf dem Faktzeitfeld anwenden.
  4. Chronologisch sortieren.
  5. LIMIT und OFFSET anwenden.

Dadurch kann ein Duplikat nicht auf zwei Seiten erscheinen und die Gesamtzahl entspricht der sichtbaren Ergebnismenge. Die Einzelarchivansicht filtert dagegen direkt mit archive_id und bewahrt Recordreihenfolge beziehungsweise Sektionsbezug.

9.3 Zugriff aus der UI

Repository-Abfragen erfolgen in UiJobController-Workern. Der Worker öffnet seine eigene SQLite-Verbindung und liefert nur Listen, Dictionaries oder DTOs zurück. Qt-Widgets und Modelle werden ausschließlich im UI-Callback verändert. Detailtabs werden erst bei ihrer ersten Aktivierung erzeugt; Geschwindigkeit, Aktivitäten und andere große Tabellen laden seitenweise.

10. Idempotenz, Migration und Wiederherstellung

  • Ein M-Reimport löscht und ersetzt Import-, Abschnitts-, Signatur-, Rohrecord- und Fachtabellen des Archivs innerhalb der Repository-Transaktion.
  • C-Fachbereiche werden archivbezogen gelöscht und neu geschrieben; Signatur- und Archivstatus werden anschließend aktualisiert.
  • Nicht mehr referenzierte materialisierte Fakten werden entfernt.
  • Ein Parserfehler überschreibt einen zuvor verifizierten Archivstatus nicht mit unvollständigen Detaildaten.
  • raw_json-/source_json-Caches werden nicht neu aufgebaut.
  • Wartungsmigrationen arbeiten auf einer SQLite-Kopie, prüfen Originalhashes, Zählungen und PRAGMA quick_check, erzeugen mit VACUUM INTO eine kompakte Kandidatin und tauschen sie nur nach erfolgreicher Abnahme aus.
  • Ist die Anwendung geöffnet, wird die Kandidatin über einen Hash- und Integritäts-geprüften Restore-Marker für den nächsten Start vorgemerkt. Vor dem Austausch entsteht eine zusätzliche Pre-Restore-Sicherung.

11. Diagnose für Administratoren

Empfohlene Nur-Lese-Prüfungen:

PRAGMA quick_check;
PRAGMA journal_mode;

SELECT component, version, applied_at, duration_ms
FROM app_schema_migration
ORDER BY component;

SELECT ddd_type, generation, parse_status, signature_status, COUNT(*)
FROM ddd_archive
WHERE is_deleted = 0
GROUP BY ddd_type, generation, parse_status, signature_status;

SELECT COUNT(*) AS speed_blocks,
       SUM(60) AS stored_seconds
FROM ddd_vu_speed_block;

Direkte Schreibzugriffe auf die Datenbank sind im Betrieb nicht zulässig. Reparse, Migration, Restore und Linkkorrekturen müssen über die vorgesehenen Services erfolgen, damit Audit, Hashprüfung, Cacheinvalidierung und Status konsistent bleiben.