Skip to content

Commit

Permalink
Merge branch 'main' into keep-querystring-in-assistant-links
Browse files Browse the repository at this point in the history
  • Loading branch information
fabienheureux authored Jan 16, 2025
2 parents e8da144 + b7065f3 commit f2e909c
Show file tree
Hide file tree
Showing 44 changed files with 3,215 additions and 366 deletions.
1 change: 1 addition & 0 deletions airflow-requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ python-decouple
ratelimit
shortuuid
unidecode
scikit-learn==1.3.2
80 changes: 79 additions & 1 deletion airflow-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2335,4 +2335,82 @@ yarl==1.18.3 \
zipp==3.21.0 \
--hash=sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4 \
--hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931
# via importlib-metadata
# via importlib-metadata
joblib==1.4.2 \
--hash=sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6 \
--hash=sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e
# via scikit-learn
scikit-learn==1.3.2 \
--hash=sha256:0402638c9a7c219ee52c94cbebc8fcb5eb9fe9c773717965c1f4185588ad3107 \
--hash=sha256:0ee107923a623b9f517754ea2f69ea3b62fc898a3641766cb7deb2f2ce450161 \
--hash=sha256:1215e5e58e9880b554b01187b8c9390bf4dc4692eedeaf542d3273f4785e342c \
--hash=sha256:15e1e94cc23d04d39da797ee34236ce2375ddea158b10bee3c343647d615581d \
--hash=sha256:18424efee518a1cde7b0b53a422cde2f6625197de6af36da0b57ec502f126157 \
--hash=sha256:1d08ada33e955c54355d909b9c06a4789a729977f165b8bae6f225ff0a60ec4a \
--hash=sha256:3271552a5eb16f208a6f7f617b8cc6d1f137b52c8a1ef8edf547db0259b2c9fb \
--hash=sha256:35a22e8015048c628ad099da9df5ab3004cdbf81edc75b396fd0cff8699ac58c \
--hash=sha256:535805c2a01ccb40ca4ab7d081d771aea67e535153e35a1fd99418fcedd1648a \
--hash=sha256:5b2de18d86f630d68fe1f87af690d451388bb186480afc719e5f770590c2ef6c \
--hash=sha256:61a6efd384258789aa89415a410dcdb39a50e19d3d8410bd29be365bcdd512d5 \
--hash=sha256:64381066f8aa63c2710e6b56edc9f0894cc7bf59bd71b8ce5613a4559b6145e0 \
--hash=sha256:67f37d708f042a9b8d59551cf94d30431e01374e00dc2645fa186059c6c5d78b \
--hash=sha256:6c43290337f7a4b969d207e620658372ba3c1ffb611f8bc2b6f031dc5c6d1d03 \
--hash=sha256:6fb6bc98f234fda43163ddbe36df8bcde1d13ee176c6dc9b92bb7d3fc842eb66 \
--hash=sha256:763f0ae4b79b0ff9cca0bf3716bcc9915bdacff3cebea15ec79652d1cc4fa5c9 \
--hash=sha256:785a2213086b7b1abf037aeadbbd6d67159feb3e30263434139c98425e3dcfcf \
--hash=sha256:8db94cd8a2e038b37a80a04df8783e09caac77cbe052146432e67800e430c028 \
--hash=sha256:a19f90f95ba93c1a7f7924906d0576a84da7f3b2282ac3bfb7a08a32801add93 \
--hash=sha256:a2f54c76accc15a34bfb9066e6c7a56c1e7235dda5762b990792330b52ccfb05 \
--hash=sha256:b8692e395a03a60cd927125eef3a8e3424d86dde9b2370d544f0ea35f78a8073 \
--hash=sha256:cb06f8dce3f5ddc5dee1715a9b9f19f20d295bed8e3cd4fa51e1d050347de525 \
--hash=sha256:dc9002fc200bed597d5d34e90c752b74df516d592db162f756cc52836b38fe0e \
--hash=sha256:e326c0eb5cf4d6ba40f93776a20e9a7a69524c4db0757e7ce24ba222471ee8a1 \
--hash=sha256:ed932ea780517b00dae7431e031faae6b49b20eb6950918eb83bd043237950e0 \
--hash=sha256:fc4144a5004a676d5022b798d9e573b05139e77f271253a4703eed295bde0433
# via -r req_sickit.in
scipy==1.15.1 \
--hash=sha256:033a75ddad1463970c96a88063a1df87ccfddd526437136b6ee81ff0312ebdf6 \
--hash=sha256:0458839c9f873062db69a03de9a9765ae2e694352c76a16be44f93ea45c28d2b \
--hash=sha256:070d10654f0cb6abd295bc96c12656f948e623ec5f9a4eab0ddb1466c000716e \
--hash=sha256:09c52320c42d7f5c7748b69e9f0389266fd4f82cf34c38485c14ee976cb8cb04 \
--hash=sha256:0ac102ce99934b162914b1e4a6b94ca7da0f4058b6d6fd65b0cef330c0f3346f \
--hash=sha256:0fb57b30f0017d4afa5fe5f5b150b8f807618819287c21cbe51130de7ccdaed2 \
--hash=sha256:100193bb72fbff37dbd0bf14322314fc7cbe08b7ff3137f11a34d06dc0ee6b85 \
--hash=sha256:14eaa373c89eaf553be73c3affb11ec6c37493b7eaaf31cf9ac5dffae700c2e0 \
--hash=sha256:2114a08daec64980e4b4cbdf5bee90935af66d750146b1d2feb0d3ac30613692 \
--hash=sha256:21e10b1dd56ce92fba3e786007322542361984f8463c6d37f6f25935a5a6ef52 \
--hash=sha256:2722a021a7929d21168830790202a75dbb20b468a8133c74a2c0230c72626b6c \
--hash=sha256:395be70220d1189756068b3173853029a013d8c8dd5fd3d1361d505b2aa58fa7 \
--hash=sha256:3fe1d95944f9cf6ba77aa28b82dd6bb2a5b52f2026beb39ecf05304b8392864b \
--hash=sha256:491d57fe89927fa1aafbe260f4cfa5ffa20ab9f1435025045a5315006a91b8f5 \
--hash=sha256:4b17d4220df99bacb63065c76b0d1126d82bbf00167d1730019d2a30d6ae01ea \
--hash=sha256:4c9d8fc81d6a3b6844235e6fd175ee1d4c060163905a2becce8e74cb0d7554ce \
--hash=sha256:55cc79ce4085c702ac31e49b1e69b27ef41111f22beafb9b49fea67142b696c4 \
--hash=sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff \
--hash=sha256:5bd8d27d44e2c13d0c1124e6a556454f52cd3f704742985f6b09e75e163d20d2 \
--hash=sha256:5dff14e75cdbcf07cdaa1c7707db6017d130f0af9ac41f6ce443a93318d6c6e0 \
--hash=sha256:5eb0ca35d4b08e95da99a9f9c400dc9f6c21c424298a0ba876fdc69c7afacedf \
--hash=sha256:63b9b6cd0333d0eb1a49de6f834e8aeaefe438df8f6372352084535ad095219e \
--hash=sha256:667f950bf8b7c3a23b4199db24cb9bf7512e27e86d0e3813f015b74ec2c6e3df \
--hash=sha256:6b3e71893c6687fc5e29208d518900c24ea372a862854c9888368c0b267387ab \
--hash=sha256:71ba9a76c2390eca6e359be81a3e879614af3a71dfdabb96d1d7ab33da6f2364 \
--hash=sha256:74bb864ff7640dea310a1377d8567dc2cb7599c26a79ca852fc184cc851954ac \
--hash=sha256:82add84e8a9fb12af5c2c1a3a3f1cb51849d27a580cb9e6bd66226195142be6e \
--hash=sha256:837299eec3d19b7e042923448d17d95a86e43941104d33f00da7e31a0f715d3c \
--hash=sha256:900f3fa3db87257510f011c292a5779eb627043dd89731b9c461cd16ef76ab3d \
--hash=sha256:9f151e9fb60fbf8e52426132f473221a49362091ce7a5e72f8aa41f8e0da4f25 \
--hash=sha256:af0b61c1de46d0565b4b39c6417373304c1d4f5220004058bdad3061c9fa8a95 \
--hash=sha256:bc7136626261ac1ed988dca56cfc4ab5180f75e0ee52e58f1e6aa74b5f3eacd5 \
--hash=sha256:be3deeb32844c27599347faa077b359584ba96664c5c79d71a354b80a0ad0ce0 \
--hash=sha256:c09aa9d90f3500ea4c9b393ee96f96b0ccb27f2f350d09a47f533293c78ea776 \
--hash=sha256:c352c1b6d7cac452534517e022f8f7b8d139cd9f27e6fbd9f3cbd0bfd39f5bef \
--hash=sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1 \
--hash=sha256:cdde8414154054763b42b74fe8ce89d7f3d17a7ac5dd77204f0e142cdc9239e9 \
--hash=sha256:ce3a000cd28b4430426db2ca44d96636f701ed12e2b3ca1f2b1dd7abdd84b39a \
--hash=sha256:f735bc41bd1c792c96bc426dece66c8723283695f02df61dcc4d0a707a42fc54 \
--hash=sha256:f82fcf4e5b377f819542fbc8541f7b5fbcf1c0017d0df0bc22c781bf60abc4d8
# via scikit-learn
threadpoolctl==3.5.0 \
--hash=sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107 \
--hash=sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467
# via scikit-learn
273 changes: 273 additions & 0 deletions dags/cluster/dags/cluster_acteurs_suggestions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
"""
DAG de suggestions de clusters pour les acteurs
Les suggestions sont écrites dans la table qfdmo_suggestions
Le traitement des suggestions est géré hors de ce DAG par
l'app django data_management
"""

from datetime import datetime

from airflow import DAG
from airflow.models.baseoperator import chain
from airflow.models.param import Param
from cluster.tasks.airflow_logic import (
cluster_acteurs_config_validate_task,
cluster_acteurs_db_data_read_acteurs_task,
cluster_acteurs_db_data_write_suggestions_task,
cluster_acteurs_normalize_task,
cluster_acteurs_suggestions_task,
)
from utils.airflow_params import airflow_params_dropdown_from_mapping
from utils.django import django_model_fields_attributes_get, django_setup_full

# Setup Django avant import des modèles
django_setup_full()
from qfdmo.models import Acteur, ActeurType, Source # noqa: E402

# -------------------------------------------
# Gestion des dropdowns des paramètres
# -------------------------------------------
# A noter que ce design pattern est a éviter au maximum
# car le code est executé au parsing des fichiers DAG
# selon min_file_process_interval
# https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html#config-scheduler-min-file-process-interval
# https://airflow.apache.org/docs/apache-airflow/stable/best-practices.html#top-level-python-code
# En revanche sans cette approche, il va falloir:
# - soit laisser le métier rentrer des paramètres complexes à la main
# - soit maintenir des mapping statiques ici
# Ces deux options semblent pires que l'inconvénient du top-level code
# sachant que le code executé demeure assez légé

