Utilisation d'un store plutôt que df global, regroupement des inputs/outputs

This commit is contained in:
Colin Maudry
2026-03-23 13:25:08 +01:00
parent 08fc4dcfdc
commit 1f48319a0f
3 changed files with 93 additions and 214 deletions
+25 -146
View File
@@ -90,8 +90,6 @@ OBSERVATOIRE_COLUMNS = [
] ]
] ]
DF_FILTERED: pl.DataFrame = pl.DataFrame()
layout = [ layout = [
dcc.Location(id="dashboard_url", refresh="callback-nav"), dcc.Location(id="dashboard_url", refresh="callback-nav"),
dcc.Store(id="observatoire-filters", storage_type="local"), dcc.Store(id="observatoire-filters", storage_type="local"),
@@ -324,7 +322,7 @@ Alors, on fait comment ?
dbc.Col("Sous-traitance :", lg=5), dbc.Col("Sous-traitance :", lg=5),
dbc.Col( dbc.Col(
dbc.RadioItems( dbc.RadioItems(
id="dashboard_marche_sousTraitanceDeclaree", id="dashboard_marche_sous_traitance_declaree",
options=[ options=[
{ {
"label": "Tous", "label": "Tous",
@@ -380,7 +378,7 @@ Alors, on fait comment ?
dbc.Row( dbc.Row(
dbc.Col( dbc.Col(
dcc.Dropdown( dcc.Dropdown(
id="dashboard_marche_considerationsSociales", id="dashboard_marche_considerations_sociales",
placeholder="Considérations sociales", placeholder="Considérations sociales",
options=get_enum_values_as_dict( options=get_enum_values_as_dict(
"considerationsSociales" "considerationsSociales"
@@ -394,7 +392,7 @@ Alors, on fait comment ?
dbc.Row( dbc.Row(
dbc.Col( dbc.Col(
dcc.Dropdown( dcc.Dropdown(
id="dashboard_marche_considerationsEnvironnementales", id="dashboard_marche_considerations_environnementales",
placeholder="Considérations environnementales", placeholder="Considérations environnementales",
multi=True, multi=True,
options=get_enum_values_as_dict( options=get_enum_values_as_dict(
@@ -544,30 +542,14 @@ FILTER_PARAMS = [
("dashboard_montant_max", "montant_max", False, None), ("dashboard_montant_max", "montant_max", False, None),
("dashboard_marche_techniques", "techniques", True, None), ("dashboard_marche_techniques", "techniques", True, None),
("dashboard_marche_innovant", "innovant", False, "all"), ("dashboard_marche_innovant", "innovant", False, "all"),
("dashboard_marche_sousTraitanceDeclaree", "sous_traitance", False, "all"), ("dashboard_marche_sous_traitance_declaree", "sous_traitance", False, "all"),
("dashboard_marche_considerationsSociales", "social", True, None), ("dashboard_marche_considerations_sociales", "social", True, None),
("dashboard_marche_considerationsEnvironnementales", "env", True, None), ("dashboard_marche_considerations_environnementales", "env", True, None),
] ]
@callback( @callback(
Output("dashboard_year", "value"), *[Output(fp[0], "value") for fp in FILTER_PARAMS],
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"),
Input("dashboard_url", "search"), Input("dashboard_url", "search"),
Input("dashboard_url", "pathname"), Input("dashboard_url", "pathname"),
State("observatoire-filters", "data"), State("observatoire-filters", "data"),
@@ -670,73 +652,25 @@ def show_confirmation(n_clicks):
@callback( @callback(
Output("cards", "children"), Output("cards", "children"),
Input("dashboard_year", "value"), Output("observatoire-filters", "data"),
Input("dashboard_acheteur_id", "value"), *[Input(fp[0], "value") for fp in FILTER_PARAMS],
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"),
) )
def udpate_dashboard_cards( def udpate_dashboard_cards(*filter_values):
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,
):
lff: pl.LazyFrame = df.lazy() lff: pl.LazyFrame = df.lazy()
# Filtrage des données # Filtrage des données
lff = prepare_dashboard_data(
lff=lff, filter_params = {}
year=dashboard_year, for (input_id, url_key, is_multi, default), value in zip(FILTER_PARAMS, filter_values):
acheteur_id=dashboard_acheteur_id, if value is None or value == default or value == [] or value == "":
acheteur_categorie=dashboard_acheteur_categorie, continue
acheteur_departement_code=dashboard_acheteur_departement_code, filter_params[input_id] = value
titulaire_id=dashboard_titulaire_id,
titulaire_categorie=dashboard_titulaire_categorie, lff = prepare_dashboard_data(lff=lff, **filter_params)
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,
)
# Génération des métriques # Génération des métriques
dff = lff.collect(engine="streaming") dff = lff.collect(engine="streaming")
global DF_FILTERED
DF_FILTERED = dff
logger.debug("Filter data: " + str(dff.height)) logger.debug("Filter data: " + str(dff.height))
df_per_uid = ( 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( @callback(
Output("download-observatoire", "data"), Output("download-observatoire", "data"),
Input("btn-download-observatoire", "n_clicks"), Input("btn-download-observatoire", "n_clicks"),
State("dashboard_year", "value"), State("observatoire-filters", "data"),
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-hidden-columns", "data"), State("observatoire-hidden-columns", "data"),
prevent_initial_call=True, prevent_initial_call=True,
) )
def download_observatoire( def download_observatoire(_n_clicks, filter_params, hidden_columns):
_n_clicks, lff = prepare_dashboard_data(lff=df.lazy(), **(filter_params or {}))
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,
)
if hidden_columns: if hidden_columns:
lff = lff.drop(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", "page_size"),
Input("observatoire-preview-table", "sort_by"), Input("observatoire-preview-table", "sort_by"),
State("observatoire-preview-table", "data_timestamp"), State("observatoire-preview-table", "data_timestamp"),
State("observatoire-filters", "data"),
prevent_initial_call=True, prevent_initial_call=True,
) )
def populate_preview_table( 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: if not is_open:
return (no_update,) * 9 return (no_update,) * 9
global DF_FILTERED lff = prepare_dashboard_data(lff=df.lazy(), **(filter_params or {}))
lff = DF_FILTERED.lazy()
return prepare_table_data( return prepare_table_data(
lff, lff,
+51 -51
View File
@@ -699,98 +699,98 @@ def prepare_table_data(
def prepare_dashboard_data( def prepare_dashboard_data(
lff: pl.LazyFrame, lff: pl.LazyFrame,
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,
techniques, dashboard_marche_techniques,
marche_innovant, dashboard_marche_innovant,
sous_traitance_declaree, dashboard_marche_sous_traitance_declaree,
montant_min=None, dashboard_montant_min=None,
montant_max=None, dashboard_montant_max=None,
) -> pl.LazyFrame: ) -> pl.LazyFrame:
if year: if dashboard_year:
lff = lff.filter(pl.col("dateNotification").dt.year() == int(year)) lff = lff.filter(pl.col("dateNotification").dt.year() == int(dashboard_year))
else: else:
lff = lff.filter( lff = lff.filter(
pl.col("dateNotification") > (datetime.now() - timedelta(days=365)) pl.col("dateNotification") > (datetime.now() - timedelta(days=365))
) )
if acheteur_id: if dashboard_acheteur_id:
lff = lff.filter(pl.col("acheteur_id").str.contains(acheteur_id)) lff = lff.filter(pl.col("acheteur_id").str.contains(dashboard_acheteur_id))
else: else:
if acheteur_categorie: if dashboard_acheteur_categorie:
lff = lff.filter(pl.col("acheteur_categorie") == acheteur_categorie) lff = lff.filter(pl.col("acheteur_categorie") == dashboard_acheteur_categorie)
if acheteur_departement_code: if dashboard_acheteur_departement_code:
lff = lff.filter( 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: if dashboard_titulaire_id:
lff = lff.filter(pl.col("titulaire_id").str.contains(titulaire_id)) lff = lff.filter(pl.col("titulaire_id").str.contains(dashboard_titulaire_id))
else: else:
if titulaire_categorie: if dashboard_titulaire_categorie:
lff = lff.filter(pl.col("titulaire_categorie") == titulaire_categorie) lff = lff.filter(pl.col("titulaire_categorie") == dashboard_titulaire_categorie)
if titulaire_departement_code: if dashboard_titulaire_departement_code:
lff = lff.filter( 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: if dashboard_marche_type:
lff = lff.filter(pl.col("type") == type) lff = lff.filter(pl.col("type") == dashboard_marche_type)
if objet: if dashboard_marche_objet:
lff = lff.filter(pl.col("objet").str.contains(f"(?i){objet}")) lff = lff.filter(pl.col("objet").str.contains(f"(?i){dashboard_marche_objet}"))
if code_cpv: if dashboard_marche_code_cpv:
lff = lff.filter(pl.col("codeCPV").str.starts_with(code_cpv)) lff = lff.filter(pl.col("codeCPV").str.starts_with(dashboard_marche_code_cpv))
if marche_innovant and marche_innovant != "all": if dashboard_marche_innovant and dashboard_marche_innovant != "all":
lff = lff.filter(pl.col("marcheInnovant") == marche_innovant) lff = lff.filter(pl.col("marcheInnovant") == dashboard_marche_innovant)
if sous_traitance_declaree and sous_traitance_declaree != "all": if dashboard_marche_sous_traitance_declaree and dashboard_marche_sous_traitance_declaree != "all":
lff = lff.filter(pl.col("sousTraitanceDeclaree") == sous_traitance_declaree) lff = lff.filter(pl.col("sousTraitanceDeclaree") == dashboard_marche_sous_traitance_declaree)
if techniques: if dashboard_marche_techniques:
lff = lff.filter( lff = lff.filter(
pl.col("techniques") pl.col("techniques")
.str.split(", ") .str.split(", ")
.list.set_intersection(techniques) .list.set_intersection(dashboard_marche_techniques)
.list.len() .list.len()
> 0 > 0
) )
if considerations_sociales: if dashboard_marche_considerations_sociales:
lff = lff.filter( lff = lff.filter(
pl.col("considerationsSociales") pl.col("considerationsSociales")
.str.split(", ") .str.split(", ")
.list.set_intersection(considerations_sociales) .list.set_intersection(dashboard_marche_considerations_sociales)
.list.len() .list.len()
> 0 > 0
) )
if considerations_environnementales: if dashboard_marche_considerations_environnementales:
lff = lff.filter( lff = lff.filter(
pl.col("considerationsEnvironnementales") pl.col("considerationsEnvironnementales")
.str.split(", ") .str.split(", ")
.list.set_intersection(considerations_environnementales) .list.set_intersection(dashboard_marche_considerations_environnementales)
.list.len() .list.len()
> 0 > 0
) )
if montant_min is not None: if dashboard_montant_min is not None:
lff = lff.filter(pl.col("montant") >= montant_min) lff = lff.filter(pl.col("montant") >= dashboard_montant_min)
if montant_max is not None: if dashboard_montant_max is not None:
lff = lff.filter(pl.col("montant") <= montant_max) lff = lff.filter(pl.col("montant") <= dashboard_montant_max)
return lff return lff
+17 -17
View File
@@ -234,23 +234,23 @@ def test_010_observatoire_montant_filter():
def apply(min_val=None, max_val=None): def apply(min_val=None, max_val=None):
return prepare_dashboard_data( return prepare_dashboard_data(
data.lazy(), data.lazy(),
year="2025", dashboard_year="2025",
acheteur_id=None, dashboard_acheteur_id=None,
acheteur_categorie=None, dashboard_acheteur_categorie=None,
acheteur_departement_code=None, dashboard_acheteur_departement_code=None,
titulaire_id=None, dashboard_titulaire_id=None,
titulaire_categorie=None, dashboard_titulaire_categorie=None,
titulaire_departement_code=None, dashboard_titulaire_departement_code=None,
type=None, dashboard_marche_type=None,
objet=None, dashboard_marche_objet=None,
code_cpv=None, dashboard_marche_code_cpv=None,
considerations_sociales=None, dashboard_marche_considerations_sociales=None,
considerations_environnementales=None, dashboard_marche_considerations_environnementales=None,
techniques=None, dashboard_marche_techniques=None,
marche_innovant=None, dashboard_marche_innovant=None,
sous_traitance_declaree=None, dashboard_marche_sous_traitance_declaree=None,
montant_min=min_val, dashboard_montant_min=min_val,
montant_max=max_val, dashboard_montant_max=max_val,
).collect() ).collect()
assert apply().height == 3 assert apply().height == 3