diff --git a/dags/cluster/tasks/business_logic/cluster_acteurs_parents_choose_data.py b/dags/cluster/tasks/business_logic/cluster_acteurs_parents_choose_data.py index af7516330..9169ffc96 100644 --- a/dags/cluster/tasks/business_logic/cluster_acteurs_parents_choose_data.py +++ b/dags/cluster/tasks/business_logic/cluster_acteurs_parents_choose_data.py @@ -168,7 +168,8 @@ def set_revision_source( # once the values are chosen, we need to reconstruct the perimetre_adomicile # and lieu_prestation if service_a_domicile := result.get("service_a_domicile"): - result["perimetre_adomiciles"] = service_a_domicile["perimetre_adomicile"] + if perimetre_adomiciles := service_a_domicile["perimetre_adomicile"]: + result["perimetre_adomiciles"] = perimetre_adomiciles result["lieu_prestation"] = service_a_domicile["lieu_prestation"] if "service_a_domicile" in result: del result["service_a_domicile"] diff --git a/dags/sources/tasks/transform/transform_df.py b/dags/sources/tasks/transform/transform_df.py index a80e68dbd..e714289c1 100644 --- a/dags/sources/tasks/transform/transform_df.py +++ b/dags/sources/tasks/transform/transform_df.py @@ -28,10 +28,6 @@ ACTEUR_TYPE_ESS = "ess" LABEL_ESS = "ess" -# FIXME : Utiliser Django pour toutes ces constantes ? -A_DOMICILE = "A_DOMICILE" -SUR_PLACE = "SUR_PLACE" -SUR_PLACE_OU_A_DOMICILE = "SUR_PLACE_OU_A_DOMICILE" LABEL_TO_IGNORE = ["non applicable", "na", "n/a", "null", "aucun", "non"] MANDATORY_COLUMNS_AFTER_NORMALISATION = [ @@ -47,6 +43,14 @@ ] REGEX_BAN_SEPARATORS = r"\s,;-" STRIP_BAN = REGEX_BAN_SEPARATORS.replace("\\s", " ") +REGEXPS_SERVICE_A_DOMICILE_UNIQUEMENT = [ + r"SERVICE\s*A\s*DOMICILE\s*UNIQUEMENT", + r"OUI\s*EXCLUSIVEMENT", +] +REGEXPS_SERVICE_SUR_PLACE_OU_A_DOMICILE = [ + r"OUI", + r"SERVICE\s*A\s*DOMICILE\s*ET\s*EN\s*BOUTIQUE", +] def merge_duplicates( @@ -323,10 +327,8 @@ def get_point_from_location(longitude, latitude): def clean_proposition_services(row, _): - # formater les propositions de service selon les colonnes - # action_codes and sous_categorie_codes - # - # [{'action': 'CODE_ACTION','sous_categories': ['CODE_SSCAT']}] ou [] + # Format proposition de service following action_codes and sous_categorie_codes + # ex: [{'action': 'CODE_ACTION','sous_categories': ['CODE_SSCAT']}] ou [] if row["sous_categorie_codes"]: row["proposition_service_codes"] = [ { @@ -341,24 +343,27 @@ def clean_proposition_services(row, _): return row[["proposition_service_codes"]] -### Fonctions de résolution de l'adresse au format BAN et avec vérification via l'API -# adresse.data.gouv.fr en option -# TODO : A déplacer ? +def _sanitize_string(str_to_sanitize): + return unidecode(str_to_sanitize).upper().strip() def _clean_lieu_prestation(service_a_domicile): from qfdmo.models.acteur import Acteur - service_a_domicile = service_a_domicile.lower().strip() + service_a_domicile = _sanitize_string(service_a_domicile) - if re.match(r"oui\s*exclusivement", service_a_domicile): + if any( + re.match(regexp, service_a_domicile) + for regexp in REGEXPS_SERVICE_A_DOMICILE_UNIQUEMENT + ): return Acteur.LieuPrestation.A_DOMICILE - elif re.match(r"^oui$", service_a_domicile): + elif any( + re.match(regexp, service_a_domicile) + for regexp in REGEXPS_SERVICE_SUR_PLACE_OU_A_DOMICILE + ): return Acteur.LieuPrestation.SUR_PLACE_OU_A_DOMICILE - elif re.match(r"^non$", service_a_domicile): - return Acteur.LieuPrestation.SUR_PLACE else: - return Acteur.LieuPrestation.UNKNOWN + return Acteur.LieuPrestation.SUR_PLACE def _clean_departement_code(departement_code): @@ -373,7 +378,7 @@ def _clean_perimetre_adomicile_codes(perimetre_dinterventions): perimetre_prestation = [] perimetre_dinterventions = perimetre_dinterventions.split("|") for perimetre in perimetre_dinterventions: - perimetre = unidecode(perimetre).upper().strip() + perimetre = _sanitize_string(perimetre) if matches := re.match(r"(\d+)\s*KM", perimetre): perimetre_prestation.append( { @@ -429,17 +434,25 @@ def _clean_perimetre_adomicile_codes(perimetre_dinterventions): def clean_service_a_domicile(row, _): + from qfdmo.models.acteur import Acteur + row["lieu_prestation"] = _clean_lieu_prestation(row["service_a_domicile"]) row["perimetre_adomicile_codes"] = [] - if row["lieu_prestation"] in [A_DOMICILE, SUR_PLACE_OU_A_DOMICILE]: + if row["lieu_prestation"] in [ + Acteur.LieuPrestation.A_DOMICILE, + Acteur.LieuPrestation.SUR_PLACE_OU_A_DOMICILE, + ]: row["perimetre_adomicile_codes"] = _clean_perimetre_adomicile_codes( row["perimetre_dintervention"] ) - return row[["lieu_prestation", "perimetre_adomicile_codes"]] +### Functions to resolve BAN formatted address and check it with APIs +# adresse.data.gouv.fr en option + + def _get_address( adresse_format_ban: str, ) -> tuple[str, str, str]: diff --git a/dags/tests/cluster/tasks/business_logic/test_cluster_acteurs_parents_choose_data.py b/dags/tests/cluster/tasks/business_logic/test_cluster_acteurs_parents_choose_data.py index cbce39cbf..0d25ae108 100644 --- a/dags/tests/cluster/tasks/business_logic/test_cluster_acteurs_parents_choose_data.py +++ b/dags/tests/cluster/tasks/business_logic/test_cluster_acteurs_parents_choose_data.py @@ -239,7 +239,11 @@ def test_cluster_acteurs_parents_choose_data_parent_create( # Retrieve parent data assert df.loc[df["identifiant_unique"] == "p1", "parent_data_new"].values[ 0 - ] == {"nom": "prio 1", "email": "email.acteur@source.3"} + ] == { + "nom": "prio 1", + "email": "email.acteur@source.3", + "lieu_prestation": "SUR_PLACE", + } assert ( df.loc[df["identifiant_unique"] != "p1", "parent_data_new"].isnull().all() ), "tester que tous les autres sont None" @@ -288,6 +292,7 @@ def test_cluster_acteurs_parents_choose_data_parent_create_keep_empty( ] == { "nom": "prio 1", "email": "email.acteur@source.3", + "lieu_prestation": "SUR_PLACE", }, ( "keep_empty is forced to False and the empty email is ignored until" " found `email.acteur@source.3`" diff --git a/dags/tests/sources/tasks/transform/test_transform_df.py b/dags/tests/sources/tasks/transform/test_transform_df.py index ca46f540b..dc792759e 100644 --- a/dags/tests/sources/tasks/transform/test_transform_df.py +++ b/dags/tests/sources/tasks/transform/test_transform_df.py @@ -439,6 +439,12 @@ class TestCleanServiceADomicile: ), [ # Nominal cases : one perimeter + ( + "Service à domicile et en Boutique", + "10 km", + "SUR_PLACE_OU_A_DOMICILE", + [{"type": "KILOMETRIQUE", "valeur": 10}], + ), ( "oui", "10 km", @@ -457,6 +463,12 @@ class TestCleanServiceADomicile: "SUR_PLACE_OU_A_DOMICILE", [{"type": "FRANCE_METROPOLITAINE", "valeur": ""}], ), + ( + "Service à domicile uniquement", + "10 km", + "A_DOMICILE", + [{"type": "KILOMETRIQUE", "valeur": 10}], + ), ( "oui exclusivement", "10 km", @@ -478,6 +490,9 @@ class TestCleanServiceADomicile: {"type": "DROM_TOM", "valeur": ""}, ], ), + # Nominal cases empty + ("", "", "SUR_PLACE", []), + ("", "10 km", "SUR_PLACE", []), # Nominal cases : non ("non", "", "SUR_PLACE", []), # other cases : non diff --git a/qfdmo/migrations/0173_alter_acteur_lieu_prestation_and_more.py b/qfdmo/migrations/0173_alter_acteur_lieu_prestation_and_more.py new file mode 100644 index 000000000..8e4ca98fc --- /dev/null +++ b/qfdmo/migrations/0173_alter_acteur_lieu_prestation_and_more.py @@ -0,0 +1,73 @@ +# Generated by Django 5.2.5 on 2025-09-11 15:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("qfdmo", "0172_vueacteur_enfants_liste_vueacteur_enfants_nombre_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="acteur", + name="lieu_prestation", + field=models.CharField( + blank=True, + choices=[ + ("A_DOMICILE", "À domicile"), + ("SUR_PLACE", "Sur place"), + ("SUR_PLACE_OU_A_DOMICILE", "Sur place ou à domicile"), + ], + default="SUR_PLACE", + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="displayedacteur", + name="lieu_prestation", + field=models.CharField( + blank=True, + choices=[ + ("A_DOMICILE", "À domicile"), + ("SUR_PLACE", "Sur place"), + ("SUR_PLACE_OU_A_DOMICILE", "Sur place ou à domicile"), + ], + default="SUR_PLACE", + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="revisionacteur", + name="lieu_prestation", + field=models.CharField( + blank=True, + choices=[ + ("A_DOMICILE", "À domicile"), + ("SUR_PLACE", "Sur place"), + ("SUR_PLACE_OU_A_DOMICILE", "Sur place ou à domicile"), + ], + default="SUR_PLACE", + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="vueacteur", + name="lieu_prestation", + field=models.CharField( + blank=True, + choices=[ + ("A_DOMICILE", "À domicile"), + ("SUR_PLACE", "Sur place"), + ("SUR_PLACE_OU_A_DOMICILE", "Sur place ou à domicile"), + ], + default="SUR_PLACE", + max_length=255, + null=True, + ), + ), + ] diff --git a/qfdmo/models/acteur.py b/qfdmo/models/acteur.py index f0754d3cb..bdf8ee2a2 100644 --- a/qfdmo/models/acteur.py +++ b/qfdmo/models/acteur.py @@ -367,7 +367,6 @@ class LieuPrestation(models.TextChoices): A_DOMICILE = "A_DOMICILE", "À domicile" SUR_PLACE = "SUR_PLACE", "Sur place" SUR_PLACE_OU_A_DOMICILE = "SUR_PLACE_OU_A_DOMICILE", "Sur place ou à domicile" - UNKNOWN = "", "" class Meta: abstract = True @@ -467,8 +466,9 @@ class Meta: lieu_prestation = models.CharField( max_length=255, choices=LieuPrestation.choices, - default=LieuPrestation.UNKNOWN, + default=LieuPrestation.SUR_PLACE, blank=True, + null=True, ) @property diff --git a/unit_tests/core/test_qfdmo_tags.py b/unit_tests/core/test_qfdmo_tags.py index 360471561..516e8c3fa 100644 --- a/unit_tests/core/test_qfdmo_tags.py +++ b/unit_tests/core/test_qfdmo_tags.py @@ -320,7 +320,5 @@ def test_acteur_pinpoint_tag_carte_config( sous_categorie.id, ) - assert result_context["marker_icon_file"] == ( - "/media/config/groupeaction/icones/top.svg" - ) + assert result_context["marker_icon_file"].endswith("top.svg") assert result_context["marker_icon"] == ""