Skip to content

Livrable documentaire uniquement — Plan complet du pipeline vidéo NEXORA

1. Livrable de cette tâche

Créer uniquement :

  • docs/docs/architecture/video-pipeline-implementation-plan.md

Aucun code, schéma, dépendance, configuration ou test ne sera modifié à ce stade. Le document contiendra la spécification ci-dessous, suffisamment précise pour être confiée directement à un développeur.

Le document suivra une discipline d’ingénierie stricte :

  • Flux simples, explicites et déterministes.
  • Une seule source de vérité par donnée.
  • Aucun fallback silencieux vers des données fictives.
  • Aucune vidéo chargée intégralement en mémoire.
  • Transitions d’état atomiques et idempotentes.
  • Erreurs structurées, observables et récupérables.
  • SQL paramétré, types stricts, aucun nouveau any.
  • Aucun secret, chemin public ou détail biométrique dans les logs.
  • Pas d’abstraction sans au moins deux implémentations réelles.
  • Couverture lignes et branches de 100 % pour tout nouveau code testable.

2. Architecture d’implémentation documentée

Flux cible

Panel admin
  → session existante ou nouvel événement planifié
  → multipart upload direct vers R2
  → validation et création atomique du job PostgreSQL
  → orchestrateur CPU
  → Runpod Serverless GPU ou worker GPU local
  → proxy vidéo + détections + états + annotations dans R2
  → publication atomique dans Gold
  → activation du résultat
  → lecture privée avec overlays temporels

Invariants obligatoires

  • sessions.video_name reste l’identité logique utilisée par Gold.
  • video_assets.id devient l’identité physique du média.
  • Une session possède au maximum un asset vidéo actif.
  • Un asset possède au maximum un job actif.
  • Un retry crée une nouvelle tentative ; il ne réutilise pas les sorties d’une tentative échouée.
  • Le résultat précédent reste actif jusqu’à la publication complète du nouveau résultat.
  • completed signifie que proxy, overlays, annotations et Gold sont tous publiés.
  • Une requête complete upload répétée retourne le même résultat sans créer un second job.
  • La suppression ne peut commencer qu’après annulation ou terminaison du job actif.
  • Tous les timestamps sont absolus depuis le début de la vidéo.
  • Les coordonnées de bbox sont normalisées entre 0 et 1.
  • Un track insuffisamment identifié reste unknown.
  • Un modèle absent en production fait échouer le job ; il ne déclenche jamais le mock.
  • Le mock reste disponible uniquement avec NEXORA_MODEL_BACKEND=mock explicite.

Runpod

Configuration initiale documentée :

  • Endpoint Queue asynchrone /run.
  • Worker Flex, workersMin=0, workersMax=1.
  • GPU prioritaire A4000 16 Go, fallback A5000/L4 24 Go.
  • Timeout d’exécution : 6 heures.
  • TTL du job : 12 heures.
  • Un job par worker.
  • Image Docker taggée par SHA, jamais latest.
  • Modèles préentraînés intégrés ou mis en cache dans l’image.
  • Surveillance par polling toutes les 15 secondes ; webhook seulement comme optimisation.
  • Provider sélectionné par NEXORA_INFERENCE_PROVIDER=runpod|local.

Runpod permet les jobs async, le polling de progression et le scale-to-zero ; le tarif Flex 16 Go est actuellement annoncé autour de 0,58 $/h, à revérifier au déploiement. Endpoints Runpod, requêtes async, tarification.

3. Inventaire exact des fichiers de la future implémentation

Base de données et types

Fichier Action future
web/src/lib/server/db/schema.ts Ajouter videoAssets, processingJobs et les colonnes de stockage privé des photos.
web/drizzle/0001_video_pipeline.sql Migration versionnée, contraintes, index et backfill non destructif.
web/drizzle/meta/_journal.json Mise à jour générée par Drizzle, jamais éditée manuellement.
web/drizzle/meta/0001_snapshot.json Snapshot généré correspondant à la migration.
web/src/lib/video/types.ts Nouveaux types partagés et unions discriminées des états.
web/src/lib/server/video/service.ts Service transactionnel unique pour assets, jobs, transitions et retries.
web/src/lib/server/video/access.ts Autorisation par session : admin/director ou enseignant propriétaire.