# Récupération données DB
mapping_source_id_by_code = {obj.code: obj.id for obj in Source.objects.all()}
mapping_acteur_type_id_by_code = {obj.code: obj.id for obj in ActeurType.objects.all()}
# Création des dropdowns
dropdown_sources = airflow_params_dropdown_from_mapping(mapping_source_id_by_code)
dropdown_acteur_types = airflow_params_dropdown_from_mapping(
mapping_acteur_type_id_by_code
)

dropdown_acteur_columns = django_model_fields_attributes_get(Acteur)

UI_PARAMS_SEPARATOR_SELECTION = r"""
---
# Paramètres de sélection
Les paramètres suivants décident des acteurs à inclure
ou exclure comme candidats au clustering. Ce n'est pas
parce qu'un acteur est selectionné qu'il sera forcément clusterisé.
(ex: si il se retrouve tout seul sachant qu'on supprime
les clusters de taille 1)
"""

UI_PARAMS_SEPARATOR_NORMALIZATION = r"""
---
# Paramètres de normalisation
Les paramètres suivants définissent comment les valeurs
des champs vont être transformées avant le clustering.
"""

UI_PARAMS_SEPARATOR_CLUSTERING = r"""
---
# Paramètres de clustering
Les paramètres suivants définissent comment les acteurs
vont être regroupés en clusters.
"""


