docs: spec tuile considérations sociales/environnementales (observatoire)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user