Skip to main content

mockforge_intelligence/handlers/drift_budget/
metrics.rs

1//! Incident-derived metrics and lookback selection.
2
3use super::DriftBudgetStateProvider;
4use crate::handlers::structural_incidents::StructuralIncidents;
5use axum::{
6    extract::{Query, State},
7    http::StatusCode,
8    response::Json,
9};
10use mockforge_foundation::incidents_types::{IncidentQuery, IncidentType};
11use serde::Deserialize;
12use std::collections::HashMap;
13
14/// Get drift metrics over time
15///
16/// GET /api/v1/drift/metrics?endpoint=/api/users&method=GET&days=30
17#[derive(Debug, Deserialize)]
18pub struct GetMetricsQuery {
19    /// Optional endpoint filter
20    pub endpoint: Option<String>,
21    /// Optional HTTP method filter
22    pub method: Option<String>,
23    /// Optional workspace ID filter
24    pub workspace_id: Option<String>,
25    /// Lookback window in days
26    pub days: Option<u32>,
27}
28
29/// Get drift metrics
30///
31/// GET /api/v1/drift/metrics
32pub async fn get_drift_metrics<S: DriftBudgetStateProvider>(
33    State(state): State<S>,
34    Query(params): Query<GetMetricsQuery>,
35) -> Result<Json<serde_json::Value>, StatusCode> {
36    // Query incidents for metrics
37    let mut query = IncidentQuery {
38        endpoint: params.endpoint,
39        method: params.method,
40        workspace_id: params.workspace_id,
41        ..IncidentQuery::default()
42    };
43
44    // Filter by date range if days specified
45    if let Some(days) = params.days {
46        let start_date = chrono::Utc::now()
47            .checked_sub_signed(chrono::Duration::days(days as i64))
48            .map(|dt| dt.timestamp())
49            .unwrap_or(0);
50        query.start_date = Some(start_date);
51    }
52
53    let incidents = state.incident_manager().query_incidents(query).await;
54
55    // Calculate metrics
56    let total_incidents = incidents.len();
57    let breaking_changes = incidents
58        .iter()
59        .filter(|i| matches!(i.incident_type, IncidentType::BreakingChange))
60        .count();
61    let threshold_exceeded = total_incidents - breaking_changes;
62
63    let by_severity: HashMap<String, usize> =
64        incidents.iter().fold(HashMap::new(), |mut acc, inc| {
65            let key = format!("{:?}", inc.severity).to_lowercase();
66            *acc.entry(key).or_insert(0) += 1;
67            acc
68        });
69
70    Ok(Json(serde_json::json!({
71        "total_incidents": total_incidents,
72        "breaking_changes": breaking_changes,
73        "threshold_exceeded": threshold_exceeded,
74        "by_severity": by_severity,
75        "incidents": incidents.iter().take(100).collect::<Vec<_>>(), // Limit to first 100
76    })))
77}