API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com
Python 60.9%
HTML 21%
TypeScript 7.3%
JavaScript 5.2%
CSS 4.8%
Shell 0.8%
1# ============================================2# Projet : API-KA3# Fichier : src/api/routes/runs.py4# Node : m3u96b5# Author : Simon-Pierre Boucher6# Contact : contact@spboucher.ai7# Date : 2026-08-168# ============================================9"""Route /api/v1/runs : historique des collectes, filtrable par service et statut."""1011from __future__ import annotations1213from typing import Any1415from fastapi import APIRouter, Depends, HTTPException, Query16from sqlalchemy import func, select17from sqlalchemy.orm import Session1819from src.api.routes import envelope20from src.config import SERVICES21from src.database.db import get_db22from src.database.models import CollectionRun2324router = APIRouter(prefix="/api/v1", tags=["runs"])2526VALID_STATUSES = ("success", "failed", "retried")272829@router.get("/runs")30def list_runs(31 service: str | None = Query(None, description="Filtrer par service KA"),32 status: str | None = Query(None, description="success / failed / retried"),33 page: int = Query(1, ge=1),34 limit: int = Query(100, ge=1, le=500),35 db: Session = Depends(get_db),36) -> dict[str, Any]:37 """Historique des runs de collecte, du plus récent au plus ancien."""38 if service is not None and service not in SERVICES:39 raise HTTPException(status_code=404, detail=f"Service inconnu : {service}")40 if status is not None and status not in VALID_STATUSES:41 raise HTTPException(42 status_code=422,43 detail=f"Statut invalide : {status}. Valides : {', '.join(VALID_STATUSES)}",44 )4546 base = select(CollectionRun)47 if service:48 base = base.where(CollectionRun.service == service)49 if status:50 base = base.where(CollectionRun.status == status)5152 total = db.execute(select(func.count()).select_from(base.subquery())).scalar() or 053 rows = (54 db.execute(55 base.order_by(CollectionRun.started_at.desc())56 .offset((page - 1) * limit)57 .limit(limit)58 )59 .scalars()60 .all()61 )6263 data = [64 {65 "id": run.id,66 "service": run.service,67 "date_key": run.date_key.isoformat(),68 "status": run.status,69 "records_count": run.records_count,70 "duration_seconds": run.duration_seconds,71 "error_message": run.error_message,72 "node": run.node,73 "started_at": run.started_at.isoformat(),74 "finished_at": run.finished_at.isoformat(),75 }76 for run in rows77 ]78 return envelope(data, page=page, limit=limit, total=total)79