# Rapport d'exécution — v3.35.0 (les 19 familles alignées + la porte de re-classification du stock)

**Deux volets courts. UNE migration, additive et idempotente. Aucune dépendance composer. Aucun
écran modifié. Se livre aux deux installations, à l'identique — tient sur une base vide (RUWAD :
la migration amorce, l'endpoint répond `ao: []`).**

Le problème que ce lot résout : la moissonneuse v3.56 unifie ses **deux** classifieurs (objet →
`famille_ia`, lignes de bordereau → `familles_ia`) sur **une seule taxonomie de dix-neuf familles**.
L'app valide ce qu'elle reçoit contre la table `familles_objet` et **rejette en silence** un slug
qu'elle ne connaît pas — la fiche retombe alors sur le classement par mots-clés, sans un mot dans
les logs. Les slugs divergents ont été corrigés **côté moissonneuse** (elle émet désormais
exactement les slugs de l'app, qui sont l'identifiant stable référencé par les préréglages de
filtres et les familles prioritaires). Restaient **cinq familles neuves** que la table ignorait :
sans elles, la classification IA de ces marchés se perdait, elle aussi, en silence.

---

## Volet A — les dix-neuf familles alignées

### A1 — Cinq familles neuves, posées par le VPS UNIQUEMENT

Migration `2026_09_05_100000_amorcer_familles_services_2026.php`, calquée sur
`2026_09_04_100000_amorcer_famille_travaux_installation.php` : `Schema::hasTable` en garde,
`insertOrIgnore` sur le slug unique, `FamillesObjetService::oublierCache()` en fin de course,
`down()` no-op (le retour arrière du socle est la restauration de sauvegarde, jamais un rollback
destructeur).

| slug | nom | couleur | priorité | sort |
|---|---|---|---|---|
| `restauration-alimentation` | Restauration & alimentation | `red` | 150 | 140 |
| `transport-logistique` | Transport & logistique | `gray` | 155 | 145 |
| `gardiennage-securite` | Gardiennage & sécurité | `stone` | 160 | 150 |
| `nettoyage-proprete` | Nettoyage & propreté | `green` | 165 | 155 |
| `accueil-interim` | Accueil & intérim | `lime` | 170 | 160 |

**`mots_inclus` VIDE, `mots_exclus` vide** — exactement comme `travaux-installation`, et ce n'est
pas négociable : « transport » désigne le plus souvent un ACHAT de titres (Fournitures) ou une
plateforme (Informatique), « nettoyage » attrape des « espaces verts » qui sont des travaux.
Deviner ces familles d'un objet par mots-clés rejouerait précisément les pièges qu'on vient
d'éliminer côté IA. Elles ne sont posées **que** par le VPS, qui lit l'objet entier. Le gérant
pourra toujours leur régler des mots-clés au CRUD s'il le souhaite ; les priorités, entre les
métiers (≤ 110) et `travaux-installation` / `fournitures-equipements`, ne servent donc que leur
rang d'affichage. Les couleurs sont indicatives — des noms de couleur Filament, jamais un hex, pour
que le badge suive le thème clair/sombre.

### A2 — Deux renommages de NOM (le slug ne bouge pas)

Dans la même migration, deux `update` **conditionnés sur l'ancien nom** — un nom déjà personnalisé
par le gérant n'est jamais réécrit :

- `formation` : « Formation » → **« Formation & éducation »** ;
- `etude` : « Conseil & Étude » → **« Conseil & audit »**.

Les slugs restent `formation` et `etude` : c'est ce que la moissonneuse émet et ce que les
réglages (`ao.familles_prioritaires`, préréglages de filtres v3.28) référencent.

### A3 — Le filet

`TenderProjectionService::LIBELLES_FAMILLES` aligne ses deux libellés sur les nouveaux noms, pour
qu'une base pas encore migrée affiche la même chose. Les cinq nouveaux slugs **ne rejoignent pas**
la constante keyword `FAMILLES_OBJET` ni les libellés du filet : comme `travaux-installation`,
elles vivent dans la table seule, jamais dans le repli mots-clés.

## Volet B — `GET /api/veille/a-reclasser`

Contrôleur invokable `VeilleAReclasserController`, calqué sur `VeilleAApprofondirController` :
même trait `JetonDeMoissonneuse`, **fermé par défaut** (503 sans jeton configuré, 401 sur mauvais
jeton), même throttle `veille-ingest`, **aucune écriture** — c'est une lecture.

Le mode `--reclasser` de la moissonneuse v3.56 rejoue la classification sur le stock **sans
retélécharger un seul DCE** : il relit l'objet et la `fiche_ia` déjà stockés ici, reclasse, et
repose le tout par la porte d'ingestion existante (qui accepte déjà `famille_ia`, `familles_ia`,
`fiche_ia`). Il lui manquait seulement de quoi LIRE, page par page.

- **Sélection** : toutes les fiches portant une `fiche_ia` (`ai_analysis->fiche_ia` non nul),
  **quelle que soit la participation** — veille, soumission, parcours : même niveau d'information
  depuis la v3.32. Les fiches sans `fiche_ia` ne sont pas ici (rien à reclasser tant qu'un dossier
  n'a pas été lu — elles passent par `a-approfondir`).
- **Ordre** : `id` croissant. **Pagination** : `apres` (entier, défaut 0) ne rend que les `id >
  apres` ; la moissonneuse rappelle avec le plus grand `tender_id` reçu, jusqu'à réponse vide.
- **`limite`** : défaut 50, plafond 50.
- **Pas de dédoublonnage par ref**, contrairement à ses aînées : la moissonneuse repose par ref et
  la propagation aux jumelles est idempotente.
- **`fiche_ia` rendue ENTIÈRE**, telle que stockée, ni triée ni amputée. C'est le point capital :
  la moissonneuse la repose telle quelle avec seulement les familles de lignes corrigées — n'en
  rendre qu'une partie (le bordereau seul, par exemple) ferait perdre le reste (résumé, mode de
  passation, cautions…) à la réécriture.

---

## Recette

14 tests neufs (`tests/Feature/Veille/FamillesUnifieesV335Test.php`) :

1. les cinq slugs existent, `actif = true`, `mots_inclus = []`, couleur Filament valide ;
2. « titres de transport » et « espaces verts » ne tombent PAS dans les familles neuves ;
3. les deux noms sont renommés, les slugs `formation` / `etude` classent toujours ;
4. rejeu de la migration : ni la famille retouchée ni le nom personnalisé ne sont réécrits ;
5. sans la table, la migration ne plante pas ;
6. **le correctif** : `famille_ia = 'restauration-alimentation'` est ACCEPTÉE par
   `familleAfficheeDeFiche()` — plus de repli silencieux sur les mots-clés ;
7. idem pour le slug renommé stable `etude` ;
8. la file sans jeton → 503, avec un mauvais jeton → 401 ;
9. seules les fiches à `fiche_ia` sont listées, dans l'ordre des `id` ;
10. toutes les participations sont servies, pas seulement la veille ;
11. `apres` pagine, `limite` est plafonnée à 50 ;
12. la `fiche_ia` revient entière (résumé, mode de passation, caution, bordereau complet) ;
13. la file n'écrit rien.

Deux tests existants suivent le lot : `FamillesObjetTest` compte désormais 18 familles,
`FrictionsUiTest` attend le badge « Formation & éducation ».

Suite complète verte, Pint sans écart.

## À faire après déploiement

Rien à configurer. La migration s'applique seule et amorce les cinq familles (base vide RUWAD
comprise). Pour lancer la passe, la moissonneuse appelle
`GET /api/veille/a-reclasser?limite=50&apres=<dernier tender_id>` en boucle jusqu'à réponse vide,
et repose chaque fiche par `POST /api/veille/ingest`. Les couleurs et les mots-clés des cinq
familles neuves restent réglables par le gérant dans **Réglages → Familles d'objet**.
