# Rapport d'exécution — v3.34.0 (rouvrir la file quand la moissonneuse progresse, + le lieu d'ouverture)

**Trois volets courts, sans dépendance l'un envers l'autre. Aucune migration, aucune dépendance
composer, aucun changement d'écran hors UN champ ajouté à la grille de la fiche AO. Se livre aux
deux installations, à l'identique — tient sur une base vide.**

Le problème que ce lot résout : `GET /api/veille/a-approfondir` ne sert que les fiches **sans**
`fiche_ia`. Les ~500 fiches déjà lues par une moissonneuse antérieure étaient donc sorties de la
file **définitivement** — elles gardaient leur photo pauvre pour toujours, quels que soient les
progrès de l'extraction. La v3.45 de la moissonneuse (bordereaux d'une seule ligne reconstruits,
traits de tableau collés au texte, trois champs de plus) rendait ce cul-de-sac coûteux : sur le
DCE 27/2026/DRAIRSK/BG, la relecture passe de 9 à 12 champs remplis. Il fallait pouvoir **rejouer
le stock à chaque progrès, sans rien effacer**.

---

## Volet A — la signature de version

### A1 — Le contrat d'ingestion

`POST /api/veille/ingest` accepte un champ de plus, que la moissonneuse **envoie déjà** depuis sa
v3.45 : `moissonneuse` (`nullable`, `string`, `max:20`). Comme tout le reste du contrat, il n'est
**jamais bloquant** : une moissonneuse plus ancienne qui ne l'envoie pas est ingérée à l'identique,
et rien dans ce champ ne peut produire un 422 sur le reste de la livraison.

### A2 — Le stockage

Nouvelle constante `FicheIaService::CLE_VERSION` = **`moissonneuse_v`**, rangée dans `ai_analysis`
— **aucune migration**, c'est du JSON.

Elle est écrite **au même moment et aux mêmes conditions que la photo** : dans le même `if
($fiche !== [])` qui pose `fiche_ia` et `fiche_ia_at`. Conséquences voulues :

- une livraison dont la `fiche_ia` n'est pas retenue (absente, vide, malformée) **ne stampe rien** —
  la version estampille une photo, jamais une fiche ;
- une livraison d'une moissonneuse trop ancienne pour l'annoncer laisse la clé **où elle en était**,
  sans l'effacer ;
- la valeur est bornée à 20 caractères et assainie comme n'importe quel texte du contrat.

Deux points de cohérence sont tenus au-delà de la lettre du volet :

- la propagation **aux jumelles** (v3.32.2 §A3) transmet la signature avec la photo — deux fiches
  d'un même ref sortent ensemble de la file de rattrapage ;
- `VeilleImportService::CLES_IA` (le rapatriement d'un squelette fusionné) porte `CLE_VERSION` à
  côté de `CLE_FICHE` / `CLE_FICHE_AT` : une photo léguée sans sa version serait rouverte pour rien.

**Elle ne s'affiche nulle part.** C'est une donnée de machine.

## Volet B — `?version=` : rouvrir la file

`GET /api/veille/a-approfondir` accepte `version` (chaîne, `max:20`, optionnel ; hors contrat →
**422**, pas de troncature : servir la file d'une autre version que celle demandée serait pire que
ne rien servir).

### Sans `version` — rigoureusement inchangé

Le critère historique, mot pour mot : les fiches sans `fiche_ia`, l'ordre priorité (étoilés et
parcours) puis urgence. **Le cron des trois heures ne se met pas à rejouer le stock.** C'est prouvé
par un test de non-régression dédié.

### Avec `?version=3.45`

La file sert **aussi** les fiches qui ont une photo dont `ai_analysis->moissonneuse_v` est
**absent ou différent** de la valeur demandée. Comparaison de **chaîne stricte**, aucun ordre de
versions : « tout ce qui n'est pas déjà estampillé 3.45 ». Rien n'est effacé — la vieille photo
reste jusqu'à ce que la nouvelle la remplace, et une passe interrompue en pleine nuit n'a rien
détruit.

Le **dédoublonnage par `ref_consultation`** de la v3.32.2 tient, et son `whereNotExists` teste la
version lui aussi quand elle est fournie : une jumelle ne fait sortir le ref que si **sa** photo
porte la version demandée. Sans ce test, une jumelle bien lue masquerait sa sœur restée en vieille
lecture — ou l'inverse.

### L'ordre du rattrapage

Quand `version` est fourni, l'ordre change, et c'est délibéré : une passe de 500 dossiers dure une
nuit, donc **ce qui est encore soumissionnable est remis à jour en premier**.

1. les AO encore **ouverts** (date limite ≥ aujourd'hui), la plus proche en tête ;
2. les AO **passés**, du plus récent au plus ancien ;
3. les fiches **sans date**, en dernier.

L'étoile ne commande plus rien dans ce mode : c'est une passe de rattrapage, pas une file
d'attention. Le plafond `limite` est inchangé (60).

## Volet C — `lieu_ouverture`

La moissonneuse lit le lieu d'ouverture des plis sur la fiche portail (« Salle de réunions de la
Délégation… ») et l'envoie dans `fiche_ia.lieu_ouverture` depuis sa v3.43. Le contrat ne connaissait
pas le champ : il était **jeté en silence** à chaque livraison.

- `'lieu_ouverture' => 200` rejoint `FicheIaService::TEXTES` — mêmes règles que les autres textes :
  borné (le surplus est coupé), assaini, ignoré champ par champ s'il n'est pas une chaîne.
- Il s'affiche dans la fiche AO (`FicheAoService::champs()`) sous le libellé **« Lieu d'ouverture »**,
  **juste après « Ouverture des plis »**, avec « — » quand il est vide. La grille passe de 12 à
  13 cases ; elle est rendue par une boucle (`FicheAoHtml::grille()`), donc l'écran et la vue
  imprimable suivent sans autre changement. **Rien d'autre ne bouge dans la fiche** — la v3.32.1
  validée au pixel n'est pas touchée.

---

## Recette

11 tests neufs (`tests/Feature/Veille/RattrapageVersionV334Test.php`) :

1. ingest avec `moissonneuse: "3.45"` → `ai_analysis.moissonneuse_v` = `3.45` ;
2. ingest **sans** le champ → rien d'estampillé, aucune erreur ;
3. signature **sans** photo retenue → rien d'écrit ;
4. la file **sans** `version` ignore une fiche déjà photographiée (non-régression v3.32.2) ;
5. la file **avec** `?version=3.45` ressert `3.44` et le non-estampillé, jamais `3.45` ;
6. jumelles : le ref sort si l'une porte `3.45`, il est servi **une seule fois** sinon ;
7. l'ordre « ouverts d'abord », puis passés décroissants, sans-date en dernier ;
8. une `version` de 21 caractères → 422 ;
9. une livraison à la bonne version fait sortir le ref de la file de rattrapage ;
10. `lieu_ouverture` accepté, coupé à 200, affiché **juste après** « Ouverture des plis » ;
11. absent → « — ».

Suite complète verte, Pint sans écart.

## À faire après déploiement

Rien à configurer, rien à migrer. Pour lancer la passe de rattrapage, la moissonneuse appelle
`GET /api/veille/a-approfondir?version=3.45&limite=60` en boucle : la file se vide d'elle-même à
mesure que les livraisons estampillent les photos. Le cron des trois heures, lui, continue d'appeler
l'URL **sans** paramètre — il ne voit toujours que les fiches jamais lues.
