feat(extraction): modèle DB dossier patient extrait (Job/Table/Field)
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>
This commit is contained in:
@@ -321,6 +321,70 @@ class ExecutionStep(db.Model):
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Extraction — « dossier patient extrait » (brique 2)
|
||||
#
|
||||
# ⚠️ CANAL EXTRACTION ≠ canal apprentissage. Ces tables conservent les
|
||||
# VRAIES données patient (patient_ref, ExtractedField.value) : c'est le but,
|
||||
# constituer le dossier. Elles NE doivent PAS être anonymisées/tokenisées
|
||||
# (à l'inverse du canal apprentissage, cf. pii_sanitizer). Aucun appel
|
||||
# d'assainissement PII ne doit cibler ces colonnes.
|
||||
#
|
||||
# Sémantique de preuve réutilisée de contracts/evidence.py (VWBEvidence) :
|
||||
# screenshot_ref ≈ screenshot, screen_bbox/bbox ≈ highlight_box, confidence
|
||||
# ≈ confidence_score, created_at ≈ timestamp.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ExtractionJob(db.Model):
|
||||
"""Dossier patient extrait — racine d'une session d'extraction."""
|
||||
__tablename__ = 'extraction_jobs'
|
||||
|
||||
id = db.Column(db.String(64), primary_key=True)
|
||||
patient_ref = db.Column(db.String(255), nullable=True) # donnée patient EN CLAIR (volontaire)
|
||||
source_session_id = db.Column(db.String(64), nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
# status: 'needs_review' (revue humaine requise) | 'complete' (validé)
|
||||
status = db.Column(db.String(32), default='needs_review')
|
||||
|
||||
tables = db.relationship('ExtractedTable', backref='job', lazy='dynamic',
|
||||
cascade='all, delete-orphan')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ExtractionJob {self.id}: {self.status}>'
|
||||
|
||||
|
||||
class ExtractedTable(db.Model):
|
||||
"""Tableau extrait d'un écran (preuve : screenshot_ref + screen_bbox)."""
|
||||
__tablename__ = 'extracted_tables'
|
||||
|
||||
id = db.Column(db.String(64), primary_key=True)
|
||||
job_id = db.Column(db.String(64), db.ForeignKey('extraction_jobs.id'), nullable=False)
|
||||
screen_bbox = db.Column(db.JSON, nullable=True) # {x, y, width, height}
|
||||
screenshot_ref = db.Column(db.String(512), nullable=True)
|
||||
|
||||
fields = db.relationship('ExtractedField', backref='table', lazy='dynamic',
|
||||
cascade='all, delete-orphan')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ExtractedTable {self.id}>'
|
||||
|
||||
|
||||
class ExtractedField(db.Model):
|
||||
"""Cellule extraite (donnée patient EN CLAIR) + preuve bbox/confidence."""
|
||||
__tablename__ = 'extracted_fields'
|
||||
|
||||
id = db.Column(db.String(64), primary_key=True)
|
||||
table_id = db.Column(db.String(64), db.ForeignKey('extracted_tables.id'), nullable=False)
|
||||
row = db.Column(db.Integer, nullable=True)
|
||||
col = db.Column(db.Integer, nullable=True)
|
||||
value = db.Column(db.Text, nullable=True) # valeur patient EN CLAIR (volontaire)
|
||||
bbox = db.Column(db.JSON, nullable=True) # {x, y, width, height}
|
||||
confidence = db.Column(db.Float, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ExtractedField {self.id}: r{self.row}c{self.col}>'
|
||||
|
||||
|
||||
# Session active (en mémoire, pas en DB)
|
||||
class SessionState:
|
||||
"""État de la session utilisateur (en mémoire)"""
|
||||
|
||||
Reference in New Issue
Block a user