Skip to content

feat(annotations): bulk import endpoint (COPY + chunked Pydantic validation) #366

Description

@Yanstart

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

  1. Lire le fichier en streaming (pas de chargement complet en RAM)
  2. Pour chaque chunk de 500 :
    • Valider via les schémas Pydantic (N2)
    • Convertir geometry → WKT/PostGIS
    • Batch INSERT via asyncpg copy_records_to_table
  3. 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)

  • Endpoint POST /api/v1/annotations/bulk créé
  • 3 parseurs dans services/annotations/importers/ (geojson, coco, qupath_geojson)
  • Validation Pydantic appliquée par annotation (réutilise N2)
  • Insertion COPY via asyncpg
  • Rapport JSON détaillé {inserted, skipped, errors[]}
  • Tests de charge : 10 000 annotations < 30 s

Review checklist (conditions de validation pour le reviewer PR)

Performance

  • Benchmark joint : import de 10 000 annotations en < 30 s sur la machine de CI
  • Profilage mémoire : pic < 500 MB pendant l'import
  • Pas de N+1 query (chaque chunk = 1 transaction)

Robustesse

  • Tests : fichier corrompu (JSON invalide, geometry invalide, label inexistant) → 422 propre, pas de partial state
  • Tests : rollback complet si une annotation du chunk échoue
  • Tests : on_conflict=skip ne consomme pas un label inexistant silencieusement

Sécurité

  • Tenant isolation : slide_id doit appartenir au tenant de l'utilisateur (404 sinon)
  • Taille fichier vérifiée AVANT lecture complète (header Content-Length)
  • Audit log : 1 entrée par bulk import avec inserted_count et failed_count

Cohérence

  • Roundtrip avec N3 : export → bulk import → re-export produit le même set
  • Si annotation_type=auto et source_model_id manquant : rejet selon règle N1

Documentation

  • Exemples de payloads dans Swagger
  • docs/Admin/services/annotation-bulk-import.md
  • docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md : G12 marqué RESOLVED

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions