OdoKeep ist jetzt im App Store. Kostenlos laden.

Alle Artikel

Datenimport

Ein TypeScript-CSV-Importer, der bei deiner Tankhistorie nicht rät

CSV-Konventionen erkennen, ohne Datumswerte, Einheiten oder Tankzustände umzudeuten. Ein TypeScript-Importer mit Prüfung vor dem Speichern.

Aktualisiert am

In diesem Artikel

03/09/2026 ist in zwei unterschiedlichen Historien ein gültiges Datum.

Importiere ich es als 9. März statt als gemeinten 3. September, wirft der Parser keinen Fehler. Die Datenbank akzeptiert den Eintrag. Die Zeitleiste sieht ordentlich aus. Jeder darauf aufbauende Bericht ist nun falsch.

Dieses Problem wollte ich mit dem CSV-Importer in OdoKeep lösen: Fehler, die gültige Daten erzeugen.

OdoKeep ist ein Fahrzeugtagebuch mit React Native, Expo und TypeScript. Wer es ausprobiert, hat möglicherweise jahrelange Tankdaten in einer anderen App oder Tabelle. Diese Historie aufgeben zu müssen ist eine erhebliche Hürde, bevor überhaupt feststeht, ob die neue App gefällt.

CSV lesen senkt einen Teil dieser Hürde. Die Bedeutung der Zahlen zu erhalten ist der größere Teil.

Untersuchen, aufbauen, dann schreiben

Die Implementierung in lib/csv-import.ts liefert zwei getrennte Ergebnisse. Die Untersuchung erzeugt Tabelle, vorgeschlagene Spaltenzuordnung, erkannte Quelle, Konventionen und offene Fragen. Der Aufbau erzeugt gewöhnliche VehicleRecordDraft-Objekte sowie ausgelassene Zeilen mit Begründungen. Das Schreiben erfolgt separat über den bestehenden Garagenimport.

So kann die Person eine eigene Dateizeile prüfen, bevor ein Eintrag angelegt wird.

Trennzeichen durch tatsächliches Parsen erkennen

Eine portugiesische Tabelle kann Semikolons zwischen Spalten und Kommas in fast jedem Zahlenwert enthalten:

Data;Odometro;Litros;Preco/l;Valor total
03/09/2026;142350;31,93;1,899;60,64
04/09/2026;142890;28,5;1,879;53,55

Kommas und Semikolons zu zählen identifiziert den Trenner nicht zuverlässig. Sie können zu Zahlen oder Notizen in Anführungszeichen gehören.

Der Importer probiert Komma, Semikolon, Tabulator und senkrechten Strich, indem er den Text mit jedem Kandidaten parst. Er bewertet, wie gleichmäßig die Zeilen zur Breite der Kopfzeile passen, ergänzt um einen kleinen Beitrag der Spaltenanzahl. Ein Trenner, der nie mindestens zwei Spalten erzeugt, liefert keinen brauchbaren Hinweis.

Der Parser läuft zeichenweise und verfolgt den Zustand von Anführungszeichen. Er unterstützt eingeschlossene Trenner und Zeilenumbrüche, doppelte Anführungszeichen, Byte Order Mark und die erwarteten Zeilenenden. Vorher nach Zeilen aufzuteilen würde mehrzeilige Notizen bereits zerstören.

Das berücksichtigt Dateien, die Menschen speichern und bearbeiten. Es ist kein Anspruch, jede fehlerhafte CSV-Variante zu verstehen. Die folgenden Schritte müssen erst entscheiden, ob die Tabelle verwendbar ist.

Zahlen und Daten aus der ganzen Datei verstehen

1.234 kann eine Dezimalzahl oder eine gruppierte Ganzzahl sein. Ein einzelner Wert löst die Mehrdeutigkeit oft nicht, andere numerische Spalten können aber Hinweise liefern.

Die Erkennung untersucht Menge, Einzelpreis, Gesamtkosten und Kilometerstand. Beide Trennzeichen in einem Wert sind ein starker Hinweis. Andernfalls kann eine andere Anzahl als drei nachfolgende Ziffern Dezimalstellen von Tausendergruppen unterscheiden.

Mehrdeutige Werte zählen nicht als Beweis. Lösen weder zugeordnete Daten noch ein bekanntes Profil die Konvention auf, gibt die Untersuchung null zurück und die Oberfläche fragt nach.

Bei Daten gilt dasselbe Prinzip. Ein führendes Jahr kennzeichnet dieses Format. Steht das Jahr hinten, kann ein Bestandteil über zwölf den Tag bestimmen. Liegen alle Daten in den ersten zwölf Tagen, enthält die Spalte möglicherweise keine Antwort.

Das ist ein legitimes Ergebnis. Die Regionseinstellung des Benutzers beweist nicht die Konventionen einer anderswo erzeugten Datei.

Die Datei hat Vorrang vor dem Profil

Fuelly- und Drivvo-Profile stehen unterhalb der Hinweise aus der Datei:

const named = unitFromHeaders(table.headers, mapping);

return {
  table,
  mapping,
  source,
  decimal: detectDecimalStyle(numeric) ?? source?.decimal ?? null,
  dateOrder: detectDateOrder(column("date")) ?? source?.dateOrder ?? null,
  odometerUnit: named.odometerUnit ?? source?.odometerUnit ?? null,
  // Other inspection fields are omitted here.
};