with DAG(
dag_id="cluster_acteurs_suggestions",
dag_display_name="Cluster - Acteurs - Suggestions",
default_args={
"owner": "airflow",
"depends_on_past": False,
# Une date bidon dans le passée pour
# par que Airflow "attende" que la date
# soit atteinte
"start_date": datetime(2025, 1, 1),
# Notre donnée n'étant pas versionnée dans le temps,
# faire du catchup n'a pas de sense
"catchup": False,
"email_on_failure": False,
"email_on_retry": False,
# Les tâches de ce DAG ne devrait pas avoir de problème
# de perf donc 0 retries par défaut
"retries": 0,
},
description=("Un DAG pour générer des suggestions de clustering pour les acteurs"),
tags=["cluster", "acteurs", "suggestions"],
params={
"dry_run": Param(
True,
type="boolean",
description_md=f"""🚱 Si coché, seules les tâches qui ne modifient pas
la base de données seront exécutées.
{UI_PARAMS_SEPARATOR_SELECTION}""",
),
"include_source_codes": Param(
[],
type="array",
# La terminologie Airflow n'est pas très heureuse
# mais "examples" est bien la façon de faire des dropdowns
# voir https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/params.html
examples=dropdown_sources,
description_md="""**➕ INCLUSION ACTEURS**: seuls ceux qui proviennent
de ces sources (opérateur **OU/OR**)""",
),
"include_acteur_type_codes": Param(
[],
type="array",
examples=dropdown_acteur_types,
description_md="""**➕ INCLUSION ACTEURS**: ceux qui sont de ces types
(opérateur **OU/OR**)""",
),
"include_only_if_regex_matches_nom": Param(
"",
type=["null", "string"],
description_md="""**➕ INCLUSION ACTEURS**: ceux dont le champ 'nom'
correspond à cette expression régulière ([voir recettes](https://www.notion.so/accelerateur-transition-ecologique-ademe/Expressions-r-guli-res-regex-1766523d57d780939a37edd60f367b75))
🧹 Note: la normalisation basique est appliquée à la volée sur ce
champ avant l'application de la regex pour simplifier les expressions
0️⃣ Si aucune valeur spécifiée = cette option n'a PAS d'effet
""",
),
"include_if_all_fields_filled": Param(
["code_postal"],
type="array",
examples=dropdown_acteur_columns,
description_md="""**➕ INCLUSION ACTEURS**: ceux dont tous ces champs
sont **remplis** (opérateur **ET/AND**)
exemple: travailler uniquement sur les acteurs avec SIRET
""",
),
"exclude_if_any_field_filled": Param(
[],
type=["null", "array"],
examples=dropdown_acteur_columns,
description_md=f"""**🛑 EXCLUSION ACTEURS**: ceux dont n'importe quel
de ces champs est **rempli** (opérateur **OU/OR**)
exemple: travailler uniquement sur les acteurs SANS SIRET
0️⃣ Si aucune valeur spécifiée = cette option n'a PAS d'effet
{UI_PARAMS_SEPARATOR_NORMALIZATION}
""",
),
"normalize_fields_basic": Param(
[],
type=["null", "array"],
examples=dropdown_acteur_columns,
description_md=r"""Les champs à normaliser de manière basique:
- minuscule
- conversion des accents
- suppression des caractères spéciaux et espaces superflus
exemple: ' Château de l'île' -> 'chateau de l ile'
💯 Si aucun champ spécifié = s'applique à TOUS les champs
💡 Les normalisations de cette option et des options suivantes
sont appliquées dans l'ordre de la UI. Plusieurs normalisations
peuvent être appliquées à un même champ à la suite.
""",
),
"normalize_fields_no_words_size1": Param(
["nom"],
type=["null", "array"],
examples=dropdown_acteur_columns,
description_md=r"""Les champs à normaliser en supprimant les mots
de taille 1.
exemple: 'Place à la montagne' -> 'Place la montagne'
0️⃣ Si aucun champ spécifié = s'applique à AUCUN champ
""",
),
"normalize_fields_no_words_size2_or_less": Param(
["nom"],
type=["null", "array"],
examples=dropdown_acteur_columns,
description_md="""Les champs à normaliser en supprimant les mots
de taille 2 ou moins.
exemple: "Place à la montagne" -> "Place montagne"
0️⃣ Si aucun champ spécifié = s'applique à AUCUN champ
""",
),
"normalize_fields_no_words_size3_or_less": Param(
["nom"],
type=["null", "array"],
examples=dropdown_acteur_columns,
description_md=r"""Les champs à normaliser en supprimant les mots
de taille 3 ou moins.
exemple: 'rue de la montagne' -> 'montagne'
0️⃣ Si aucun champ spécifié = s'applique à AUCUN champ
""",
),
"normalize_fields_order_unique_words": Param(
[],
type=["null", "array"],
examples=dropdown_acteur_columns,
description_md=f"""Les champs à normaliser en ordonnant les mots
par ordre alphabétique et en supprimant les doublons.
exemple: 'rue de la montagne rue' -> 'de la montagne rue'
💯 Si aucun champ spécifié = s'applique à TOUS les champs
{UI_PARAMS_SEPARATOR_CLUSTERING}
""",
),
"cluster_intra_source_is_allowed": Param(
False,
type="boolean",
description_md="""**🔄 INTRA-SOURCE**: si coché, les acteurs d'une même
source peuvent être clusterisés ensemble""",
),
"cluster_fields_exact": Param(
["code_postal", "ville"],
type="array",
examples=dropdown_acteur_columns,
description_md=r"""Les champs sur lesquels on fait le groupage exact.
exemple: ["code_postal", "ville"]""",
),
"cluster_fields_fuzzy": Param(
["nom", "adresse"],
type="array",
examples=dropdown_acteur_columns,
description_md=r"""Les champs sur lesquels on fait le groupage fuzzy.
exemple: ["code_postal", "ville"]""",
),
"cluster_fuzzy_threshold": Param(
0.5,
type="number",
description_md="""Seuil de similarité pour le groupage fuzzy.
0 = pas de similarité, 1 = similarité parfaite""",
),
},
schedule=None,
) as dag:
chain(
cluster_acteurs_config_validate_task(dag=dag),
cluster_acteurs_db_data_read_acteurs_task(dag=dag),
cluster_acteurs_normalize_task(dag=dag),
# TODO: besoin de refactoriser cette tâche:
# - changer cluster_acteurs_suggestions pour obtenir une
# df de clusters ignorés
# - utiliser cette df pour la tâche d'info
# cluster_acteurs_info_size1_task(dag=dag),
cluster_acteurs_suggestions_task(dag=dag),
cluster_acteurs_db_data_write_suggestions_task(dag=dag),
)
Empty file added dags/cluster/tasks/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions dags/cluster/tasks/airflow_logic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from .cluster_acteurs_config_validate_task import ( # noqa
cluster_acteurs_config_validate_task,
)
from .cluster_acteurs_db_data_read_acteurs_task import ( # noqa
cluster_acteurs_db_data_read_acteurs_task,
)
from .cluster_acteurs_db_data_write_suggestions_task import ( # noqa
cluster_acteurs_db_data_write_suggestions_task,
)
from .cluster_acteurs_info_size1_task import cluster_acteurs_info_size1_task # noqa
from .cluster_acteurs_normalize_task import cluster_acteurs_normalize_task # noqa
from .cluster_acteurs_suggestions_task import cluster_acteurs_suggestions_task # noqa
Loading

0 comments on commit f2e909c

Please sign in to comment.