Commit Graph
3 Commits
Author SHA1 Message Date
Johan LEROY aeb07e14db feat(backend): moteur de règles de recommandations et route de génération
`recommendation` n'avait aucun écrivain : les quatre couches de lecture étaient
livrées, mais rien ne produisait de ligne. Le moteur comble ce trou.

Le catalogue `REGLES` vit dans `app/services/`, pas dans `ml/` : il lit `alert.type`,
`alert.severity`, `alert.value` et `alert.threshold`, sans modèle ni feature, et
s'appuie sur deux repositories existants. L'arbitrage avec l'ADR 0005, qui annonçait
#38 du côté ML, est tranché par l'ADR 0006.

Sept règles, cinq par type d'alerte et deux transverses (sévérité critique,
dépassement d'au moins 20 % du seuil), donc une à trois recommandations par alerte.
L'idempotence est portée par la base : `create_missing()` insère en
`ON CONFLICT DO NOTHING` sur `uq_recommendation_alert_rule`, ce qui supprime la
fenêtre entre un contrôle préalable et l'insertion. `rule_reference` devient de ce
fait une clé fonctionnelle, d'où le suffixe de version sur chaque référence.

Deux déclencheurs : `POST /api/v1/recommendations/generate` réservé `admin`, et
`python -m app.cli generate-recommendations` (cible `make recommendations`).

Limite connue : aucune source n'alimente `alert` aujourd'hui, ni détection interne
(#104) ni ingestion de l'API Mock. La route répond, le rapport reste à zéro, et la
chaîne s'allume sans retoucher le moteur le jour où les alertes existent.

Tests : 80 unitaires et API verts, plus 6 d'intégration dont l'idempotence jouée
contre PostgreSQL.

Closes #38
2026-09-18 15:49:54 +02:00
Dorian 460d6c1b3e Merge remote-tracking branch 'origin/dev' into feat/service-de-scoring 2026-09-18 15:06:41 +02:00
Johan LEROY 7eef960a30 test(backend): classe les routes du contrat et dérive les listes d'autorisation
`ROUTES_A_ROLE` était recopiée dans `test_openapi.py`, et deux de ses entrées portaient
`{id}` là où le contrat expose `{user_id}`. Elles ne correspondaient donc à aucune
opération, et `test_every_role_guarded_route_documents_the_role_refusal` passait au vert
sans rien vérifier sur `PATCH /users/{user_id}` ni sur sa réinitialisation de mot de passe :
11 des 13 routes gardées étaient réellement couvertes.

`tests/api/acces.py` porte désormais la classification des 24 routes du contrat en quatre
ensembles, dont la table `ROLE_MINIMUM`, et `test_every_declared_route_is_classified` refuse
aussi bien une route non classée qu'une entrée qui ne correspond plus à rien. C'est ce que
`docs/architecture/20-backend.md` annonçait comme impossible : « ces deux listes sont
maintenues à la main, pas dérivées ».

Au passage, `chemin_concret()` substitue les trois gabarits du contrat et non plus le seul
`{user_id}`, ce qui est sans effet sur le refus anonyme mais nécessaire à un appel qui doit
aboutir.
2026-09-18 12:08:59 +02:00