Skip to content

feat(annotations): traçabilité du modèle source sur annotations IA (source_model_id, source_confidence, source_tile_refs) #363

Description

@Yanstart

Contexte

Audit du modèle de données annotations (revue prod-grade, mai 2026) :
une annotation annotation_type IN ('auto', 'auto_confirmed') ne porte
aucune trace du modèle qui l'a produite. Seule la table corrections
mémorise model_name + model_version, et uniquement si une correction
ultérieure a eu lieu.

Conséquence : impossible de reconstituer le training set d'une prochaine
version du modèle (« annotations produites par ResNet50-v2.3 puis
validées par un pathologiste »). Bloque #346, #340, #341, #347.

Référence : docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md — gaps G1 + G10.

Objectif

Enregistrer sur chaque annotation produite par l'IA :

  1. Quel modèle l'a produite — FK vers ml_models (créé par feat(ml): foundation model registry + adapter (UNI, CONCH, Virchow, GigaPath, mSTAR) #346)
  2. Avec quel score brutsource_confidence, distinct de confidence (recalculable)
  3. Sur quelles tilessource_tile_refs JSONB structuré, pour explicabilité AI Act art. 12

Approche technique proposée

Migration 010_annotation_source_traceability.py

ALTER TABLE annotations
  ADD COLUMN source_model_id UUID REFERENCES ml_models(id) ON DELETE SET NULL,
  ADD COLUMN source_confidence FLOAT,
  ADD COLUMN source_tile_refs JSONB;

CREATE INDEX idx_annotations_source_model
  ON annotations(source_model_id)
  WHERE source_model_id IS NOT NULL;

Backfill best-effort depuis corrections quand un seul (model_name, model_version) existe pour une annotation.

Schéma Pydantic source_tile_refs

class TileRef(BaseModel):
    level: int = Field(ge=0)
    x: int = Field(ge=0)
    y: int = Field(ge=0)
    contribution: float | None = Field(None, ge=0.0, le=1.0)

class SourceTileRefs(BaseModel):
    tile_size: int
    tiles: list[TileRef]
    aggregation: Literal["mean", "max", "attention"] = "mean"

Modifications routes ML

  • routes/ml.py::predict : passer source_model_id à la matérialisation
  • routes/ml.py::detect : idem
  • routes/annotations.py::POST : si annotation_type IN ('auto', 'auto_confirmed'), source_model_id devient obligatoire (422 sinon)

Backward-compat

Annotations historiques sans source_model_id restent valides (NULL OK).
Endpoint admin GET /api/v1/admin/annotations/orphans liste ces orphelins.

Acceptance criteria (fonctionnel)

  • Migration Alembic 010_* créée, up + down testés
  • Modèle SQLAlchemy Annotation étendu avec les 3 colonnes
  • Schémas Pydantic TileRef, SourceTileRefs dans schemas/ml_source.py
  • Routes predict, detect, annotations POST rejettent les annotations IA sans source_model_id (HTTP 422 avec message explicite)
  • Endpoint admin GET /api/v1/admin/annotations/orphans (réservé ADMIN_TECHNIQUE)
  • Documentation docs/Admin/services/annotation-provenance.md

Review checklist (conditions de validation pour le reviewer PR)

Code

  • Tests unitaires couvrent les 3 routes modifiées + le nouvel endpoint admin (≥ 8 tests)
  • Tests d'intégration : pipeline predict → matérialiser → GET annotation retourne source_model_id non-null
  • Couverture des nouveaux chemins > 80 % (rapport pytest-cov joint à la PR)
  • Aucune régression sur les routes annotations existantes (tests anciens passent)
  • Pas de breaking change non documenté dans CHANGELOG.md

Base de données

  • Migration down() testée — restitue la DB dans son état pré-migration
  • Index conditionnel WHERE source_model_id IS NOT NULL créé (économie d'espace)
  • Backfill best-effort exécuté avec compte rendu (N annotations updated, M orphans)
  • FK ON DELETE SET NULL validée par un test (supprimer un ml_models ne casse pas les annotations)

Sécurité & RGPD

  • source_model_id filtré par tenant (un user du tenant A ne peut pas référencer un modèle du tenant B)
  • Endpoint orphans en require_role("ADMIN_TECHNIQUE") uniquement
  • source_tile_refs validé strictement par Pydantic (pas de JSONB libre)
  • Audit log généré pour chaque création d'annotation auto

Documentation

  • backend/models/info.md mis à jour (description des 3 nouveaux champs)
  • docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md : G1 + G10 marqués RESOLVED
  • Swagger UI : descriptions claires sur les 3 nouvelles colonnes (Field(description=...))

Dépendances

Hors-scope

Références

  • docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md §3 (G1 + G10)
  • EU AI Act art. 12 — logging des décisions algorithmiques
  • EU MDR Annexe XIV §3 — provenance des données utilisées par l'IA
  • FDA 21 CFR 820.30 — design controls

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:mlDomaine: Machine Learningpriority:criticalBloquant - A faire immediatementtype:featureNouvelle fonctionnalite

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions