Contexte
L'API d'annotations ne propose que des endpoints unitaires (POST /api/v1/annotations = 1 row par appel HTTP). Pour réimporter un dataset existant (export QuPath de 50 000 annotations, migration depuis un système antérieur), 50 000 appels HTTP = ingestion irrespirable.
Référence : docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md — gap G12.
Objectif
Endpoint POST /api/v1/annotations/bulk qui ingère un fichier (GeoJSON, COCO, QuPath GeoJSON) en streaming + validation par chunks + COPY PostgreSQL.
Approche technique proposée
Endpoint
@router.post("/annotations/bulk")
async def bulk_import_annotations(
file: UploadFile,
format: Literal["geojson", "coco", "qupath_geojson"],
slide_id: str,
on_conflict: Literal["error", "skip", "update"] = "error",
current_user: CurrentUser = Depends(require_role("MEDECIN", "ADMIN_TECHNIQUE")),
):
"""Import annotations en bulk avec validation par chunks de 500."""
...
Pipeline interne
- Lire le fichier en streaming (pas de chargement complet en RAM)
- Pour chaque chunk de 500 :
- Valider via les schémas Pydantic (N2)
- Convertir geometry → WKT/PostGIS
- Batch INSERT via asyncpg
copy_records_to_table
- Rapport final :
{ inserted: N, skipped: M, errors: [...] }
Gestion des conflits
error : 422 au premier doublon (hash geometry + label_id)
skip : ignore doublons
update : remplace (déclenche annotation_history shadow)
Limites
- Taille max fichier : 100 MB (configurable)
- Annotations max par appel : 100 000 (au-delà, paginer)
- Timeout : 5 minutes
Acceptance criteria (fonctionnel)
Review checklist (conditions de validation pour le reviewer PR)
Performance
Robustesse
Sécurité
Cohérence
Documentation
Dépendances
- Amont (bloquant) : N2 (Pydantic schemas), N1 (validation
source_model_id)
- Aval : Cas de migration depuis QuPath / autre système
Hors-scope
- WebSocket progress en temps réel (SSE acceptable, plus tard)
- Validation cross-slide (geometry overlap entre slides)
Références
docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md §3 (G12)
- asyncpg
copy_records_to_table doc
Contexte
L'API d'annotations ne propose que des endpoints unitaires (
POST /api/v1/annotations= 1 row par appel HTTP). Pour réimporter un dataset existant (export QuPath de 50 000 annotations, migration depuis un système antérieur), 50 000 appels HTTP = ingestion irrespirable.Objectif
Endpoint
POST /api/v1/annotations/bulkqui ingère un fichier (GeoJSON, COCO, QuPath GeoJSON) en streaming + validation par chunks + COPY PostgreSQL.Approche technique proposée
Endpoint
Pipeline interne
copy_records_to_table{ inserted: N, skipped: M, errors: [...] }Gestion des conflits
error: 422 au premier doublon (hash geometry + label_id)skip: ignore doublonsupdate: remplace (déclenche annotation_history shadow)Limites
Acceptance criteria (fonctionnel)
POST /api/v1/annotations/bulkcrééservices/annotations/importers/(geojson, coco, qupath_geojson){inserted, skipped, errors[]}Review checklist (conditions de validation pour le reviewer PR)
Performance
Robustesse
on_conflict=skipne consomme pas un label inexistant silencieusementSécurité
slide_iddoit appartenir au tenant de l'utilisateur (404 sinon)Content-Length)inserted_countetfailed_countCohérence
annotation_type=autoetsource_model_idmanquant : rejet selon règle N1Documentation
docs/Admin/services/annotation-bulk-import.mddocs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md: G12 marquéRESOLVEDDépendances
source_model_id)Hors-scope
Références
docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md§3 (G12)copy_records_to_tabledoc