# Plan de remise au carré — chaîne d'apprentissage & rejeu de Léa - `Date`: 2026-06-27 - `Auteur`: Claude (mandat Dom) - `Statut`: actif — formalise l'analyse du 27/06 ; **n'invente rien**, chapeaute `PLAN_ACTION_SUITE_2026-06-23` (axe « Rejeu intelligent ») et `CARTO_APPRENTISSAGE_FONDS_COMMUN_2026-06-16`. - `Contrainte cardinale (Dom 27/06)`: **câbler la chaîne ET rendre Léa correcte AVANT la dernière manip manuelle de Dom** (= avant la perte de l'accès hands-on / on-site). Cette contrainte est la **barre d'acceptation** de ce plan. *(À préciser : quoi exactement, et quelle date.)* --- ## 1. Pourquoi ça ne marche pas (vérifié le 27/06) **En une phrase : la chaîne a été diagnostiquée (16/06), décidée (23/06) et planifiée (R1→R7), puis la livraison clinique a absorbé les 2 semaines — le câblage end-to-end n'a jamais démarré. Le blocage est d'EXÉCUTION, pas de décision.** Faits vérifiés (grep/fichier, ce jour) — la chaîne n'est pas câblée au runtime : | Maillon | Preuve vérifiée 27/06 | Effet | |---|---|---| | Worker d'enrichissement | `data/training/_worker_queue.txt` = **0 octet, mtime 11/06** | enrichissement **à l'arrêt depuis 16 j** | | Import auto session→workflow | `ShadowLearningHook()` instancié **uniquement dans son propre fichier**, jamais dans `finalize`/runtime | l'appris **n'est pas** rendu rejouable sans geste manuel | | Lecture du fonds au rejeu | `GlobalFAISSIndex.search()` → **grep = 0 appel** ; `TargetResolver.lookup()` jamais appelé par `replay-session` | rejeu = **coords figées**, pas cible apprise | | Dé-silo | filtre `machine_id` à **5 points** de `stream_processor.py` (3199, 3285, 4499, 4530, 5062) | apprentissage **siloté par poste** | Causes racines (au-delà du « c'est débranché ») : 1. **Les 2 semaines = infra de survie** (portage DGX ARM, installateur EXE, réseau/firewall/VPN, reprise panne secteur + reboot, watchdog OVMF, enrôlement, streaming, push-log). Indispensable pour livrer — mais **0 h sur le câblage de la boucle**. 2. **Découpage par composant, jamais par boucle.** Hook, FAISS, TargetMemory bâtis et validés isolément (sessions/agents différents). **Personne n'a possédé ni testé la boucle entière sur une vraie session** → trous accumulés à chaque soudure, invisibles. 3. **Le raccourci démo est devenu permanent.** Le rejeu `Urgence_aiva` marchait en coords figées + templating `{{var}}` (seule pièce câblée). « Ça marchait » → le chemin intelligent n'a **jamais été allumé**. ⚠️ **À confirmer en amont (audit runtime Qwen, NON vérifié indépendamment)** : « **11/15 postes heartbeat-only, 0 % résolution vision/OCR/anchors** ». Si avéré, le premier problème n'est pas l'apprentissage mais que **les postes ne font pas encore le geste du POC** (pas de vraie capture, cascade vision non exercée). → **à prouver, chiffré, AVANT tout recâblage** (Phase 0). --- ## 1bis. CARTE DE CÂBLAGE VÉRIFIÉE (28/06 — 3 agents read-only, sourcé code) > Cette section **corrige** le §1 sur deux points (diagnostic affiné, moins grave qu'annoncé). **Deux corrections vérifiées (mes affirmations antérieures étaient fausses) :** 1. **« sessions → squelettes sans action » = FAUX.** Les actions (clics/saisies) sont attachées au workflow sur les **edges** (`WorkflowEdge.action`, `graph_builder.py:1457`), pas sur `node.variants` (qui = variantes *visuelles* d'écran, champ non peuplé au runtime). 48/71 workflows auto-appris portent leurs actions ; 23 sont vides (sessions trop courtes). 2. **« rejeu = coords figées » = FAUX.** Léa **résout chaque cible par la vue** à chaque rejeu (cascade OCR→template→YOLO→VLM sur anchors, `resolve_engine.py:1804`). Coords = fallback ultime seulement. Conforme 100 % vision. **Ce qui MARCHE** : capture→workflow avec actions ; worker traite ; 36/71 atteignent `AUTO_CANDIDATE` ; rejeu visuel (VWB-DB 226 `click_anchor` + JSON auto-appris non vides) ; **R2 à moitié branché** (`TargetMemoryStore` consulté en tête de `_resolve_target_sync`, `resolve_engine.py:1862`). **Les 4 vrais trous (sourcés) :** | # | Trou | Preuve | Type | |---|---|---|---| | **1 (P0)** | **11/15 postes n'enregistrent rien** | démarrage capture 100 % manuel (clic TIM « Apprenez-moi », `smart_tray.py:349` / `chat_window.py:1716`) ; heartbeats auto (`main.py:378`). Risque : dialogue consentement `Tk()` (`smart_tray.py:54`) invisible en RDP/Citrix `pythonw` | **amont / UX (à confirmer bug vs usage par logs client)** | | 2 | **Apprentissage incrémental débranché** | `LearningManager` non instancié serveur ; mute `WorkflowStats` mémoire non re-persisté ; `record_observation` (`learning_manager.py:54`) **0 appelant**. Seul `GraphBuilder` écrit `learning_state`, fige sur OBSERVATION si qualité faible (`graph_builder.py:400`) | promotion jamais déclenchée | | 3 | **2 mondes disjoints** : JSON auto-appris ≠ DB VWB rejouable | stores/loaders/matchers séparés ; une session apprise ne devient pas un workflow DB rejouable | = **R1** (pont JSON→DB) | | 4 | **Fonds commun jamais lu au rejeu** | `GlobalFAISSIndex.search()` = 0 appel (seul `add_pack` écrit) | = **R3** (FAISS au rejeu) | **Points d'insertion confirmés** : R1 = worker `_process_session` après `_persist_workflow` (réutiliser `import_learned_workflow`/`learned_workflow_bridge`, idempotence par `workflow_trajectory_signature` existante). R2/R3 = `resolve_engine.py:1862-1878` (élargir `memory_lookup` + insérer `GlobalFAISSIndex.search()`). **Priorité (contrainte « Léa correcte avant dernière manip ») : #1 (amont) d'abord** — si 11/15 postes ne capturent pas, l'aval est sans objet. Test décisif = grep logs client `"Session … en cours"` vs `"Session finalisée"` (Qwen). --- ## 2. État des décisions (rappel — la plupart sont déjà prises) Tranchées le 23/06 (`DECISIONS_PRODUIT_EN_ATTENTE_2026-06-23.md`) → **on exécute, on ne re-décide pas** : - **F2-1/F14-1** : rejeu intelligent = **OUI, prérequis** (consulter le fonds appris, pas de coords figées). - **F1-1** : critère de fusion = **signature de trajectoire** (create-or-update). - **F9-1** : **DB = vérité**, JSON = échange ; métrique = workflows rejouables validés. - **F6-1** : mutualisation **cross + intra-clinique** (fédération anonymisée dans le périmètre + lever silo `machine_id`). **Reste ouvert (1 seule, vraie décision) :** - **Q-F2-2 — Provider Léa au runtime** : quel modèle/route sert la **résolution** au rejeu. ⚠️ Gap tracé 23/06 : le point d'entrée actif = **agent_chat 5004 → `SemanticMatcher.find_workflow()` sur fichiers JSON**, pas la DB → **contredit F9-1**. Se résout **en chemin** à la Phase 2 (où la résolution est recâblée). Reco modèle : Qwen3-VL-4B grounder + gemma4 (bench 13/06). **Décisions potentiellement induites par la Phase 0** : si « 0 % vision » confirmé, une décision « comment forcer/garantir la capture vision réelle sur poste » surgira (priorité absolue, avant R1). --- ## 3. Plan d'exécution (séquencé, chirurgie supervisée) > Les chantiers R1→R7 détaillés sont dans `PLAN_ACTION_SUITE_2026-06-23` (§ Axe central) — **non dupliqués ici**. Ce plan ajoute la **Phase 0 de mesure** (nouvelle) et l'**ordre/critères**. **Phase 0 — MESURER (avant tout recâblage).** Établir la vérité terrain : par poste, nb de **vraies sessions**, la **cascade vision est-elle déclenchée** (compteur de résolutions par méthode), captures reçues, état queue. **C'est ce que push-log + une télémétrie vision apportent** (lien direct avec briques 1-4 livrées). → *Mission Qwen (accès runtime DGX).* **Critère de sortie : on sait, chiffré, ce que font les 15 postes.** **Phase 1 — RECONNECTER L'AMONT (R1).** Import auto session→workflow post-`finalize` + **relancer le worker** (queue morte 11/06). *Critère : une session TIM réelle devient un workflow rejouable sans geste manuel.* **Phase 2 — RECONNECTER L'AVAL (R2+R3) + résoudre Q-F2-2.** Câbler `TargetResolver.lookup()` + lecture FAISS au rejeu, **fallback obligatoire sur coords figées** (non négociable Qwen — enrichir, pas casser) ; aligner le point d'entrée résolution sur la DB (F9-1). *Critère : Léa résout par cible apprise, retombe sur coords si échec.* **Phase 3 — BOUCLE + DÉ-SILO (R4/R5/R6).** verify post-condition (échec → pause supervisée), réécriture du fonds, lever silo `machine_id` + brancher fédération (`GlobalFAISSIndex.search()`). --- ## 4. Gouvernance (corrige la cause racine #2) - **Un seul propriétaire de la boucle entière** (pas un découpage par composant). - **Critère d'acceptation = un test end-to-end sur une session réelle**, pas une validation par brique. - Chirurgie itérative supervisée : un maillon = un test ≤ 2 min = GO Dom ; démo `Urgence_aiva` intacte à chaque étape ; reconfirmer le wiring runtime avant chaque modif (imports lazy = verdicts « orphelin » non fiables). - **Merge prod supervisé Dom.** ## 5. Lien avec la « dernière manip manuelle » (deadline) La contrainte de Dom fait de ce plan un **chemin critique** : tant que Léa n'est pas correcte (au moins Phase 0 + Phase 1-2 sur 1 poste pilote), **la dernière manip manuelle ne doit pas avoir lieu** — sinon plus d'accès hands-on pour réparer. → **Définir avec Dom : quelle est cette manip, et sa date butoir**, pour caler le séquencement. ## 6. Première action concrète **Phase 0 confiée à Qwen** (chiffres runtime). Doc + page décisions = ce fichier. Reste : GO Dom sur le séquencement + définition de la deadline « dernière manip ». --- *Plans sources (ne pas dupliquer) : `PLAN_ACTION_SUITE_2026-06-23`, `CARTO_APPRENTISSAGE_FONDS_COMMUN_2026-06-16`, `DECISIONS_PRODUIT_EN_ATTENTE_2026-06-23`, `PLAN_CHANTIER_UNIFICATION_LEA_VWB_2026-06-17`.*