OdoKeep est sur l’App Store. Téléchargez-la gratuitement.

Tous les articles

Importation de données

Un importateur CSV en TypeScript qui ne devine pas votre historique de carburant

Reconnaître les conventions CSV sans changer dates, unités ni état du réservoir. Un importateur TypeScript avec vérification avant enregistrement.

Mis à jour le

Dans cet article

03/09/2026 est une date valide dans deux historiques différents.

Si je l'importe comme le 9 mars alors que l'utilisateur pensait au 3 septembre, le parseur ne lève aucune erreur. La base accepte la ligne. La chronologie paraît propre. Tous les rapports calculés à partir de cette date sont désormais faux.

C'est le problème que je voulais résoudre dans l'importateur CSV d'OdoKeep : des erreurs qui produisent des données valides.

OdoKeep est un carnet de véhicules en React Native, Expo et TypeScript. Une personne qui l'essaie peut déjà avoir des années de pleins dans une autre application ou un tableur. Lui demander d'abandonner cet historique crée un coût avant même qu'elle ait décidé d'adopter le produit.

Lire un CSV réduit une partie de ce coût. Préserver le sens des nombres représente le plus gros du travail.

Inspecter, construire, puis enregistrer

L'implémentation réside dans lib/csv-import.ts et produit deux résultats. L'inspection renvoie le tableau décodé, les correspondances proposées, la source détectée, les conventions et les questions ouvertes. La construction produit des objets VehicleRecordDraft ordinaires et une liste des lignes ignorées avec leurs motifs. L'écriture passe séparément par le parcours d'importation du garage.

L'utilisateur peut donc voir une ligne de son fichier avant qu'un enregistrement soit ajouté.

Détecter le séparateur en analysant le fichier

Un tableur portugais peut séparer ses colonnes par des points-virgules et utiliser des virgules dans presque tous les nombres :

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

Compter les virgules et les points-virgules ne suffit pas. Ils peuvent appartenir à des nombres ou à des notes entre guillemets.

L'importateur essaie virgule, point-virgule, tabulation et barre verticale en analysant réellement le texte avec chacun. Il évalue la régularité de la largeur des lignes par rapport à l'en-tête, avec une petite contribution du nombre de colonnes. Un candidat qui ne produit jamais au moins deux colonnes n'apporte pas de preuve utile.

Le parseur parcourt les caractères en gardant l'état des guillemets. Il accepte séparateurs et retours à la ligne dans les champs cités, guillemets doublés, marque d'ordre des octets et fins de ligne attendues. Découper d'abord le texte en lignes détruirait déjà une note multiligne.

Ces choix répondent à des fichiers conservés et modifiés par des personnes. Ils ne prétendent pas reconnaître tous les CSV mal formés. Il faut encore déterminer si le tableau obtenu est exploitable.

Chercher les conventions dans tout le fichier

1.234 peut être un décimal ou un entier avec séparateur de milliers. Un seul jeton est parfois insuffisant, mais d'autres colonnes numériques peuvent départager les possibilités.

Le détecteur examine quantité, prix unitaire, coût total et compteur. La présence des deux séparateurs apporte une preuve forte. Sinon, un séparateur suivi d'un nombre de chiffres différent de trois peut distinguer décimales et groupement des milliers.

Les valeurs ambiguës ne comptent pas comme preuve. Sans convention établie dans les données associées ni valeur par défaut d'un profil reconnu, l'inspection renvoie null et l'interface pose la question.

L'ordre des dates suit le même principe. Une année initiale identifie sa structure. Avec l'année à la fin, un composant supérieur à douze révèle le jour. Si toutes les dates sont dans les douze premiers jours, la colonne peut ne donner aucune réponse.

C'est un résultat légitime. La région de l'utilisateur ne prouve pas les conventions d'un fichier produit ailleurs.

Faire passer les données avant le profil

Les profils Fuelly et Drivvo restent subordonnés aux preuves du fichier :

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.
};

Même priorité pour les unités de quantité : une unité explicite l'emporte sur le profil.

Un fichier ressemblant à Fuelly peut venir d'un compte métrique ou avoir été réenregistré dans un tableur européen. Identifier la source aide à proposer des correspondances, pas à contredire le contenu.

