826448d6be
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
113 lines
4.2 KiB
Markdown
113 lines
4.2 KiB
Markdown
# Tuile « Considérations sociales et environnementales » — Observatoire
|
|
|
|
## Objectif
|
|
|
|
Ajouter dans `/observatoire` une tuile (card) qui visualise, à l'aide de barres
|
|
de progression « plus ou moins remplies », la part des marchés publics filtrés
|
|
qui comportent **au moins une considération sociale** et la part qui comportent
|
|
**au moins une considération environnementale**.
|
|
|
|
La tuile s'insère juste **après la tuile « Type d'achat »**, avec le même style
|
|
que les autres cards.
|
|
|
|
## Données
|
|
|
|
Colonnes concernées (type `String`, valeurs libres potentiellement composées) :
|
|
|
|
- `considerationsSociales`
|
|
- `considerationsEnvironnementales`
|
|
|
|
Exemples de valeurs : `Sans objet`, `Clause sociale`, `Critère social`,
|
|
`Marché réservé`, `Clause environnementale`, `Critère environnemental`,
|
|
`Pas de considération sociale`, `null`, ou des combinaisons
|
|
(`Critère social, Clause sociale`).
|
|
|
|
### Définition « au moins une considération »
|
|
|
|
Un marché compte comme ayant une considération si la valeur de la colonne
|
|
**contient** l'un des mots-clés (insensible à la casse) :
|
|
|
|
- `Clause`
|
|
- `Critère`
|
|
- `Marché réservé`
|
|
|
|
Regex utilisée : `(?i)Clause|Critère|Marché réservé`.
|
|
|
|
Conséquence (validée avec l'utilisateur) : **`Marché réservé` compte comme
|
|
considération sociale**. Les valeurs `Sans objet`, `Pas de considération…` et
|
|
`null` ne contiennent aucun de ces mots-clés et ne comptent donc pas.
|
|
|
|
### Calcul du pourcentage
|
|
|
|
- **Dédoublonnage par `uid`** : un marché est compté une seule fois même s'il
|
|
apparaît sur plusieurs lignes (plusieurs titulaires). On prend la première
|
|
valeur de chaque colonne par `uid`.
|
|
- **Dénominateur** : **tous** les marchés filtrés (y compris `Sans objet` et
|
|
non renseignés) — validé avec l'utilisateur.
|
|
- **Numérateur** : nombre de marchés (uid distincts) dont la valeur de colonne
|
|
satisfait la regex.
|
|
- `pourcentage = round(100 * numérateur / dénominateur)` ; si dénominateur = 0,
|
|
pourcentage = 0.
|
|
|
|
## Composant visuel
|
|
|
|
Nouvelle fonction `get_considerations_card_content(lff: pl.LazyFrame)` dans
|
|
`src/figures.py`, renvoyant un `html.Div` contenant deux barres `dbc.Progress`
|
|
empilées :
|
|
|
|
| Considération | Couleur (px.colors.qualitative.Safe) | Valeur RGB |
|
|
| ----------------- | ------------------------------------ | -------------------- |
|
|
| Sociales | index 1 (rouge) | `rgb(204, 102, 119)` |
|
|
| Environnementales | index 3 (vert) | `rgb(17, 119, 51)` |
|
|
|
|
Chaque barre :
|
|
|
|
- `dbc.Progress(value=pourcentage, label=f"{pourcentage} %", style={"backgroundColor": <couleur>})`
|
|
- précédée d'un libellé (`Sociales` / `Environnementales`) et suivie du nombre
|
|
de marchés concernés (`N marchés`), formaté avec `format_number`.
|
|
|
|
### Robustesse (colonne absente)
|
|
|
|
`tests/test.parquet` peut ne pas contenir ces colonnes. La fonction vérifie la
|
|
présence de chaque colonne via `lff.collect_schema().names()` ; si une colonne
|
|
manque, son pourcentage et son compte valent 0 (pas d'exception), à l'image de
|
|
`get_distance_histogram`.
|
|
|
|
## Intégration
|
|
|
|
Dans `src/pages/observatoire.py`, fonction `_compute_dashboard_children` :
|
|
|
|
```python
|
|
donut_marche_type = make_donut(lff, "type", per_uid=True, nulls="?")
|
|
cards.append(make_card(title="Type d'achat", ...))
|
|
|
|
# NOUVEAU
|
|
considerations = get_considerations_card_content(lff)
|
|
cards.append(
|
|
make_card(
|
|
title="Considérations sociales et environnementales",
|
|
subtitle="part des marchés concernés",
|
|
fig=considerations,
|
|
)
|
|
)
|
|
```
|
|
|
|
`make_card` utilise ses dimensions par défaut (`lg=6, xl=4`), comme la tuile
|
|
« Type d'achat ».
|
|
|
|
Import à ajouter : `get_considerations_card_content` depuis `src.figures`.
|
|
|
|
## Tests
|
|
|
|
- Test unitaire de `get_considerations_card_content` sur un petit `LazyFrame`
|
|
construit en mémoire couvrant : valeur avec considération, `Sans objet`,
|
|
`null`, `Marché réservé`, doublon de `uid`. Vérifier les pourcentages
|
|
attendus.
|
|
- Cas colonne absente → 0 % sans exception.
|
|
|
|
## Hors périmètre (YAGNI)
|
|
|
|
- Pas de tooltip détaillé sur les types de considérations.
|
|
- Pas de graphe de répartition par type (clause vs critère).
|
|
- Pas de nouveau filtre (les filtres existants `social`/`env` restent inchangés).
|