SPB Git forge

spb/api-ka

Public

API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com

48commits 1branches 0releases
5.9 MBsize
maindefault branch
19 days agolast push
Python 60.9% HTML 21% TypeScript 7.3% JavaScript 5.2% CSS 4.8% Shell 0.8%
3.5 KB · 114 lines python
Raw Blame History
1# ============================================2# Projet   : API-KA3# Fichier  : src/api/routes/monitoring.py4# Node     : m3u96b5# Author   : Simon-Pierre Boucher6# Contact  : contact@spboucher.ai7# Date     : 2026-08-188# ============================================9"""Routes publiques de supervision des connecteurs de l'écosystème KA.1011L'état est produit toutes les 2 h par ``src.monitoring.connector_health``12(job apika-scheduler) et stocké dans la table ``connector_health``.13"""1415from __future__ import annotations1617from typing import Any1819from fastapi import APIRouter, Depends, HTTPException20from sqlalchemy import select21from sqlalchemy.orm import Session2223from src.api.routes import envelope24from src.config import SERVICES25from src.database.db import get_db26from src.database.models import ConnectorHealth27from src.monitoring.connector_health import ALL_STATUSES, APP_SOURCE2829router = APIRouter(prefix="/api/v1/monitoring", tags=["monitoring"])303132def _row_to_dict(row: ConnectorHealth) -> dict[str, Any]:33    return {34        "source": row.source,35        "status": row.status,36        "checked_at": row.checked_at.isoformat() if row.checked_at else None,37        "last_success": row.last_success.isoformat() if row.last_success else None,38        "found_last": row.found_last,39        "median_found": row.median_found,40        "consecutive_failures": row.consecutive_failures,41        "message": row.message,42        "last_seen": row.last_seen.isoformat() if row.last_seen else None,43    }444546def _empty_counts() -> dict[str, int]:47    return dict.fromkeys(ALL_STATUSES, 0)484950def _group_rows(rows: list[ConnectorHealth]) -> dict[str, Any]:51    services: dict[str, Any] = {}52    for row in rows:53        bucket = services.setdefault(54            row.service,55            {"summary": _empty_counts(), "app": None, "connectors": []},56        )57        if row.status in bucket["summary"]:58            bucket["summary"][row.status] += 159        payload = _row_to_dict(row)60        if row.source == APP_SOURCE:61            bucket["app"] = payload62        else:63            bucket["connectors"].append(payload)64    return services656667@router.get("/connectors")68def connectors(db: Session = Depends(get_db)) -> dict[str, Any]:69    """État courant de tous les connecteurs, groupé par service."""70    rows = (71        db.execute(72            select(ConnectorHealth).order_by(73                ConnectorHealth.service, ConnectorHealth.source74            )75        )76        .scalars()77        .all()78    )79    services = _group_rows(rows)8081    summary = _empty_counts()82    for bucket in services.values():83        for status, count in bucket["summary"].items():84            summary[status] += count8586    data = {87        "summary": summary,88        "services": services,89    }90    return envelope(data, extra_meta={"services_total": len(services)})919293@router.get("/connectors/{service}")94def connectors_service(95    service: str, db: Session = Depends(get_db)96) -> dict[str, Any]:97    """État courant des connecteurs d'un service donné."""98    if service not in SERVICES:99        raise HTTPException(status_code=404, detail=f"Service inconnu : {service}")100    rows = (101        db.execute(102            select(ConnectorHealth)103            .where(ConnectorHealth.service == service)104            .order_by(ConnectorHealth.source)105        )106        .scalars()107        .all()108    )109    bucket = _group_rows(rows).get(110        service, {"summary": _empty_counts(), "app": None, "connectors": []}111    )112    data = {"service": service, **bucket}113    return envelope(data)114