feat(observatoire): restructurer tuile en 3 barres

- Barre 1 (grise) : % marchés avec champs considérations renseignés
- Barre 2 (rose)  : % positifs parmi les marchés renseignés — sociales
- Barre 3 (verte) : % positifs parmi les marchés renseignés — env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Colin Maudry
2026-06-22 22:21:37 +02:00
parent ae8292d68d
commit 074716bc16
2 changed files with 64 additions and 71 deletions
+36 -30
View File
@@ -736,20 +736,18 @@ CONSIDERATIONS_COLUMNS = {
def compute_considerations_stats(lff: pl.LazyFrame) -> dict[str, tuple[int, int]]:
"""Part des marchés (uid distincts) ayant au moins une considération.
"""Statistiques considérations pour l'observatoire.
Renvoie pour chaque clé (sociales, environnementales) :
- (count_pos, pct) : positifs / tous les uid distincts
- (count_ren, pct) suffixé _renseignees : non-null uid comme dénominateur,
positifs comme numérateur (= part de positifs parmi les marchés renseignés)
Clés renvoyées :
- "champs_renseignes" : (count_ren_sociales, pct / total)
- "{key}_renseignees" : (count_ren, pct positifs parmi renseignés)
Colonne absente -> (0, 0).
"""
names = lff.collect_schema().names()
present = {key: col for key, col in CONSIDERATIONS_COLUMNS.items() if col in names}
stats: dict[str, tuple[int, int]] = {}
stats: dict[str, tuple[int, int]] = {"champs_renseignes": (0, 0)}
for key in CONSIDERATIONS_COLUMNS:
stats[key] = (0, 0)
stats[f"{key}_renseignees"] = (0, 0)
if not present:
@@ -767,22 +765,22 @@ def compute_considerations_stats(lff: pl.LazyFrame) -> dict[str, tuple[int, int]
return stats
for key, col in present.items():
count_pos = agg.filter(pl.col(col).str.contains(CONSIDERATIONS_REGEX)).height
count_ren = agg.filter(pl.col(col).is_not_null()).height
count_pos_ren = agg.filter(
pl.col(col).is_not_null() & (pl.col(col) != "Sans objet")
).height
pct_pos_ren = round(100 * count_pos_ren / count_ren) if count_ren > 0 else 0
stats[key] = (count_pos, round(100 * count_pos / total))
stats[f"{key}_renseignees"] = (count_ren, pct_pos_ren)
if key == "sociales":
stats["champs_renseignes"] = (count_ren, round(100 * count_ren / total))
return stats
# (clé, libellé, couleur principale, couleur renseignée)
CONSIDERATIONS_DISPLAY = [
("sociales", "Sociales", "#CC6677", "#E5B2BB"),
("environnementales", "Environnementales", "#117733", "#88BB99"),
# (clé stats, libellé, couleur)
CONSIDERATIONS_RENSEIGNEES = [
("sociales_renseignees", "Sociales", "#CC6677"),
("environnementales_renseignees", "Environnementales", "#117733"),
]
@@ -795,13 +793,31 @@ def _progress_bar(pct: int, color: str) -> dbc.Progress:
def get_considerations_card_content(lff: pl.LazyFrame) -> html.Div:
"""Quatre barres groupées par type : considération positive + valeur renseignée."""
"""Trois barres : champs renseignés + part positive pour sociales et environnementales."""
stats = compute_considerations_stats(lff)
blocks = []
for key, label, color, color_ren in CONSIDERATIONS_DISPLAY:
count_pos, pct_pos = stats[key]
count_ren, pct_ren = stats[f"{key}_renseignees"]
count_ren, pct_ren = stats["champs_renseignes"]
blocks = [
html.Div(
className="mb-3",
children=[
html.Div(
className="d-flex justify-content-between",
children=[
html.Span("Champs considérations renseignés"),
html.Span(
f"{format_number(count_ren)} marchés",
className="text-muted",
),
],
),
_progress_bar(pct_ren, "#6c757d"),
],
)
]
for key, label, color in CONSIDERATIONS_RENSEIGNEES:
count, pct = stats[key]
blocks.append(
html.Div(
className="mb-3",
@@ -811,22 +827,12 @@ def get_considerations_card_content(lff: pl.LazyFrame) -> html.Div:
children=[
html.Span(label),
html.Span(
f"{format_number(count_pos)} marchés",
f"parmi les {format_number(count)} marchés renseignés",
className="text-muted",
),
],
),
_progress_bar(pct_pos, color),
html.Div(
className="d-flex justify-content-end mt-1",
children=[
html.Span(
f"parmi les {format_number(count_ren)} marchés renseignés",
className="text-muted",
),
],
),
_progress_bar(pct_ren, color_ren),
_progress_bar(pct, color),
],
)
)