Certaines associations demandent une connaissance du domaine. Fuelly distingue odometer de miles, et fuelup date de date added. Utiliser la distance depuis le dernier plein comme kilométrage total donne des nombres plausibles au mauvais sens. Utiliser la date de saisie comme date d'achat peut déplacer des années d'historique vers l'après-midi où quelqu'un les a entrées.

Son champ price est un prix unitaire ; le constructeur peut donc calculer un total manquant par multiplication. Pour la structure Drivvo, les colonnes de prix unitaire sont examinées avant celles de coût total afin que des en-têtes proches ne capturent pas le même champ.

Les profils ont une limite précise. Ils ont été construits à partir de documentation et d'importateurs existants, pas d'exports réalisés manuellement sur de vrais comptes Fuelly et Drivvo pendant ce travail. Certains en-têtes et choix par défaut restent des hypothèses, notamment l'orthographe exacte des colonnes Drivvo. Les jeux de test vérifient ces structures, pas toutes les versions ou langues exportables par ces fournisseurs.

C'est une raison de laisser les correspondances visibles et modifiables. Une erreur de profil doit pouvoir être corrigée avant de devenir de l'historique.

Inconnu est un état du réservoir

Le calcul de consommation doit distinguer plein complet, remplissage partiel et état inconnu.

Certaines sources expriment le drapeau sous forme de plein partiel. Le profil inverse alors une valeur non vide, mais une cellule vide reste inconnue :

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

Un indicateur de remplissage partiel vide ne prouve pas que le réservoir est plein. Une colonne absente ne prouve pas non plus que tous les remplissages sont partiels. Ces raccourcis modifieraient les calculs sans provoquer d'erreur visible.

Valider le jour et la ligne

JavaScript interprète une chaîne ISO contenant seulement une date comme minuit UTC. À l'ouest d'UTC, cet instant peut s'afficher la veille au soir.

L'importateur valide d'abord explicitement le jour, avec la longueur réelle du mois. Le 30 février ne devient pas une date de mars. Il écrit ensuite minuit local avec le décalage horaire de l'appareil, comme les formulaires manuels. Le 3 septembre choisi doit rester le 3 septembre dans la chronologie et les totaux mensuels.

Le constructeur refuse les nombres négatifs et les valeurs dépassant les plafonds communs de l'application avant l'écriture. Un compteur mal saisi ou une ligne ressemblant à un remboursement devient une ligne ignorée identifiable, plutôt qu'une cause d'échec pour tout un lot valide.

Une ligne datée sans compteur, quantité ni coût utilisable est aussi écartée. Une entrée qui ne décrit rien allongerait l'historique sans l'enrichir.

Le résultat distingue brouillons et lignes ignorées. L'utilisateur peut corriger le fichier ou poursuivre avec les données lues. Après confirmation, les brouillons utilisent le mécanisme d'importation et la file hors ligne existants. Le lecteur CSV ne crée pas un second modèle de persistance.

Examiner sans écrire

Le petit script csv:probe réutilise inspection et construction sans sauvegarder. Il affiche séparateur, conventions, correspondances, exemples et lignes refusées. S'il prend des valeurs provisoires pour un diagnostic, il les signale comme hypothèses. On peut ainsi examiner un jeu d'essai sans lancer l'application ni ouvrir un compte.

Les tests couvrent points-virgules et virgules décimales, les deux profils, conventions ambiguës, notes multilignes citées, dates invalides, valeurs négatives, unités prioritaires sur le profil et états de réservoir inconnus. Le cas mêlant plusieurs formats est utile : ces problèmes arrivent souvent ensemble.

Aucune garantie d'importer n'importe quel export sans intervention ici. L'implémentation propose, valide ce qu'elle peut et réserve une place aux décisions restantes dans l'interface. Pour déplacer un carnet ancien, une question visible sur les unités coûte peu comparée à une réinterprétation silencieuse de chaque entrée.

Si votre historique est déjà dans un CSV, c'est le parcours que je construis dans OdoKeep : examiner le fichier, vérifier colonnes et unités, puis rejoindre le même historique que les nouvelles saisies. Vous pouvez l'essayer dans l'application iOS.