Tables prévues :

  • video_assets
  • ID UUID, session unique, video_name unique.
  • Nom original, MIME, taille bigint, checksum SHA-256.
  • Clés R2 original/playback/analyse.
  • Durée, dimensions, audio, statut et active_job_id.
  • processing_jobs
  • UUID, asset/session, tentative, provider et ID externe.
  • Statut, étape, progression, version de modèles.
  • Préfixe de sortie, erreurs structurées, couverture et timestamps.
  • student_photos
  • Ajouter object_key, checksum, MIME, taille et statut de validation.
  • Conserver temporairement filename pour le backfill legacy.

Contraintes :

  • Index unique partiel empêchant deux jobs actifs par asset.
  • active_job_id ne référence qu’un job completed.
  • Foreign keys avec stratégie explicite pour suppression.
  • Toutes les transitions utilisent UPDATE … WHERE status IN (…), puis vérifient le nombre de lignes modifiées.

Stockage et upload R2

Fichier Action future
web/src/lib/server/storage/index.ts Supprimer les lectures distantes intégrales et exposer les primitives nécessaires.
web/src/lib/server/storage/multipart.ts Nouveau service multipart R2 : initiate, sign, complete et abort.
web/src/lib/server/storage/presign.ts Nouveau service de présignature GET/PUT avec durées bornées.
web/package.json Ajouter @aws-sdk/s3-request-presigner et les scripts de couverture.
web/pnpm-lock.yaml Mise à jour générée.
web/src/routes/api/admin/videos/+server.ts Conserver GET comme liste agrégée ; retirer l’ancien upload en mémoire.
web/src/routes/api/admin/videos/uploads/+server.ts Initier un multipart pour une session existante.
web/src/routes/api/admin/videos/uploads/[assetId]/parts/+server.ts Signer un lot borné de numéros de parties.
web/src/routes/api/admin/videos/uploads/[assetId]/complete/+server.ts Compléter, vérifier et créer le job atomiquement.
web/src/routes/api/admin/videos/uploads/[assetId]/+server.ts DELETE pour abort idempotent.
web/src/routes/api/admin/videos/[assetId]/+server.ts GET détail et DELETE contrôlé.
web/src/routes/api/admin/videos/[assetId]/retry/+server.ts Créer une nouvelle tentative.
web/src/routes/api/admin/videos/[assetId]/cancel/+server.ts Annuler le job actif.
web/src/routes/api/admin/sessions/[sessionId]/video/+server.ts Déléguer lecture/suppression au nouveau service ; supprimer l’upload form-data.
web/src/routes/api/admin/photos/+server.ts Enregistrer une clé R2 privée au lieu d’une URL.

Contrat multipart :

  • Parties de 64 Mio.
  • Trois uploads parallèles.
  • Trois retries avec backoff exponentiel et jitter.
  • URLs PUT valables 15 minutes.
  • Vérification taille et HeadObject avant création du job.
  • Taille maximale configurable, valeur initiale 20 Gio.
  • Types acceptés : MP4, MOV, WebM et MKV, mais la validation réelle se fait par ffprobe.
  • Les multipart incomplets sont supprimés après 24 heures.

Lecture et overlays

Fichier Action future
web/src/routes/videos/[name]/+server.ts Maintenir uniquement le fallback des anciennes vidéos locales.
web/src/routes/api/media/videos/[assetId]/playback/+server.ts Contrôle d’accès puis redirection vers une URL GET présignée.
web/src/routes/api/media/videos/[assetId]/overlay/manifest/+server.ts Servir le manifeste actif.
web/src/routes/api/media/videos/[assetId]/overlay/states/+server.ts Servir les états temporels.
web/src/routes/api/media/videos/[assetId]/overlay/chunks/[index]/+server.ts Servir les bboxes compressées par tranche.
web/src/lib/video/overlay.ts Recherche temporelle, interpolation et validation pure des artefacts.
web/src/lib/components/video/OverlayCanvas.svelte Canvas superposé au <video>.
web/src/lib/components/video/StudentVideoFilter.svelte Recherche et multi-sélection.
web/src/lib/components/modals/VideoPlayerModal.svelte Intégrer player, canvas, filtres, états et erreurs.
web/src/routes/dashboard/+page.svelte Passer assetId, permissions et données de session à la modale.

Formats :

type StudentWindowStateV1 = {
  studentRef: string;
  windowStartMs: number;
  windowEndMs: number;
  state: 'deep' | 'active' | 'passive' | 'disengaged' | 'withdrawal' | 'unobserved';
  score: number | null;
  signalCode: string | null;
  confidence: number;
  visibilityRatio: number;
  observedCategories: string[];
  imputedCategories: string[];
};

