"""Couplage worker → DB VWB (mutualisé) + persistance « dossier patient extrait ». Le worker/serveur streaming est un process distinct du backend VWB : il n'a pas d'app Flask en mémoire. Ce module fournit : - ``vwb_app_context()`` : un app-context Flask lazy (singleton module) lié au fichier SQLite VWB ``visual_workflow_builder/backend/instance/workflows.db``, avec ``db.init_app`` (db de ``db.models``). Réutilisable par tout module serveur qui doit écrire dans la DB VWB (R1, extraction métier, …). - ``persist_extracted_dossier(...)`` : depuis une grille OCR (``List[List[cell]]``), crée ExtractionJob → ExtractedTable → ExtractedField et commit. Suppose un app-context actif (comme le pont R1 existant). ⚠️ CANAL EXTRACTION = données patient EN CLAIR (volontaire) : aucune tokenisation/assainissement PII ici (cf. note dans db/models.py). """ import sys import uuid from contextlib import contextmanager from pathlib import Path from typing import Any, Dict, List, Optional # Ajout du backend VWB au sys.path à l'import → rend ``db.models`` importable # (couplage worker→DB VWB mutualisé ; identique au pattern stream_processor). _VWB_BACKEND = Path(__file__).resolve().parents[2] / "visual_workflow_builder" / "backend" if str(_VWB_BACKEND) not in sys.path: sys.path.insert(0, str(_VWB_BACKEND)) # App Flask lazy (singleton module) — un seul db.init_app pour tout le process. _vwb_app = None @contextmanager def vwb_app_context(): """App-context Flask VWB (lazy singleton) sur instance/workflows.db. À utiliser via ``with vwb_app_context(): ...`` autour des appels qui nécessitent ``db.session`` (ex. persist_extracted_dossier). """ global _vwb_app if _vwb_app is None: from flask import Flask from db.models import db db_path = _VWB_BACKEND / "instance" / "workflows.db" app = Flask("worker_vwb") app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False db.init_app(app) _vwb_app = app with _vwb_app.app_context(): yield def persist_extracted_dossier( grid: List[List[Dict[str, Any]]], *, patient_ref: Optional[str], source_session_id: Optional[str], screenshot_ref: Optional[str], screen_bbox: Optional[Dict[str, Any]], status: str, ) -> str: """Persiste un « dossier patient extrait » et retourne le job_id. Crée 1 ExtractionJob → 1 ExtractedTable → N ExtractedField (une par cellule de la grille), puis commit. Suppose un app-context VWB actif (fourni par ``vwb_app_context()`` ou par l'appelant, comme le pont R1). ⚠️ ``patient_ref`` et ``cell["text"]`` sont stockés EN CLAIR (volontaire) : le but est de constituer le dossier, pas d'anonymiser. """ from db.models import db, ExtractionJob, ExtractedTable, ExtractedField job = ExtractionJob( id=uuid.uuid4().hex, patient_ref=patient_ref, source_session_id=source_session_id, status=status, ) db.session.add(job) table = ExtractedTable( id=uuid.uuid4().hex, job_id=job.id, screen_bbox=screen_bbox, screenshot_ref=screenshot_ref, ) db.session.add(table) for row in grid or []: for cell in row or []: db.session.add(ExtractedField( id=uuid.uuid4().hex, table_id=table.id, row=cell.get("row"), col=cell.get("col"), value=cell.get("text"), bbox=cell.get("bbox"), confidence=cell.get("confidence"), )) db.session.commit() return job.id