Importazione dei dati
Un importatore CSV in TypeScript che non indovina la storia dei rifornimenti
Riconoscere convenzioni CSV senza cambiare date, unità o stato del serbatoio. Un importatore TypeScript con revisione prima del salvataggio.
Aggiornato il
In questo articolo
03/09/2026 è una data valida in due storie diverse.
Se la importo come 9 marzo invece del 3 settembre previsto dall'utente, il parser non genera errori. Il database accetta il record. La cronologia sembra ordinata. Ogni report basato su quella data è ora sbagliato.
Questo volevo risolvere nell'importatore CSV di OdoKeep: errori che producono dati validi.
OdoKeep è un diario dei veicoli in React Native, Expo e TypeScript. Chi lo prova può avere anni di rifornimenti in un'altra app o in un foglio di calcolo. Chiedere di abbandonarli impone un costo importante prima ancora di decidere se la nuova app piace.
Leggere un CSV riduce quel costo. Preservare il significato dei numeri è la parte più grande.
Ispezionare, costruire e infine scrivere
L'implementazione in lib/csv-import.ts produce due risultati distinti. L'ispezione restituisce tabella, associazioni proposte, fonte rilevata, convenzioni e domande aperte. La costruzione produce normali oggetti VehicleRecordDraft e un elenco di righe saltate con i motivi. La scrittura è separata e usa il percorso di importazione esistente del garage.
La persona può quindi vedere una riga del proprio file prima che venga aggiunto un record.
Rilevare il separatore analizzando il file
Un foglio portoghese può separare le colonne con punti e virgola e usare virgole in quasi ogni numero:
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
Contare virgole e punti e virgola non identifica in modo affidabile il separatore. Possono appartenere ai numeri o a note tra virgolette.
L'importatore prova virgola, punto e virgola, tabulazione e barra verticale analizzando davvero il testo con ciascuno. Valuta la coerenza tra larghezza delle righe e intestazione, con un piccolo contributo del numero di colonne. Un candidato che non produce almeno due colonne non offre prove utili.
Il parser scorre i caratteri mantenendo lo stato delle virgolette. Supporta separatori e ritorni a capo dentro campi citati, virgolette raddoppiate, byte order mark e i fine riga previsti. Dividere prima il testo in righe distruggerebbe già una nota multilinea.
Sono scelte per file che le persone conservano e modificano, non una promessa di riconoscere ogni CSV malformato. Le fasi successive devono ancora stabilire se la tabella sia utilizzabile.
Cercare le convenzioni nell'intero file
1.234 può essere un decimale o un intero con raggruppamento delle migliaia. Un solo token non sempre basta, ma altre colonne numeriche possono fornire indizi.
Il rilevatore considera quantità, prezzo unitario, costo totale e contachilometri. Un valore con entrambi i separatori è una prova forte. Altrimenti, un separatore seguito da un numero di cifre diverso da tre può distinguere decimali e migliaia.
I valori ambigui non contribuiscono. Se i dati associati non risolvono la convenzione e nessun profilo riconosciuto offre un valore predefinito, l'ispezione restituisce null e la schermata chiede una scelta.
Le date seguono lo stesso criterio. L'anno iniziale identifica il formato. Con l'anno finale, un componente maggiore di dodici può indicare il giorno. Se tutte le date cadono nei primi dodici giorni, la colonna potrebbe non dare una risposta.
È un risultato legittimo. Le impostazioni regionali dell'utente non provano quali convenzioni abbia usato un file prodotto altrove.
Dare precedenza al file rispetto al profilo
I profili Fuelly e Drivvo restano subordinati alle prove del file:
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.
};
La stessa priorità vale per le quantità: un'unità esplicita prevale sul valore del profilo.
Un file con forma Fuelly può provenire da un account metrico o essere stato risalvato in un foglio europeo. Riconoscere la fonte aiuta a proporre associazioni, non autorizza a contraddire il contenuto.
Alcune associazioni richiedono conoscenza del dominio. Fuelly distingue odometer da miles e fuelup date da date added. Usare la distanza dall'ultimo rifornimento come contachilometri produce numeri plausibili dal significato sbagliato. Usare la data di inserimento come acquisto può spostare anni di storia nel pomeriggio in cui qualcuno li ha digitati.
Il campo price è un prezzo unitario: si può ricavare un totale mancante moltiplicando per la quantità. Per i dati Drivvo, le colonne del prezzo unitario vengono valutate prima di quelle del totale, evitando che intestazioni simili catturino lo stesso campo.
Questi profili hanno un limite preciso. Sono stati costruiti da documentazione e importatori esistenti, non da esportazioni manuali di account reali Fuelly e Drivvo durante questo lavoro. Alcune intestazioni e impostazioni rimangono ipotesi, soprattutto i nomi esatti delle colonne Drivvo. I test verificano quelle strutture, non tutte le versioni o lingue esportabili dai fornitori.
Per questo le associazioni restano visibili e modificabili. Un errore del profilo deve essere correggibile prima di diventare storia.
Sconosciuto è uno stato del serbatoio
Il calcolo dei consumi distingue pieno completo, rifornimento parziale e stato sconosciuto.
Alcune fonti esprimono il flag come rifornimento parziale. Il profilo inverte un valore non vuoto, ma una cella vuota rimane sconosciuta:
isFullTank: rawFull
? options.fullTankInverted
? !isAffirmative(rawFull)
: isAffirmative(rawFull)
: undefined
Un flag parziale vuoto non prova che il serbatoio sia pieno. Una colonna assente non prova che tutti i rifornimenti siano parziali. Entrambe le scorciatoie cambierebbero i consumi senza un errore visibile del parser.
Validare giorno e riga
JavaScript interpreta una stringa ISO con la sola data come mezzanotte UTC. In un fuso a ovest di UTC, quell'istante può essere mostrato la sera precedente.
L'importatore valida prima il giorno, inclusa la durata reale del mese. Il 30 febbraio non viene spostato a marzo. Poi scrive mezzanotte locale con l'offset del dispositivo, come i moduli manuali. Il 3 settembre scelto deve rimanere 3 settembre nella cronologia e nei totali mensili.
Prima della scrittura, il costruttore rifiuta numeri negativi e valori oltre i limiti condivisi dell'app. Un contachilometri digitato male o una riga simile a un rimborso diventa una riga saltata identificabile, invece di far fallire un blocco di record validi.
Anche una riga datata senza contachilometri, quantità o costo utilizzabili viene saltata. Una voce che non descrive nulla allungherebbe la storia senza renderla più utile.
Il risultato separa bozze e righe escluse. L'utente può correggere il file o proseguire con ciò che è stato letto. Dopo conferma, le bozze usano lo scrittore di importazione e la coda offline esistenti. Il lettore CSV non crea un secondo modello di persistenza.
Esaminare senza scrivere
Lo script csv:probe riusa ispezione e costruzione senza salvare. Mostra separatore, convenzioni, associazioni, esempi e righe rifiutate. Se usa valori provvisori per la diagnosi, li indica come ipotesi. Posso così esaminare un esempio senza avviare l'app o aprire un account.
I test coprono punto e virgola con virgola decimale, entrambi i profili, convenzioni ambigue, note multilinea tra virgolette, date non valide, negativi, unità esplicite prioritarie sui profili e stati del serbatoio sconosciuti. Il caso con formati misti è utile perché questi problemi spesso arrivano insieme.
Non c'è una garanzia di importare qualsiasi esportazione senza intervento. L'implementazione propone, valida quanto può e lascia alle decisioni restanti uno spazio nell'interfaccia. Per spostare un diario di anni, una domanda visibile sulle unità costa poco rispetto alla reinterpretazione silenziosa di ogni voce.
Se hai già uno storico CSV, questo è il percorso che sto costruendo in OdoKeep: esaminare il file, controllare colonne e unità e portare i record utilizzabili nello stesso storico delle nuove voci. Puoi provarlo nell'app iOS.