diff --git a/CHANGELOG.md b/CHANGELOG.md index b555258..4c1873c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ #### 2.6.4 2025-12-19 - Tri et numérotation des modifications après la concaténation plutôt que par ressource, pour réduire le nombre de doublons ([#156](https://github.com/ColinMaudry/decp-processing/issues/156)) +- Utilisation du logger de prefect plûtot que `log_prints=True` #### 2.6.3 2025-12-16 diff --git a/src/config.py b/src/config.py index 2866760..feb0d24 100644 --- a/src/config.py +++ b/src/config.py @@ -196,6 +196,13 @@ def make_sirene_data_dir(sirene_data_parent_dir) -> Path: SOLO_DATASET = os.getenv("SOLO_DATASET", "") ALL_CONFIG["SOLO_DATASET"] = SOLO_DATASET +# Acheteurs absents de la base SIRENE (pour raisons de sécurité ou autre) +# Format: SIRET -> {"nom": "...", ...} +# Ces données sont utilisées en fallback si l'acheteur n'est pas trouvé dans SIRENE +ACHETEURS_NON_SIRENE = { + "13001536500013": {"nom": "Ministère des Armées"}, +} + with open( make_path_from_env( "DATASETS_REFERENCE_FILEPATH", REFERENCE_DIR / "source_datasets.json" diff --git a/src/tasks/clean.py b/src/tasks/clean.py index cc5df54..fc1664b 100644 --- a/src/tasks/clean.py +++ b/src/tasks/clean.py @@ -126,6 +126,9 @@ def clean_decp(lf: pl.LazyFrame, decp_format: DecpFormat) -> pl.LazyFrame: .name.keep() ) + # Nettoyage des espaces dans titulaire_id (ex: " 33487372600239") + lf = lf.with_columns(pl.col("titulaire_id").str.strip_chars()) + # Type identifiant = SIRET si vide (marches-securises.fr) lf = lf.with_columns( pl.when( diff --git a/src/tasks/enrich.py b/src/tasks/enrich.py index 10deb06..ead303a 100644 --- a/src/tasks/enrich.py +++ b/src/tasks/enrich.py @@ -1,7 +1,7 @@ import polars as pl import polars.selectors as cs -from src.config import LOG_LEVEL, SIRENE_DATA_DIR +from src.config import LOG_LEVEL, SIRENE_DATA_DIR, ACHETEURS_NON_SIRENE from src.tasks.transform import ( extract_unique_acheteurs_siret, extract_unique_titulaires_siret, @@ -139,6 +139,9 @@ def enrich_from_sirene(lf: pl.LazyFrame): lf = lf.join(lf_sirets_acheteurs, how="left", on="acheteur_id") + # Fallback pour les acheteurs absents de SIRENE (ex: Ministère des Armées) + lf = apply_acheteurs_non_sirene_fallback(lf) + # En joignant en utilisant à la fois le SIRET et le typeIdentifiant, on s'assure qu'on ne joint pas sur # des id de titulaires non-SIRET lf = lf.join( @@ -158,6 +161,32 @@ def enrich_from_sirene(lf: pl.LazyFrame): return lf +def apply_acheteurs_non_sirene_fallback(lf: pl.LazyFrame) -> pl.LazyFrame: + """Applique les données d'acheteurs non présents dans SIRENE en fallback. + + Les acheteurs absents de SIRENE ET du fallback conservent acheteur_nom = NULL. + """ + if not ACHETEURS_NON_SIRENE: + return lf + + # Créer un DataFrame de fallback à partir du dictionnaire + fallback_data = [ + {"acheteur_id": siret, "acheteur_nom_fallback": data["nom"]} + for siret, data in ACHETEURS_NON_SIRENE.items() + ] + lf_fallback = pl.LazyFrame(fallback_data) + + # Joindre avec les données de fallback + lf = lf.join(lf_fallback, on="acheteur_id", how="left") + + # Remplacer acheteur_nom NULL par la valeur de fallback (si disponible) + lf = lf.with_columns( + pl.coalesce("acheteur_nom", "acheteur_nom_fallback").alias("acheteur_nom") + ).drop("acheteur_nom_fallback") + + return lf + + def calculate_distance(lf: pl.LazyFrame) -> pl.LazyFrame: # Utilisation de polars_ds.haversine # https://polars-ds-extension.readthedocs.io/en/latest/num.html#polars_ds.exprs.num.haversine