Suite revue adversariale MERGE-READY (« non-bloquant ≠ oublié », Dom) :
1. Contrat crop CORROMPU figé par test : `materialize_anchor_from_b64` retire la
clé `_anchor_image_base64` de params AVANT le try (pop). Un crop inexploitable
(b64 invalide, PNG au magic cassé) → pas d'exception, anchor_id NULL, aucun
VisualAnchor, ET clé retirée (pas de donnée morte volumineuse dans les params).
3 cas paramétrés verrouillent ce comportement assumé. Docstring clarifiée.
2. `workflow_id` réintroduit dans le sidecar metadata.json (parité avec la route
d'origine avant refactor). Nouveau paramètre optionnel du helper, passé par les
deux appelants live (route HTTP + bridge R1). 2 tests : présent quand fourni,
absent sinon (pas de clé None parasite). Backfill inchangé (workflow_id optionnel).
TDD : +5 tests. 29 tests unit VERTS, 0 régression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Les steps needsAnchor des workflows importés par le pont R1
(import_core_workflow_to_db) affichaient « Ancre requise » car R1 laissait
_anchor_image_base64 dans params sans créer le VisualAnchor ni poser anchor_id.
La route HTTP import_learned_workflow faisait ce travail mais en dupliquait ~50
lignes inline.
Fix GÉNÉRAL et réutilisable (mandat Dom), pas une rustine click_anchor :
- helper unique services/anchor_image_service.materialize_anchor_from_b64 :
crée VisualAnchor + fichier PNG + pose step.anchor_id + retire le b64 de params ;
ne commit pas (transaction gérée par l'appelant) ; tolérant (crop invalide →
anchor_id NULL, pas de crash) ; générique pour TOUS les action_type needsAnchor.
- branchement dans import_core_workflow_to_db (flux R1) ;
- route HTTP refactorée pour réutiliser le même helper (dé-duplication réelle,
-48 lignes) → source unique de vérité.
- source canonique needsAnchor côté backend : services/anchor_action_types.py
(miroir de frontend_v4/src/types.ts, garde-fou anti-divergence testé).
- script tools/backfill_anchors_r1.py (--dry-run par défaut / --apply) : filtre
source=learned_import ET action_type ∈ NEEDS_ANCHOR ET anchor_id NULL ET crop
présent ; DB résolue depuis la config runtime VWB (pas en dur) ; dry-run
affiche le chemin DB résolu + estime PNG/disque ; idempotent. NON exécuté en
--apply (backup + arbitrage Dom requis avant, jamais la DB clinique DGX).
TDD : tests rouges d'abord puis verts. 20 tests neufs (matérialisation 6 types
d'ancre, b64 retiré, sans-crop→NULL, R1 clic, ré-import idempotent, dé-dup route,
backfill apply/idempotent/dry-run/filtre manual+hors-needsAnchor, canonique). 24
tests unit VERTS, 0 régression (test_import_core_workflow_to_db intact).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add import_core_workflow_to_db() — create-or-update par signature de
trajectoire (décision produit Dom 23/06). Les workflows source='manual'
sont exclus du filtre de fusion. Inclut test TDD idempotent (ré-import
2× → toujours 1 seul workflow).
ExtractionJob -> ExtractedTable -> ExtractedField (SQLAlchemy, cascade), avec
preuve par cellule (bbox + confidence) réutilisant la sémantique VWBEvidence,
et statut dossier needs_review|complete. Brique 2 de la verticale extraction.
Documenté : ce canal conserve les données patient EN CLAIR (≠ canal
apprentissage anonymisé) — aucune anonymisation ne doit cibler ces colonnes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>