Filtre par montant #65

This commit is contained in:
Colin Maudry
2026-03-18 21:13:21 +01:00
parent c77511d4e8
commit 78d528f75b
3 changed files with 112 additions and 2 deletions
+12 -1
View File
@@ -201,7 +201,8 @@ p.version > a {
margin-bottom: 6px; margin-bottom: 6px;
} }
#filters input[type="text"] { #filters input[type="text"],
#filters input[type="number"] {
border: 1px #ccc solid; border: 1px #ccc solid;
border-radius: 3px; border-radius: 3px;
padding-left: 8px; padding-left: 8px;
@@ -578,3 +579,13 @@ summary > h4 {
display: none; display: none;
} }
} }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
+59 -1
View File
@@ -67,6 +67,8 @@ def _apply_filters(
marche_type, marche_type,
considerations_sociales, considerations_sociales,
considerations_environnementales, considerations_environnementales,
montant_min=None,
montant_max=None,
) -> pl.LazyFrame: ) -> pl.LazyFrame:
if year: if year:
lff = lff.filter(pl.col("dateNotification").dt.year() == int(year)) lff = lff.filter(pl.col("dateNotification").dt.year() == int(year))
@@ -116,6 +118,12 @@ def _apply_filters(
> 0 > 0
) )
if montant_min is not None:
lff = lff.filter(pl.col("montant") >= montant_min)
if montant_max is not None:
lff = lff.filter(pl.col("montant") <= montant_max)
return lff return lff
@@ -271,6 +279,32 @@ Alors, on fait comment ?
), ),
), ),
), ),
dbc.Row(
[
dbc.Col(
dcc.Input(
id="dashboard_montant_min",
placeholder="Montant min.",
type="number",
min=0,
debounce=True,
style={"width": "100%"},
),
width=6,
),
dbc.Col(
dcc.Input(
id="dashboard_montant_max",
placeholder="Montant max.",
type="number",
min=0,
debounce=True,
style={"width": "100%"},
),
width=6,
),
]
),
dcc.Download(id="download-observatoire"), dcc.Download(id="download-observatoire"),
dbc.Button( dbc.Button(
"Télécharger au format Excel", "Télécharger au format Excel",
@@ -313,6 +347,8 @@ Alors, on fait comment ?
Output("dashboard_marche_type", "value"), Output("dashboard_marche_type", "value"),
Output("dashboard_marche_considerationsSociales", "value"), Output("dashboard_marche_considerationsSociales", "value"),
Output("dashboard_marche_considerationsEnvironnementales", "value"), Output("dashboard_marche_considerationsEnvironnementales", "value"),
Output("dashboard_montant_min", "value"),
Output("dashboard_montant_max", "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"),
@@ -334,6 +370,8 @@ def restore_filters(search, _pathname, stored_filters):
None, None,
None, None,
None, None,
None,
None,
) )
if stored_filters: if stored_filters:
@@ -348,9 +386,11 @@ def restore_filters(search, _pathname, stored_filters):
stored_filters.get("marche_type"), stored_filters.get("marche_type"),
stored_filters.get("considerations_sociales"), stored_filters.get("considerations_sociales"),
stored_filters.get("considerations_environnementales"), stored_filters.get("considerations_environnementales"),
stored_filters.get("montant_min"),
stored_filters.get("montant_max"),
) )
return (no_update,) * 10 return (no_update,) * 12
@callback( @callback(
@@ -365,6 +405,8 @@ def restore_filters(search, _pathname, stored_filters):
Input("dashboard_marche_type", "value"), Input("dashboard_marche_type", "value"),
Input("dashboard_marche_considerationsSociales", "value"), Input("dashboard_marche_considerationsSociales", "value"),
Input("dashboard_marche_considerationsEnvironnementales", "value"), Input("dashboard_marche_considerationsEnvironnementales", "value"),
Input("dashboard_montant_min", "value"),
Input("dashboard_montant_max", "value"),
prevent_initial_call=True, prevent_initial_call=True,
) )
def save_filters_to_storage( def save_filters_to_storage(
@@ -378,6 +420,8 @@ def save_filters_to_storage(
marche_type, marche_type,
considerations_sociales, considerations_sociales,
considerations_environnementales, considerations_environnementales,
montant_min,
montant_max,
): ):
return { return {
"year": year, "year": year,
@@ -390,6 +434,8 @@ def save_filters_to_storage(
"marche_type": marche_type, "marche_type": marche_type,
"considerations_sociales": considerations_sociales, "considerations_sociales": considerations_sociales,
"considerations_environnementales": considerations_environnementales, "considerations_environnementales": considerations_environnementales,
"montant_min": montant_min,
"montant_max": montant_max,
} }
@@ -453,6 +499,8 @@ def sync_observatoire_share_url(acheteur_id, titulaire_id, href):
Input("dashboard_marche_type", "value"), Input("dashboard_marche_type", "value"),
Input("dashboard_marche_considerationsSociales", "value"), Input("dashboard_marche_considerationsSociales", "value"),
Input("dashboard_marche_considerationsEnvironnementales", "value"), Input("dashboard_marche_considerationsEnvironnementales", "value"),
Input("dashboard_montant_min", "value"),
Input("dashboard_montant_max", "value"),
) )
def udpate_dashboard_cards( def udpate_dashboard_cards(
dashboard_year, dashboard_year,
@@ -465,6 +513,8 @@ def udpate_dashboard_cards(
dashboard_marche_type, dashboard_marche_type,
dashboard_marche_considerations_sociales, dashboard_marche_considerations_sociales,
dashboard_marche_considerations_environnementales, dashboard_marche_considerations_environnementales,
dashboard_montant_min,
dashboard_montant_max,
): ):
lff: pl.LazyFrame = df.lazy() lff: pl.LazyFrame = df.lazy()
lff = lff.select( lff = lff.select(
@@ -490,6 +540,8 @@ def udpate_dashboard_cards(
dashboard_marche_type, dashboard_marche_type,
dashboard_marche_considerations_sociales, dashboard_marche_considerations_sociales,
dashboard_marche_considerations_environnementales, dashboard_marche_considerations_environnementales,
montant_min=dashboard_montant_min,
montant_max=dashboard_montant_max,
) )
# Génération des métriques # Génération des métriques
@@ -618,6 +670,8 @@ def udpate_dashboard_cards(
State("dashboard_marche_type", "value"), State("dashboard_marche_type", "value"),
State("dashboard_marche_considerationsSociales", "value"), State("dashboard_marche_considerationsSociales", "value"),
State("dashboard_marche_considerationsEnvironnementales", "value"), State("dashboard_marche_considerationsEnvironnementales", "value"),
State("dashboard_montant_min", "value"),
State("dashboard_montant_max", "value"),
prevent_initial_call=True, prevent_initial_call=True,
) )
def download_observatoire( def download_observatoire(
@@ -632,6 +686,8 @@ def download_observatoire(
marche_type, marche_type,
considerations_sociales, considerations_sociales,
considerations_environnementales, considerations_environnementales,
montant_min,
montant_max,
): ):
lff = _apply_filters( lff = _apply_filters(
df.lazy(), df.lazy(),
@@ -645,6 +701,8 @@ def download_observatoire(
marche_type, marche_type,
considerations_sociales, considerations_sociales,
considerations_environnementales, considerations_environnementales,
montant_min=montant_min,
montant_max=montant_max,
) )
def to_bytes(buffer): def to_bytes(buffer):
+41
View File
@@ -216,6 +216,47 @@ def test_008_search_to_observatoire(dash_duo: DashComposite):
) )
def test_010_observatoire_montant_filter():
import datetime
import polars as pl
from pages.observatoire import _apply_filters
from src.app import (
app, # noqa: F401 instantiates the Dash app before register_page() calls
)
data = pl.DataFrame(
{
"uid": ["1", "2", "3"],
"montant": [100.0, 500.0, 1000.0],
"dateNotification": [datetime.date(2025, 1, 1)] * 3,
}
)
def apply(min_val=None, max_val=None):
return _apply_filters(
data.lazy(),
year="2025",
acheteur_id=None,
acheteur_categorie=None,
acheteur_departement_code=None,
titulaire_id=None,
titulaire_categorie=None,
titulaire_departement_code=None,
marche_type=None,
considerations_sociales=None,
considerations_environnementales=None,
montant_min=min_val,
montant_max=max_val,
).collect()
assert apply().height == 3
assert apply(min_val=400).height == 2 # 500, 1000
assert apply(max_val=500).height == 2 # 100, 500
assert apply(min_val=200, max_val=600).height == 1 # 500 only
def test_009_observatoire_filter_persistence(dash_duo: DashComposite): def test_009_observatoire_filter_persistence(dash_duo: DashComposite):
import time import time