Skip to content

Collisions de uid : marchés distincts partageant le même acheteur_id + id #186

Description

@ColinMaudry

Problème

Le uid d'un marché est construit ainsi (src/tasks/clean.py:48) :

lf = lf.with_columns((pl.col("acheteur_id") + pl.col("id")).alias("uid"))

uid = acheteur_id + id. Or le champ id publié par les acheteurs n'est pas garanti unique : un même acheteur peut réutiliser le même id pour deux marchés différents. Le uid n'est donc pas un identifiant fiable d'un marché.

Exemple concret

Acheteur 21310555400017 (Ville de Toulouse), deux contrats distincts publiés avec id = "2023V" → même uid = 213105554000172023V :

marché A marché B
objet 22V0320AO — extension groupe scolaire Jolimont 23V0141MN — LOT 9 électricité CFO-CFA
codeCPV 45210000 (bâtiment) 45311200 (électricité)
procédure Appel d'offres ouvert Sans publicité ni mise en concurrence
dureeMois 31 52
lieuExecution 31500 31000

Mêmes acheteur_id, id, uid et dateNotification — mais deux contrats réellement différents.

Conséquences

Comme modification_id est un rang dense de dateNotification sur uid (sort_modifications, src/tasks/transform.py:97), les deux contrats se retrouvent avec le même (uid, modification_id), avec plusieurs impacts :

  1. modification_id corrompu : deux contrats indépendants sont fusionnés dans l'historique de modifications d'un seul marché.
  2. Perte de données silencieuse dans la table marches publiée : src/tasks/output.py:216 fait unique(subset=["uid", "modification_id"]) → pour chaque paire en collision, un des deux marchés réels est supprimé de la sortie finale.
  3. Démultiplication (fan-out) dans detect_montant_anomalies : la fonction regroupe par toutes les colonnes non-titulaire (donc les 2 contrats restent séparés) puis rejoint les titulaires sur (uid, modification_id) seul → produit cartésien des titulaires entre les deux contrats. Symptôme visible : les deux logs height df diffèrent (~3,12 M en entrée vs ~3,18 M en sortie).

Ampleur

Sur un run complet (~2,52 M de couples (uid, modification_id)) :

  • 5 806 couples (uid, modification_id) couvrent plus d'un objet distinct
  • 3 374 couples couvrent plus d'un codeCPV distinct

Soit ~0,2 % des marché-versions, mais avec perte de données réelle à la publication.

Pistes de correction

  • Rendre le uid réellement unique (intégrer p. ex. un hash de objet/codeCPV/dateNotification, ou un suffixe de désambiguïsation). ⚠️ modifie les valeurs de uid publiées → changement cassant pour les consommateurs.
  • Vérifier au préalable si le id non-unique est un problème de donnée source ou de parsing (le vrai id du marché semble être 22V0320AO / 23V0141MN, présent dans objet, alors que le id extrait vaut 2023V — possible troncature/mauvais champ).

Note

Bug préexistant, indépendant des optimisations de performance récentes. Mis en évidence par les logs de height restaurés dans detect_montant_anomalies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions