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>