Contexte métier
Le taux de déclaration mesure le pourcentage d'entreprises qui ont envoyé leur déclaration d'égalité professionnelle (index égalité H/F) sur une année donnée, parmi celles qui y sont légalement obligées.
Objectif pour la DGT : suivre en temps réel l'avancement de la campagne annuelle.
À quoi ça ressemble à l'écran
Une tuile DSFR (fr-tile) dans le dashboard admin. Elle affiche un gros pourcentage (la valeur de l'année en cours), et juste en dessous, en plus petit, l'écart par rapport à l'année précédente et les valeurs absolues qui ont servi au calcul.
┌──────────────────────────────────┐
│ Taux de déclaration 2026 │ ← titre
│ │
│ 73,4 % │ ← valeur de l'année (gros)
│ │
│ ▲ +2,1 pts vs 2025 │ ← badge vert/rouge selon le sens
│ 4 213 / 5 738 entreprises │ ← numérateur / dénominateur
└──────────────────────────────────┘
Le but : qu'un agent DGT ouvre la page et comprenne en 2 secondes "où on en est" sur la campagne, et "est-ce qu'on progresse vs l'an dernier".
Page d'accueil
Page /admin/stats (à créer si elle n'existe pas). La tuile vit dans une grille de KPIs partagée avec d'autres tiles chiffrées (#3219 K8, plus tard K12).
Données à calculer
1. La valeur principale — pourcentage entre 0 et 100, 1 décimale
| Élément |
Détail |
| Numérateur |
Nombre de déclarations soumises pour l'année. Source : table declarations, filtrée sur status = 'submitted' et year = {année sélectionnée}. |
| Dénominateur |
Nombre d'entreprises légalement obligées de déclarer cette année-là. Source : table gipMdsData (fichier annuel fourni par le GIP-MDS, l'organisme qui pré-calcule les indicateurs à partir des DSN), filtrée sur year = {année sélectionnée}, puis restreinte aux entreprises obligées. |
| Règle d'obligation |
Une fonction isObligatedForYear(size, year) à créer dans ~/modules/domain. Règles métier : moins de 50 salariés → pas obligé (déclaration volontaire) ; 50 à 99 → obligé une année sur trois (triennal) ; 100 et plus → obligé tous les ans. |
| Calcul |
(numérateur / dénominateur) × 100 |
2. La variation vs année précédente — badge DSFR
| Élément |
Détail |
| Origine |
Même calcul que la valeur principale, mais sur year - 1. |
| Calcul |
valeur(année) - valeur(année - 1), exprimée en points de pourcentage (pas en %). |
| Affichage |
Badge DSFR avec flèche : ↑ +2,1 pts en vert (fr-badge--success) si positif, ↓ -2,1 pts en rouge (fr-badge--error) si négatif, = 0 pt neutre si nul. Si pas d'historique pour year-1 → on n'affiche pas le badge. |
3. Le sous-texte — valeurs absolues
| Élément |
Détail |
| Origine |
Numérateur et dénominateur déjà calculés au-dessus. |
| Affichage |
{numérateur} / {dénominateur} entreprises, avec espaces fines insécables pour les milliers (ex : 4 213 / 5 738 entreprises). |
Filtres qui font varier la tuile
| Filtre |
Composant |
Effet sur le calcul |
| Année |
<select> DSFR placé en barre de filtres en haut du dashboard (cette barre est mutualisée avec les autres tiles K8/K12, donc à intégrer une seule fois). Liste : année courante + 3 précédentes. |
Change la valeur {année sélectionnée} dans toutes les requêtes (numérateur, dénominateur, calcul de la variation N-1). |
| Tranche d'effectif |
<CompanySizeFilter> produit par la task #3212 (composant réutilisable). Valeurs : "Toutes tailles", "50-99", "100-149", "150-249", "250+". |
Le filtre s'applique à la fois au numérateur ET au dénominateur, pour que les deux périmètres restent cohérents (sinon le ratio n'a pas de sens). |
Procédure tRPC
À ajouter au router admin.ts existant (src/server/api/routers/admin.ts) — ne pas créer un nouveau router.
admin.getCampaignStats({
year: number,
sizeRange?: CompanySizeRange,
}) → {
totalObligated: number, // dénominateur (entreprises obligées)
totalSubmitted: number, // numérateur (déclarations soumises)
submissionRate: number, // pourcentage 0..100
previousYearRate: number | null // même calcul sur year-1, null si pas d'historique
}
Procédure protégée par adminProcedure (middleware déjà existant dans src/server/api/trpc.ts).
Livrables
Note perf
L'optimisation perf (mise en cache / vue matérialisée + cron de refresh) sera traitée dans une task devops dédiée, une fois les requêtes des KPIs stabilisées. À ce stade, la procédure tRPC requête directement les tables — pas de matérialisation.
Références
Contexte métier
Le taux de déclaration mesure le pourcentage d'entreprises qui ont envoyé leur déclaration d'égalité professionnelle (index égalité H/F) sur une année donnée, parmi celles qui y sont légalement obligées.
Objectif pour la DGT : suivre en temps réel l'avancement de la campagne annuelle.
À quoi ça ressemble à l'écran
Une tuile DSFR (
fr-tile) dans le dashboard admin. Elle affiche un gros pourcentage (la valeur de l'année en cours), et juste en dessous, en plus petit, l'écart par rapport à l'année précédente et les valeurs absolues qui ont servi au calcul.Le but : qu'un agent DGT ouvre la page et comprenne en 2 secondes "où on en est" sur la campagne, et "est-ce qu'on progresse vs l'an dernier".
Page d'accueil
Page
/admin/stats(à créer si elle n'existe pas). La tuile vit dans une grille de KPIs partagée avec d'autres tiles chiffrées (#3219 K8, plus tard K12).Données à calculer
1. La valeur principale — pourcentage entre 0 et 100, 1 décimale
declarations, filtrée surstatus = 'submitted'etyear = {année sélectionnée}.gipMdsData(fichier annuel fourni par le GIP-MDS, l'organisme qui pré-calcule les indicateurs à partir des DSN), filtrée suryear = {année sélectionnée}, puis restreinte aux entreprises obligées.isObligatedForYear(size, year)à créer dans~/modules/domain. Règles métier : moins de 50 salariés → pas obligé (déclaration volontaire) ; 50 à 99 → obligé une année sur trois (triennal) ; 100 et plus → obligé tous les ans.(numérateur / dénominateur) × 1002. La variation vs année précédente — badge DSFR
year - 1.valeur(année) - valeur(année - 1), exprimée en points de pourcentage (pas en %).↑ +2,1 ptsen vert (fr-badge--success) si positif,↓ -2,1 ptsen rouge (fr-badge--error) si négatif,= 0 ptneutre si nul. Si pas d'historique pour year-1 → on n'affiche pas le badge.3. Le sous-texte — valeurs absolues
{numérateur} / {dénominateur} entreprises, avec espaces fines insécables pour les milliers (ex :4 213 / 5 738 entreprises).Filtres qui font varier la tuile
<select>DSFR placé en barre de filtres en haut du dashboard (cette barre est mutualisée avec les autres tiles K8/K12, donc à intégrer une seule fois). Liste : année courante + 3 précédentes.{année sélectionnée}dans toutes les requêtes (numérateur, dénominateur, calcul de la variation N-1).<CompanySizeFilter>produit par la task #3212 (composant réutilisable). Valeurs : "Toutes tailles", "50-99", "100-149", "150-249", "250+".Procédure tRPC
À ajouter au router
admin.tsexistant (src/server/api/routers/admin.ts) — ne pas créer un nouveau router.Procédure protégée par
adminProcedure(middleware déjà existant danssrc/server/api/trpc.ts).Livrables
isObligatedForYeardans~/modules/domain+ tests unitaires couvrant les 3 règles (volontaire, triennal, annuel)admin.getCampaignStats+ tests<AdminKpiTile title value subtitle delta />à créer dans~/modules/admin/(réutilisable par stats(admin): K8 — Taux d'écart ≥ 5% #3219 K8 et plus tard K12)/admin/statsà créer si elle n'existe pas, avec la barre de filtres (année + effectif) et la tuile K1Note perf
L'optimisation perf (mise en cache / vue matérialisée + cron de refresh) sera traitée dans une task devops dédiée, une fois les requêtes des KPIs stabilisées. À ce stade, la procédure tRPC requête directement les tables — pas de matérialisation.
Références
<AdminKpiTile>: stats(admin): K8 — Taux d'écart ≥ 5% #3219 (K8 — Taux d'écart ≥ 5 %), plus tard K12