From 1f48319a0f1ae8a736d1ab9dab2b2490dbc8d0ba Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Mon, 23 Mar 2026 13:25:08 +0100 Subject: [PATCH] =?UTF-8?q?Utilisation=20d'un=20store=20plut=C3=B4t=20que?= =?UTF-8?q?=20df=20global,=20regroupement=20des=20inputs/outputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/observatoire.py | 171 ++++++-------------------------------- src/utils.py | 102 +++++++++++------------ tests/test_main.py | 34 ++++---- 3 files changed, 93 insertions(+), 214 deletions(-) diff --git a/src/pages/observatoire.py b/src/pages/observatoire.py index 8a8dee1..a06cee6 100644 --- a/src/pages/observatoire.py +++ b/src/pages/observatoire.py @@ -90,8 +90,6 @@ OBSERVATOIRE_COLUMNS = [ ] ] -DF_FILTERED: pl.DataFrame = pl.DataFrame() - layout = [ dcc.Location(id="dashboard_url", refresh="callback-nav"), dcc.Store(id="observatoire-filters", storage_type="local"), @@ -324,7 +322,7 @@ Alors, on fait comment ? dbc.Col("Sous-traitance :", lg=5), dbc.Col( dbc.RadioItems( - id="dashboard_marche_sousTraitanceDeclaree", + id="dashboard_marche_sous_traitance_declaree", options=[ { "label": "Tous", @@ -380,7 +378,7 @@ Alors, on fait comment ? dbc.Row( dbc.Col( dcc.Dropdown( - id="dashboard_marche_considerationsSociales", + id="dashboard_marche_considerations_sociales", placeholder="Considérations sociales", options=get_enum_values_as_dict( "considerationsSociales" @@ -394,7 +392,7 @@ Alors, on fait comment ? dbc.Row( dbc.Col( dcc.Dropdown( - id="dashboard_marche_considerationsEnvironnementales", + id="dashboard_marche_considerations_environnementales", placeholder="Considérations environnementales", multi=True, options=get_enum_values_as_dict( @@ -544,30 +542,14 @@ FILTER_PARAMS = [ ("dashboard_montant_max", "montant_max", False, None), ("dashboard_marche_techniques", "techniques", True, None), ("dashboard_marche_innovant", "innovant", False, "all"), - ("dashboard_marche_sousTraitanceDeclaree", "sous_traitance", False, "all"), - ("dashboard_marche_considerationsSociales", "social", True, None), - ("dashboard_marche_considerationsEnvironnementales", "env", True, None), + ("dashboard_marche_sous_traitance_declaree", "sous_traitance", False, "all"), + ("dashboard_marche_considerations_sociales", "social", True, None), + ("dashboard_marche_considerations_environnementales", "env", True, None), ] @callback( - Output("dashboard_year", "value"), - Output("dashboard_acheteur_id", "value"), - Output("dashboard_acheteur_categorie", "value"), - Output("dashboard_acheteur_departement_code", "value"), - Output("dashboard_titulaire_id", "value"), - Output("dashboard_titulaire_categorie", "value"), - Output("dashboard_titulaire_departement_code", "value"), - Output("dashboard_marche_type", "value"), - Output("dashboard_marche_objet", "value"), - Output("dashboard_marche_code_cpv", "value"), - Output("dashboard_montant_min", "value"), - Output("dashboard_montant_max", "value"), - Output("dashboard_marche_techniques", "value"), - Output("dashboard_marche_innovant", "value"), - Output("dashboard_marche_sousTraitanceDeclaree", "value"), - Output("dashboard_marche_considerationsSociales", "value"), - Output("dashboard_marche_considerationsEnvironnementales", "value"), + *[Output(fp[0], "value") for fp in FILTER_PARAMS], Input("dashboard_url", "search"), Input("dashboard_url", "pathname"), State("observatoire-filters", "data"), @@ -670,73 +652,25 @@ def show_confirmation(n_clicks): @callback( Output("cards", "children"), - Input("dashboard_year", "value"), - Input("dashboard_acheteur_id", "value"), - Input("dashboard_acheteur_categorie", "value"), - Input("dashboard_acheteur_departement_code", "value"), - Input("dashboard_titulaire_id", "value"), - Input("dashboard_titulaire_categorie", "value"), - Input("dashboard_titulaire_departement_code", "value"), - Input("dashboard_marche_type", "value"), - Input("dashboard_marche_objet", "value"), - Input("dashboard_marche_code_cpv", "value"), - Input("dashboard_montant_min", "value"), - Input("dashboard_montant_max", "value"), - Input("dashboard_marche_techniques", "value"), - Input("dashboard_marche_innovant", "value"), - Input("dashboard_marche_sousTraitanceDeclaree", "value"), - Input("dashboard_marche_considerationsSociales", "value"), - Input("dashboard_marche_considerationsEnvironnementales", "value"), + Output("observatoire-filters", "data"), + *[Input(fp[0], "value") for fp in FILTER_PARAMS], ) -def udpate_dashboard_cards( - dashboard_year, - dashboard_acheteur_id, - dashboard_acheteur_categorie, - dashboard_acheteur_departement_code, - dashboard_titulaire_id, - dashboard_titulaire_categorie, - dashboard_titulaire_departement_code, - dashboard_marche_type, - dashboard_marche_objet, - dashboard_marche_code_cpv, - dashboard_montant_min, - dashboard_montant_max, - dashboard_marche_techniques, - dashboard_marche_innovant, - dashboard_marche_sous_traitance_declaree, - dashboard_marche_considerations_sociales, - dashboard_marche_considerations_environnementales, -): +def udpate_dashboard_cards(*filter_values): lff: pl.LazyFrame = df.lazy() # Filtrage des données - lff = prepare_dashboard_data( - lff=lff, - year=dashboard_year, - acheteur_id=dashboard_acheteur_id, - acheteur_categorie=dashboard_acheteur_categorie, - acheteur_departement_code=dashboard_acheteur_departement_code, - titulaire_id=dashboard_titulaire_id, - titulaire_categorie=dashboard_titulaire_categorie, - titulaire_departement_code=dashboard_titulaire_departement_code, - type=dashboard_marche_type, - objet=dashboard_marche_objet, - code_cpv=dashboard_marche_code_cpv, - considerations_sociales=dashboard_marche_considerations_sociales, - considerations_environnementales=dashboard_marche_considerations_environnementales, - montant_min=dashboard_montant_min, - montant_max=dashboard_montant_max, - techniques=dashboard_marche_techniques, - marche_innovant=dashboard_marche_innovant, - sous_traitance_declaree=dashboard_marche_sous_traitance_declaree, - ) + + filter_params = {} + for (input_id, url_key, is_multi, default), value in zip(FILTER_PARAMS, filter_values): + if value is None or value == default or value == [] or value == "": + continue + filter_params[input_id] = value + + lff = prepare_dashboard_data(lff=lff, **filter_params) # Génération des métriques dff = lff.collect(engine="streaming") - global DF_FILTERED - DF_FILTERED = dff - logger.debug("Filter data: " + str(dff.height)) df_per_uid = ( @@ -832,73 +766,18 @@ def udpate_dashboard_cards( ) ) - return dbc.Row(children=cards + geographic_maps + other_cards) + return dbc.Row(children=cards + geographic_maps + other_cards), filter_params @callback( Output("download-observatoire", "data"), Input("btn-download-observatoire", "n_clicks"), - State("dashboard_year", "value"), - State("dashboard_acheteur_id", "value"), - State("dashboard_acheteur_categorie", "value"), - State("dashboard_acheteur_departement_code", "value"), - State("dashboard_titulaire_id", "value"), - State("dashboard_titulaire_categorie", "value"), - State("dashboard_titulaire_departement_code", "value"), - State("dashboard_marche_type", "value"), - State("dashboard_marche_objet", "value"), - State("dashboard_marche_code_cpv", "value"), - State("dashboard_montant_min", "value"), - State("dashboard_montant_max", "value"), - State("dashboard_marche_techniques", "value"), - State("dashboard_marche_innovant", "value"), - State("dashboard_marche_sousTraitanceDeclaree", "value"), - State("dashboard_marche_considerationsSociales", "value"), - State("dashboard_marche_considerationsEnvironnementales", "value"), + State("observatoire-filters", "data"), State("observatoire-hidden-columns", "data"), prevent_initial_call=True, ) -def download_observatoire( - _n_clicks, - dashboard_year, - dashboard_acheteur_id, - dashboard_acheteur_categorie, - dashboard_acheteur_departement_code, - dashboard_titulaire_id, - dashboard_titulaire_categorie, - dashboard_titulaire_departement_code, - dashboard_marche_type, - dashboard_marche_objet, - dashboard_marche_code_cpv, - dashboard_montant_min, - dashboard_montant_max, - dashboard_marche_techniques, - dashboard_marche_innovant, - dashboard_marche_sous_traitance_declaree, - dashboard_considerations_sociales, - dashboard_considerations_environnementales, - hidden_columns, -): - lff = prepare_dashboard_data( - lff=df.lazy(), - year=dashboard_year, - acheteur_id=dashboard_acheteur_id, - acheteur_categorie=dashboard_acheteur_categorie, - acheteur_departement_code=dashboard_acheteur_departement_code, - titulaire_id=dashboard_titulaire_id, - titulaire_categorie=dashboard_titulaire_categorie, - titulaire_departement_code=dashboard_titulaire_departement_code, - type=dashboard_marche_type, - objet=dashboard_marche_objet, - code_cpv=dashboard_marche_code_cpv, - considerations_sociales=dashboard_considerations_sociales, - considerations_environnementales=dashboard_considerations_environnementales, - montant_min=dashboard_montant_min, - montant_max=dashboard_montant_max, - techniques=dashboard_marche_techniques, - marche_innovant=dashboard_marche_innovant, - sous_traitance_declaree=dashboard_marche_sous_traitance_declaree, - ) +def download_observatoire(_n_clicks, filter_params, hidden_columns): + lff = prepare_dashboard_data(lff=df.lazy(), **(filter_params or {})) if hidden_columns: lff = lff.drop(hidden_columns) @@ -974,16 +853,16 @@ def toggle_observatoire_preview(n_clicks, is_open): Input("observatoire-preview-table", "page_size"), Input("observatoire-preview-table", "sort_by"), State("observatoire-preview-table", "data_timestamp"), + State("observatoire-filters", "data"), prevent_initial_call=True, ) def populate_preview_table( - is_open, filter_query, page_current, page_size, sort_by, data_timestamp + is_open, filter_query, page_current, page_size, sort_by, data_timestamp, filter_params ): if not is_open: return (no_update,) * 9 - global DF_FILTERED - lff = DF_FILTERED.lazy() + lff = prepare_dashboard_data(lff=df.lazy(), **(filter_params or {})) return prepare_table_data( lff, diff --git a/src/utils.py b/src/utils.py index 63e5750..934ee56 100644 --- a/src/utils.py +++ b/src/utils.py @@ -699,98 +699,98 @@ def prepare_table_data( def prepare_dashboard_data( lff: pl.LazyFrame, - year, - acheteur_id, - acheteur_categorie, - acheteur_departement_code, - titulaire_id, - titulaire_categorie, - titulaire_departement_code, - type, - objet, - code_cpv, - considerations_sociales, - considerations_environnementales, - techniques, - marche_innovant, - sous_traitance_declaree, - montant_min=None, - montant_max=None, + dashboard_year, + dashboard_acheteur_id, + dashboard_acheteur_categorie, + dashboard_acheteur_departement_code, + dashboard_titulaire_id, + dashboard_titulaire_categorie, + dashboard_titulaire_departement_code, + dashboard_marche_type, + dashboard_marche_objet, + dashboard_marche_code_cpv, + dashboard_marche_considerations_sociales, + dashboard_marche_considerations_environnementales, + dashboard_marche_techniques, + dashboard_marche_innovant, + dashboard_marche_sous_traitance_declaree, + dashboard_montant_min=None, + dashboard_montant_max=None, ) -> pl.LazyFrame: - if year: - lff = lff.filter(pl.col("dateNotification").dt.year() == int(year)) + if dashboard_year: + lff = lff.filter(pl.col("dateNotification").dt.year() == int(dashboard_year)) else: lff = lff.filter( pl.col("dateNotification") > (datetime.now() - timedelta(days=365)) ) - if acheteur_id: - lff = lff.filter(pl.col("acheteur_id").str.contains(acheteur_id)) + if dashboard_acheteur_id: + lff = lff.filter(pl.col("acheteur_id").str.contains(dashboard_acheteur_id)) else: - if acheteur_categorie: - lff = lff.filter(pl.col("acheteur_categorie") == acheteur_categorie) - if acheteur_departement_code: + if dashboard_acheteur_categorie: + lff = lff.filter(pl.col("acheteur_categorie") == dashboard_acheteur_categorie) + if dashboard_acheteur_departement_code: lff = lff.filter( - pl.col("acheteur_departement_code").is_in(acheteur_departement_code) + pl.col("acheteur_departement_code").is_in(dashboard_acheteur_departement_code) ) - if titulaire_id: - lff = lff.filter(pl.col("titulaire_id").str.contains(titulaire_id)) + if dashboard_titulaire_id: + lff = lff.filter(pl.col("titulaire_id").str.contains(dashboard_titulaire_id)) else: - if titulaire_categorie: - lff = lff.filter(pl.col("titulaire_categorie") == titulaire_categorie) - if titulaire_departement_code: + if dashboard_titulaire_categorie: + lff = lff.filter(pl.col("titulaire_categorie") == dashboard_titulaire_categorie) + if dashboard_titulaire_departement_code: lff = lff.filter( - pl.col("titulaire_departement_code").is_in(titulaire_departement_code) + pl.col("titulaire_departement_code").is_in(dashboard_titulaire_departement_code) ) - if type: - lff = lff.filter(pl.col("type") == type) + if dashboard_marche_type: + lff = lff.filter(pl.col("type") == dashboard_marche_type) - if objet: - lff = lff.filter(pl.col("objet").str.contains(f"(?i){objet}")) + if dashboard_marche_objet: + lff = lff.filter(pl.col("objet").str.contains(f"(?i){dashboard_marche_objet}")) - if code_cpv: - lff = lff.filter(pl.col("codeCPV").str.starts_with(code_cpv)) + if dashboard_marche_code_cpv: + lff = lff.filter(pl.col("codeCPV").str.starts_with(dashboard_marche_code_cpv)) - if marche_innovant and marche_innovant != "all": - lff = lff.filter(pl.col("marcheInnovant") == marche_innovant) + if dashboard_marche_innovant and dashboard_marche_innovant != "all": + lff = lff.filter(pl.col("marcheInnovant") == dashboard_marche_innovant) - if sous_traitance_declaree and sous_traitance_declaree != "all": - lff = lff.filter(pl.col("sousTraitanceDeclaree") == sous_traitance_declaree) + if dashboard_marche_sous_traitance_declaree and dashboard_marche_sous_traitance_declaree != "all": + lff = lff.filter(pl.col("sousTraitanceDeclaree") == dashboard_marche_sous_traitance_declaree) - if techniques: + if dashboard_marche_techniques: lff = lff.filter( pl.col("techniques") .str.split(", ") - .list.set_intersection(techniques) + .list.set_intersection(dashboard_marche_techniques) .list.len() > 0 ) - if considerations_sociales: + if dashboard_marche_considerations_sociales: lff = lff.filter( pl.col("considerationsSociales") .str.split(", ") - .list.set_intersection(considerations_sociales) + .list.set_intersection(dashboard_marche_considerations_sociales) .list.len() > 0 ) - if considerations_environnementales: + if dashboard_marche_considerations_environnementales: lff = lff.filter( pl.col("considerationsEnvironnementales") .str.split(", ") - .list.set_intersection(considerations_environnementales) + .list.set_intersection(dashboard_marche_considerations_environnementales) .list.len() > 0 ) - if montant_min is not None: - lff = lff.filter(pl.col("montant") >= montant_min) + if dashboard_montant_min is not None: + lff = lff.filter(pl.col("montant") >= dashboard_montant_min) - if montant_max is not None: - lff = lff.filter(pl.col("montant") <= montant_max) + if dashboard_montant_max is not None: + lff = lff.filter(pl.col("montant") <= dashboard_montant_max) return lff diff --git a/tests/test_main.py b/tests/test_main.py index 8719e7b..0eaaec1 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -234,23 +234,23 @@ def test_010_observatoire_montant_filter(): def apply(min_val=None, max_val=None): return prepare_dashboard_data( data.lazy(), - year="2025", - acheteur_id=None, - acheteur_categorie=None, - acheteur_departement_code=None, - titulaire_id=None, - titulaire_categorie=None, - titulaire_departement_code=None, - type=None, - objet=None, - code_cpv=None, - considerations_sociales=None, - considerations_environnementales=None, - techniques=None, - marche_innovant=None, - sous_traitance_declaree=None, - montant_min=min_val, - montant_max=max_val, + dashboard_year="2025", + dashboard_acheteur_id=None, + dashboard_acheteur_categorie=None, + dashboard_acheteur_departement_code=None, + dashboard_titulaire_id=None, + dashboard_titulaire_categorie=None, + dashboard_titulaire_departement_code=None, + dashboard_marche_type=None, + dashboard_marche_objet=None, + dashboard_marche_code_cpv=None, + dashboard_marche_considerations_sociales=None, + dashboard_marche_considerations_environnementales=None, + dashboard_marche_techniques=None, + dashboard_marche_innovant=None, + dashboard_marche_sous_traitance_declaree=None, + dashboard_montant_min=min_val, + dashboard_montant_max=max_val, ).collect() assert apply().height == 3