Dasselbe gilt für Mengeneinheiten: Eine ausdrücklich genannte Einheit hat Vorrang vor dem Profilstandard.

Eine Fuelly-artige Datei kann aus einem metrischen Konto stammen oder in einem europäischen Tabellenprogramm neu gespeichert worden sein. Die Quelle zu erkennen hilft bei Vorschlägen, berechtigt aber nicht, dem Inhalt zu widersprechen.

Einige Zuordnungen benötigen Fachwissen. Fuelly unterscheidet odometer von miles und fuelup date von date added. Die Strecke seit dem letzten Tanken als Kilometerstand zu verwenden erzeugt plausible Zahlen mit falscher Bedeutung. Das Eingabedatum als Kaufdatum kann Jahre der Historie auf den Nachmittag verschieben, an dem jemand sie nachgetragen hat.

price ist dort ein Einzelpreis. Ein fehlender Gesamtbetrag lässt sich daher mit der Menge berechnen. Bei Drivvo-Strukturen werden Einzelpreisspalten vor Gesamtkostenspalten geprüft, damit ähnliche Überschriften nicht das falsche Feld beanspruchen.

Die Profile haben eine klare Grenze. Sie wurden aus Dokumentation und bestehenden Importern entwickelt, nicht aus während dieser Arbeit manuell erzeugten Exporten echter Fuelly- und Drivvo-Konten. Manche Überschriften und Standardwerte bleiben Annahmen, insbesondere genaue Drivvo-Schreibweisen. Die Testdaten prüfen diese Formen, bestätigen aber nicht jede exportierbare Version oder Sprache.

Deshalb bleibt die Zuordnung sichtbar und bearbeitbar. Ein Profilfehler sollte korrigierbar sein, bevor er zur Historie wird.

Unbekannt ist ein eigener Tankzustand

Verbrauchsberechnungen müssen volle, teilweise und unbekannte Tankfüllungen unterscheiden.

Manche Quellen verwenden ein Kennzeichen für Teilbetankung. Das Profil kehrt dann einen nicht leeren Wert um. Eine leere Zelle bleibt jedoch unbekannt:

isFullTank: rawFull
  ? options.fullTankInverted
    ? !isAffirmative(rawFull)
    : isAffirmative(rawFull)
  : undefined

Ein leeres Teilbetankungsfeld beweist keinen vollen Tank. Eine fehlende Volltankspalte beweist auch nicht, dass alle Vorgänge teilweise waren. Beide Abkürzungen würden die Verbrauchsberechnung ohne sichtbaren Parserfehler verändern.

Kalendertag und Zeile prüfen

JavaScript interpretiert eine reine ISO-Datumszeichenfolge als Mitternacht UTC. Westlich von UTC kann dieser Zeitpunkt am Vorabend erscheinen.

Der Importer prüft den Kalendertag ausdrücklich, einschließlich der tatsächlichen Monatslänge. Der 30. Februar rutscht nicht in den März. Anschließend schreibt er lokale Mitternacht mit dem Zeitzonenoffset des Geräts, wie die manuellen Formulare. Der gewählte 3. September soll in Historie und Monatssummen der 3. September bleiben.

Der Zeilenaufbau weist negative Zahlen und Werte über gemeinsamen App-Grenzen vor dem Schreiben zurück. Ein vertippter Kilometerstand oder eine erstattungsartige Zeile wird so nachvollziehbar ausgelassen, statt einen ganzen Block gültiger Einträge scheitern zu lassen.

Auch eine datierte Zeile ohne brauchbaren Kilometerstand, Menge oder Kosten wird ausgelassen. Ein Eintrag ohne Ereignis würde die Historie nur verlängern.

Das Ergebnis trennt Entwürfe von ausgelassenen Zeilen. Die Person kann die Datei korrigieren oder mit den gelesenen Werten fortfahren. Nach Bestätigung verwenden die Entwürfe den bestehenden Import-Writer und die Offline-Warteschlange. Der CSV-Leser führt kein weiteres Speichermodell ein.

Untersuchen, ohne zu schreiben

Das kleine Skript csv:probe verwendet Untersuchung und Aufbau ohne Speicherung. Es zeigt Trenner, Konventionen, Zuordnungen, Beispiele und abgelehnte Zeilen. Vorläufige Diagnosewerte werden als Annahmen markiert. Damit lässt sich ein Testbeispiel prüfen, ohne App oder Konto zu öffnen.

Die Tests umfassen Semikolon mit Dezimalkomma, beide Profile, mehrdeutige Konventionen, mehrzeilige zitierte Notizen, ungültige Daten, negative Werte, explizite Einheiten mit Vorrang vor dem Profil und unbekannte Tankzustände. Gemischte Formate sind besonders nützlich, weil diese Probleme häufig zusammen eintreffen.

Es gibt keine Garantie, einen beliebigen Export unbeaufsichtigt einzulesen. Die Implementierung schlägt vor, prüft das Prüfbare und lässt übrige Entscheidungen in der Oberfläche offen. Beim Umzug eines langjährigen Tagebuchs kostet eine sichtbare Einheitenfrage wenig im Vergleich zur stillen Umdeutung jeder Zeile.

Wenn deine Tankhistorie schon in CSV vorliegt, ist das der Ablauf, den ich in OdoKeep baue: Datei prüfen, Spalten und Einheiten bestätigen und brauchbare Einträge in dieselbe Historie wie neue Eingaben übernehmen. Ausprobieren lässt sich das in der iOS-App.