type OverlayTrackSampleV1 = {
  tMs: number;
  studentRef: string | null;
  trackId: number;
  box: [number, number, number, number];
  detectionConfidence: number;
  identityConfidence: number | null;
};

Comportement UI :

  • Tous les élèves sont sélectionnés à chaque ouverture.
  • Recherche, sélectionner tout et désélectionner tout.
  • Cadre coloré par état.
  • Label Nom · état · signal lisible.
  • Élève absent plus de 50 % de la fenêtre : Non observé.
  • Inconnus masqués dans l’interface standard.
  • Interpolation maximale 500 ms ; bbox masquée après une seconde sans observation.
  • requestVideoFrameCallback utilisé quand disponible, fallback timeupdate.
  • Chunk courant et suivant préchargés.
  • Si exactement un élève est sélectionné, afficher sa timeline ; sinon moyenne de classe.
  • La vidéo reste lisible sans overlay avec un message d’indisponibilité.

Pipeline IA

Fichier Action future
ai/pipeline/config.py Configuration typée, validation des providers, modèles et seuils.
ai/pipeline/storage.py Téléchargement/upload en streaming et clés R2 canoniques.
ai/pipeline/db.py Remplacer les interpolations SQL par un driver PostgreSQL paramétré.
ai/pipeline/runner.py Pipeline déterministe, reprise idempotente et publication des artefacts.
ai/pipeline/artifacts.py Nouveau writer/validator des manifestes, états et chunks overlay.
ai/pipeline/timeline.py Nouveau calcul centralisé des timestamps absolus.
ai/pipeline/steps/ingest.py Validation ffprobe et création des proxies lecture/analyse.
ai/pipeline/steps/detect.py YOLO11m préentraîné, sortie bornée et normalisée.
ai/pipeline/steps/track.py ByteTrack continu sur toute la vidéo.
ai/pipeline/steps/identify.py InsightFace, votes, seuil et marge explicites.
ai/pipeline/steps/pose.py YOLO11n-pose avec échantillonnage configuré.
ai/pipeline/steps/classify.py Retirer l’aléatoire ; baseline déterministe et catégories imputées.
ai/pipeline/steps/score.py Conserver le moteur canonique et ajouter confiance/provenance.
ai/orchestrator.py Nouveau poller CPU pour dispatch, statut, finalisation et récupération.
ai/runpod_handler.py Nouveau wrapper minimal runpod.serverless.start.
ai/providers/base.py Contrat provider limité à submit/status/cancel.
ai/providers/runpod.py Adaptateur Runpod avec timeouts, backoff et erreurs typées.
ai/worker.py Mode worker GPU local utilisant le même handler métier.
ai/Dockerfile Targets séparées orchestrateur léger et inference CUDA.
ai/requirements.txt Dépendances CPU et tests épinglées.
ai/requirements-ml.txt Versions compatibles CUDA, Ultralytics et InsightFace épinglées.
ai/pyproject.toml Ruff, pytest, coverage et règles statiques.

Modèles baseline :

  • YOLO11m : détection des personnes.
  • ByteTrack : continuité des tracks.
  • YOLO11n-pose : posture et gestes.
  • InsightFace buffalo_l : correspondance à la galerie.
  • Seuil identité initial : cosine ≥ 0,45.
  • Marge premier/deuxième candidat ≥ 0,05.
  • Minimum trois observations concordantes.
  • Les embeddings de référence sont temporaires et ne sont pas publiés.

Publication Gold

Fichier Action future
processing/pipelines/polars_pipeline.py Publication vidéo par vidéo dans un staging puis remplacement atomique.
processing/pipelines/scoring.py Inchangé sauf si un type explicite de provenance est nécessaire ; aucune duplication des règles.
processing/pipelines/video_publish.py Nouveau verrou, validation, commit et rollback des partitions.
web/src/lib/server/gold.ts Lire uniquement les partitions activées et exposer les états nécessaires.

Ordre de publication :

  1. Valider result.json, checksums et versions.
  2. Importer annotations vers bronze staging.
  3. Construire silver et gold staging.
  4. Prendre un verrou PostgreSQL unique.
  5. Remplacer atomiquement les partitions de la vidéo.
  6. Recalculer les agrégats globaux.
  7. Définir active_job_id.
  8. Passer job et asset à completed/ready.
  9. Libérer le verrou.

Interface admin

Fichier Action future
web/src/lib/components/views/AdminView.svelte Remplacer l’ancien champ file par l’uploader multipart et le suivi des jobs.
web/src/lib/components/modals/PlanningModal.svelte Créer d’abord la session, puis lancer le même uploader.
web/src/lib/components/video/VideoUpload.svelte Nouveau composant unique d’upload, progression et reprise.
web/src/lib/components/video/ProcessingStatus.svelte Nouveau composant des étapes, couverture, retry et cancel.
web/src/lib/components/layout/Controls.svelte Utiliser le statut du job actif.

États affichés :

  • Upload.
  • Validation.
  • Transcodage.
  • Détection.
  • Tracking.
  • Identification.
  • Classification.
  • Publication.
  • Terminé ou échec.

Infrastructure et documentation future

Fichier Action future
docker-compose.yml Remplacer le worker ambigu par orchestrateur CPU et profil GPU local.
docker-compose.prod.yml Configurer runpod au lieu du backend invalide openrouter.
docker-compose.test.yml Ajouter MinIO, fake Runpod et tests d’intégration.
.env.example Variables R2, Runpod, limites et modèles.
web/.env.example Variables utilisées par SvelteKit.
web/docs/storage-r2.md Multipart, CORS, lifecycle et bucket privé.
ai/README.md Exécution locale/Runpod, modèles, statuts et diagnostic.
DEPLOY.md Création endpoint, secrets, rollback et smoke tests.

4. Plan de tests avec couverture complète

Politique de couverture

  • 100 % lignes, fonctions, statements et branches sur tout nouveau code TypeScript testable.
  • 100 % lignes et branches sur tout nouveau code Python testable.
  • Aucun c8 ignore, pragma: no cover ou exclusion sans justification documentée et revue.
  • Les migrations générées, fichiers Drizzle meta et markup CSS ne comptent pas dans le seuil.
  • Les appels réseau, R2, Runpod, ffmpeg et PostgreSQL sont testés via ports/adapters injectables.
  • Le smoke GPU réel complète la couverture mais ne fait pas partie du CI obligatoire.

Fichiers de tests web

Fichier Couverture
web/src/lib/server/storage/multipart.test.ts Initiate, signatures, parties invalides, complete, abort, répétitions et erreurs R2.
web/src/lib/server/storage/presign.test.ts Clés autorisées, expirations et refus des traversées.
web/src/lib/server/video/service.test.ts Toutes les transitions, concurrence, retries, suppression et activation atomique.
web/src/lib/server/video/access.test.ts Admin, director, propriétaire, autre enseignant, parent et non authentifié.
web/src/lib/video/overlay.test.ts Validation schéma, chunk lookup, seek, interpolation, données périmées et bboxes invalides.
web/tests/unit/components/VideoUpload.test.ts Progression, retry, abort, erreurs, tailles et ETags.
web/tests/unit/components/ProcessingStatus.test.ts Chaque statut et action autorisée.
web/tests/unit/components/OverlayCanvas.test.ts Redimensionnement, letterboxing, sélection et masquage.
web/tests/unit/components/StudentVideoFilter.test.ts Tous sélectionnés, recherche, tout/aucun et clavier.
web/tests/unit/components/VideoPlayerModal.test.ts Chargement, seek, état courant, fermeture, fallback et erreurs.
web/e2e/admin-video.spec.ts Remplacer les tests legacy par le flux multipart complet.
web/e2e/video-player.spec.ts Lecture, multi-sélection, cadres, signaux, seek et accès.
web/e2e/video-security.spec.ts Matrice complète des rôles et absence d’URL publique persistante.

Cas API obligatoires :

  • Fichier valide et invalide.
  • Taille 0, limite exacte et dépassement.
  • Mauvais numéro de partie, doublon, ETag manquant.
  • Appel complete concurrent.
  • Session inexistante ou déjà équipée.
  • Upload abandonné ou expiré.
  • R2 indisponible avant et après création DB.
  • Job créé une seule fois.
  • Retry pendant job actif refusé.
  • Cancel répété idempotent.
  • Delete pendant upload, processing, completed et failed.
  • Enseignant propriétaire accepté, autre enseignant refusé.
  • URL signée expirée régénérée.
  • Range et seek fonctionnels.

Fichiers de tests Python IA

Fichier Couverture
ai/tests/test_config.py Validation exhaustive des variables et providers.
ai/tests/test_storage.py Streaming, erreurs partielles, checksum et nettoyage.
ai/tests/test_database.py Requêtes paramétrées, claims concurrents et transitions.
ai/tests/test_timeline.py Timestamps absolus, frontières et vidéos à FPS variable.
ai/tests/test_ingest.py ffprobe, codecs, proxies, audio et corruption.
ai/tests/test_detect.py Filtrage personne, coordonnées, limites et lot vide.
ai/tests/test_track.py Continuité, occlusion, disparition et changement de chunk.
ai/tests/test_identify.py Match, seuil, marge, égalité, galerie absente et inconnus.
ai/tests/test_pose.py Keypoints valides, partiels et absents.
ai/tests/test_classify.py Chaque signal, imputation, visibilité et déterminisme.
ai/tests/test_score.py Compatibilité exacte avec le scoring canonique.
ai/tests/test_artifacts.py Schémas, compression, chunks, checksums et manifestes.
ai/tests/test_runner.py Succès, échec à chaque étape, retry, cleanup et idempotence.
ai/tests/test_runpod_handler.py Input invalide, progression, résultat et exception sérialisée.
ai/tests/test_runpod_provider.py Submit/status/cancel, 401, 429, 5xx, timeout et backoff.
ai/tests/test_orchestrator.py Claim, polling, résultat expiré, publication et récupération après crash.

Cas essentiels :

  • Deux chunks ne remettent jamais le temps à zéro.
  • Deux tracks ne peuvent pas publier simultanément le même élève au même instant sans résolution explicite.
  • Galerie vide ou partielle n’échoue pas.
  • Un modèle manquant en mode réel échoue.
  • Un modèle manquant en mock reste déterministe.
  • Aucun caractère du nom de fichier ne peut influencer un chemin local ou R2.
  • Un job relancé n’écrase pas la tentative active.
  • Un crash après upload des artefacts mais avant mise à jour DB est récupérable.

Tests processing

Fichier Couverture
processing/tests/test_video_publish.py Staging, commit, rollback, verrou et remplacement atomique.
processing/tests/test_pipeline.py Ajouter import d’une vidéo unique et recomposition des agrégats.
processing/tests/test_scoring.py Conserver la couverture canonique existante et ajouter provenance/imputation si nécessaire.

Scénarios :

  • Publication réussie.
  • Annotation invalide.
  • Partition existante remplacée.
  • Échec avant commit.
  • Échec pendant recomposition globale.
  • Deux publications concurrentes.
  • Ancien résultat conservé après rollback.
  • Réexécution idempotente du même job.

Intégration et E2E

Le stack de test contiendra :

  • PostgreSQL.
  • MinIO comme implémentation S3/R2.
  • Fake Runpod HTTP programmable.
  • ffmpeg réel.
  • Worker CPU/mock déterministe.
  • Vidéo synthétique avec audio.
  • Galerie complète et galerie partielle.

Parcours E2E :

  1. Admin crée une session.
  2. Upload multipart direct.
  3. Progression upload visible.
  4. Job créé automatiquement.
  5. Fake Runpod publie les artefacts.
  6. Finalizer publie Gold.
  7. Asset passe ready.
  8. Modale ouverte avec tous les élèves sélectionnés.
  9. Désélection d’un élève masque uniquement sa bbox.
  10. Seek change bbox, niveau et signal.
  11. Élève sans référence apparaît Non observé.
  12. Parent reçoit 403.
  13. Enseignant non propriétaire reçoit 403.
  14. Suppression retire asset et objets sans affecter la session avant confirmation.

Smoke test GPU réel

Non exécuté à chaque CI, mais obligatoire avant production :

  • Endpoint Runpod réel.
  • Vidéo de deux minutes, dix élèves, deux sans référence.
  • YOLO, ByteTrack, pose et InsightFace réellement chargés.
  • Vérifier proxy H.264/AAC et lecture Range.
  • Vérifier timestamps, manifestes et Gold.
  • Mesurer temps GPU, délai de queue, cold start, mémoire et coût.
  • Répéter après extinction complète du worker pour tester le cold start.
  • Tester cancel puis retry.
  • Tester indisponibilité R2 temporaire.

Commandes de validation futures

cd web
pnpm check
pnpm lint
pnpm test
pnpm test:e2e
pnpm build
pnpm vitest run --coverage
cd ai
ruff check .
pytest --cov=pipeline --cov=providers --cov-branch --cov-fail-under=100
cd processing
uv run pytest --cov=pipelines --cov-branch --cov-fail-under=100
docker compose -f docker-compose.test.yml up --build --abort-on-container-exit

5. Critères d’acceptation du futur développement

  • Aucun upload de production ne traverse le serveur web.
  • Aucune lecture distante ne charge une vidéo entière en mémoire.
  • Range et seek fonctionnent sur R2.
  • Le pipeline démarre automatiquement après finalisation.
  • Tous les statuts sont persistés et récupérables après crash.
  • Aucun fallback mock/aléatoire en production.
  • Les timestamps restent corrects sur toute la vidéo.
  • Les bboxes publiées correspondent à la timeline de lecture.
  • Tous les élèves sont sélectionnés par défaut.
  • Chaque bbox affiche engagement et un signal lisible.
  • Un élève incertain n’est jamais identifié arbitrairement.
  • Les galeries partielles génèrent un avertissement sans bloquer.
  • Les parents et enseignants non propriétaires ne peuvent lire ni vidéo ni overlays.
  • Un retraitement échoué conserve le dernier résultat valide.
  • Toutes les nouvelles branches testables atteignent 100 % de couverture.
  • Le dépôt reste vert sur type-check, lint, tests, build et intégration.
  • La seule modification de la tâche actuelle demeure la création du document de plan ; aucune implémentation n’est incluse.

20. Statut d'implémentation (livraison réelle)

Cette section reflète ce qui a été effectivement implémenté et vérifié sur master, couche par couche. Le reste (migration Drizzle appliquée en base, Runpod réel, smoke GPU) est documenté comme étant à valider sur l'environnement de production.

20.1 Socle web — implémenté et vérifié ✅

Brique Fichiers Tests
Types partagés web/src/lib/video/types.ts —
Overlay (seek/interpélation/validators) web/src/lib/video/overlay.ts overlay.test.ts
Tables video_assets, processing_jobs + colonnes photos privées (schema.ts) migration 0001_video_pipeline.sql
Service transactionnel web/src/lib/server/video/service.ts (transitions atomiques via returning()) —
Autorisation par session web/src/lib/server/video/access.ts —
Storage multipart/presign multipart.ts, presign.ts, client.ts, read.ts storage.test.ts
Routes admin uploads /uploads, /uploads/[assetId]/parts, /complete, DELETE + asset/retry/cancel —
Routes media /playback (présigné), /overlay/manifest|states|chunks —
Composants UI OverlayCanvas, StudentVideoFilter, VideoUpload, ProcessingStatus —
Intégration VideoPlayerModal (asset+overlay), dashboard (assetId), AdminView, PlanningModal —

Vérifications : svelte-check 0 erreur · vitest 75 tests ✅.

20.2 Moteur IA — implémenté et vérifié ✅

Brique Fichiers Tests
Providers providers/base.py, runpod.py, local.py tests/test_providers.py
Timestamps absolus pipeline/timeline.py tests/test_timeline.py
Artefacts overlay pipeline/artifacts.py tests/test_artifacts.py
DB paramétré pipeline/db.py (psycopg, claims atomiques) —
Orchestrateur orchestrator.py tests/test_orchestrator.py
Handler Runpod runpod_handler.py —
classify déterministe pipeline/steps/classify.py (plus de random) tests/test_classify.py
identify seuils pipeline/steps/identify.py (0.45 / marge 0.05 / min 3 obs) tests/test_identify.py

Vérifications : pytest 25 tests ✅ · ruff clean ✅.

20.3 À valider en environnement réel (non exécuté ici)

  • Appliquer web/drizzle/0001_video_pipeline.sql sur la base (ou drizzle-kit push).
  • Configurer R2 (NEXORA_S3_*) + bucket privé + lifecycle.
  • Configurer Runpod (NEXORA_RUNPOD_ENDPOINT, NEXORA_RUNPOD_API_KEY, NEXORA_INFERENCE_PROVIDER=runpod).
  • Smoke GPU : vidéo 2 min, YOLO/ByteTrack/pose/InsightFace réels, lectures Range.
  • pnpm check && pnpm test && pnpm test:e2e && pnpm build sur web ; pytest --cov-fail-under=100 sur ai (nécessite pytest-cov en CI).
  • E2E admin-video.spec.ts / video-player.spec.ts réécrits autour du multipart.

20.4 Commandes de validation actuellement vertes

cd web && npx svelte-check --threshold error   # 0 erreur
cd web && npx vitest run --config vitest.config.ts  # 75 tests
cd ai && .venv/bin/python -m pytest tests/ -q   # 25 tests
cd ai && .venv/bin/ruff check providers pipeline/... orchestrator.py  # clean