| 177 |
177 |
|
| 178 |
178 |
|
| 179 |
179 |
# ---------------------------------------------------------------- agrégats |
|
180 |
+# Endpoints « bruit » exclus des métriques du tableau de bord : chemins |
|
181 |
+# inconnus sondés par les scanners/bots (404, .env, etc.). Ils restent |
|
182 |
+# journalisés dans api_requests pour la visibilité sécurité, mais ne doivent |
|
183 |
+# pas polluer le taux d'erreur qui mesure la santé réelle de l'API. |
|
184 |
+NOISE_ENDPOINTS = ("(autre)", "/api/v1/(autre)") |
|
185 |
+ |
|
186 |
+ |
| 180 |
187 |
def _fetch_requests( |
| 181 |
188 |
db: Session, d_from: datetime.date, d_to: datetime.date |
| 182 |
189 |
) -> list[tuple[datetime.datetime, str, str, int, float]]: |
| 189 |
196 |
ApiRequest.endpoint, |
| 190 |
197 |
ApiRequest.status, |
| 191 |
198 |
ApiRequest.duration_ms, |
| 192 |
|
− ).where(ApiRequest.ts >= lo, ApiRequest.ts < hi) |
|
199 |
+ ).where( |
|
200 |
+ ApiRequest.ts >= lo, |
|
201 |
+ ApiRequest.ts < hi, |
|
202 |
+ ApiRequest.endpoint.notin_(NOISE_ENDPOINTS), |
|
203 |
+ ) |
| 193 |
204 |
).all() |
| 194 |
205 |
out = [] |
| 195 |
206 |
for ts, method, endpoint, status, duration in rows: |
| 1138 |
1149 |
|
| 1139 |
1150 |
|
| 1140 |
1151 |
# ---------------------------------------------------------------- routes |
|
1152 |
+@router.get("") |
|
1153 |
+def stats_summary(db: Session = Depends(get_db)) -> dict[str, Any]: |
|
1154 |
+ """Résumé compact de la plateforme (contrat léger /api/stats commun KA). |
|
1155 |
+ |
|
1156 |
+ Version allégée du tableau de bord pour les moniteurs et les apps sœurs : |
|
1157 |
+ activité API 7 jours, runs de collecte, dernier run par service et santé |
|
1158 |
+ des connecteurs supervisés. |
|
1159 |
+ """ |
|
1160 |
+ today = datetime.datetime.now(TZ).date() |
|
1161 |
+ d_from = today - datetime.timedelta(days=6) |
|
1162 |
+ reqs = _fetch_requests(db, d_from, today) |
|
1163 |
+ runs = _fetch_runs(db, d_from, today) |
|
1164 |
+ health = _fetch_health(db) |
|
1165 |
+ errors = sum(1 for r in reqs if r[3] >= 400) |
|
1166 |
+ ok_runs = [r for r in runs if r.status in ("success", "retried")] |
|
1167 |
+ last_run: dict[str, Any] = {} |
|
1168 |
+ for r in runs: # triés par started_at décroissant |
|
1169 |
+ if r.service not in last_run: |
|
1170 |
+ last_run[r.service] = { |
|
1171 |
+ "date": r.date_key.isoformat(), |
|
1172 |
+ "status": r.status, |
|
1173 |
+ "records": r.records_count, |
|
1174 |
+ } |
|
1175 |
+ connectors = dict.fromkeys(("ok", "degraded", "broken", "stale"), 0) |
|
1176 |
+ for h in health: |
|
1177 |
+ if h.status in connectors: |
|
1178 |
+ connectors[h.status] += 1 |
|
1179 |
+ return envelope( |
|
1180 |
+ { |
|
1181 |
+ "platform": "API-KA", |
|
1182 |
+ "period": {"from": d_from.isoformat(), "to": today.isoformat()}, |
|
1183 |
+ "api": { |
|
1184 |
+ "calls_7d": len(reqs), |
|
1185 |
+ "error_rate_7d": round(errors / len(reqs) * 100, 2) if reqs else 0.0, |
|
1186 |
+ }, |
|
1187 |
+ "runs_7d": { |
|
1188 |
+ "total": len(runs), |
|
1189 |
+ "ok": len(ok_runs), |
|
1190 |
+ "failed": sum(1 for r in runs if r.status == "failed"), |
|
1191 |
+ "records": sum(r.records_count for r in ok_runs), |
|
1192 |
+ }, |
|
1193 |
+ "last_run": last_run, |
|
1194 |
+ "connectors": connectors, |
|
1195 |
+ } |
|
1196 |
+ ) |
|
1197 |
+ |
|
1198 |
+ |
| 1141 |
1199 |
@router.get("/dashboard") |
| 1142 |
1200 |
def stats_dashboard( |
| 1143 |
1201 |
period: str = Query("30j", description="auj, 7j, 30j, 3m, 6m, 12m, annee, tout"), |
| 1144 |
1202 |
|