mockforge_http/handlers/
fidelity.rs

1//! Fidelity score API handlers
2
3use axum::extract::{Path, Query, State};
4use axum::http::StatusCode;
5use axum::response::Json;
6use mockforge_core::fidelity::{FidelityCalculator, FidelityScore};
7use serde::{Deserialize, Serialize};
8use serde_json::{json, Value};
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12use tracing::{error, info};
13
14/// State for fidelity handlers
15#[derive(Clone)]
16pub struct FidelityState {
17    /// Stored fidelity scores per workspace
18    scores: Arc<RwLock<HashMap<String, FidelityScore>>>,
19}
20
21/// Request to calculate fidelity score
22#[derive(Debug, Deserialize)]
23pub struct CalculateFidelityRequest {
24    /// Mock schema
25    pub mock_schema: Value,
26    /// Real schema
27    pub real_schema: Value,
28    /// Mock sample responses
29    #[serde(default)]
30    pub mock_samples: Vec<Value>,
31    /// Real sample responses
32    #[serde(default)]
33    pub real_samples: Vec<Value>,
34    /// Mock response times (optional)
35    #[serde(default)]
36    pub mock_response_times: Option<Vec<u64>>,
37    /// Real response times (optional)
38    #[serde(default)]
39    pub real_response_times: Option<Vec<u64>>,
40    /// Mock error patterns (optional)
41    #[serde(default)]
42    pub mock_error_patterns: Option<HashMap<String, usize>>,
43    /// Real error patterns (optional)
44    #[serde(default)]
45    pub real_error_patterns: Option<HashMap<String, usize>>,
46}
47
48/// Response for fidelity score calculation
49#[derive(Debug, Serialize)]
50pub struct FidelityResponse {
51    /// Success flag
52    pub success: bool,
53    /// Fidelity score
54    pub score: FidelityScore,
55}
56
57impl FidelityState {
58    /// Create a new fidelity state
59    pub fn new() -> Self {
60        Self {
61            scores: Arc::new(RwLock::new(HashMap::new())),
62        }
63    }
64}
65
66impl Default for FidelityState {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72/// Calculate fidelity score
73///
74/// POST /api/v1/workspace/{workspace_id}/fidelity
75pub async fn calculate_fidelity(
76    Path(workspace_id): Path<String>,
77    State(state): State<FidelityState>,
78    Json(request): Json<CalculateFidelityRequest>,
79) -> Result<Json<Value>, StatusCode> {
80    let calculator = FidelityCalculator::new();
81
82    let score = calculator.calculate(
83        &request.mock_schema,
84        &request.real_schema,
85        &request.mock_samples,
86        &request.real_samples,
87        request.mock_response_times.as_deref(),
88        request.real_response_times.as_deref(),
89        request.mock_error_patterns.as_ref(),
90        request.real_error_patterns.as_ref(),
91    );
92
93    // Store the score
94    {
95        let mut scores = state.scores.write().await;
96        scores.insert(workspace_id.clone(), score.clone());
97    }
98
99    info!(
100        "Calculated fidelity score for workspace: {} - Overall: {:.2}%",
101        workspace_id,
102        score.overall * 100.0
103    );
104
105    Ok(Json(json!({
106        "success": true,
107        "workspace_id": workspace_id,
108        "score": score
109    })))
110}
111
112/// Get fidelity score for a workspace
113///
114/// GET /api/v1/workspace/{workspace_id}/fidelity
115pub async fn get_fidelity(
116    Path(workspace_id): Path<String>,
117    State(state): State<FidelityState>,
118) -> Result<Json<Value>, StatusCode> {
119    let scores = state.scores.read().await;
120
121    if let Some(score) = scores.get(&workspace_id) {
122        // Return score with driver metrics breakdown
123        Ok(Json(json!({
124            "success": true,
125            "workspace_id": workspace_id,
126            "score": {
127                "overall": score.overall,
128                "overall_percentage": (score.overall * 100.0).round() as u8,
129                "driver_metrics": {
130                    "schema_similarity": {
131                        "value": score.schema_similarity,
132                        "percentage": (score.schema_similarity * 100.0).round() as u8,
133                        "label": "Schema Match"
134                    },
135                    "sample_similarity": {
136                        "value": score.sample_similarity,
137                        "percentage": (score.sample_similarity * 100.0).round() as u8,
138                        "label": "Sample Similarity"
139                    },
140                    "response_time_similarity": {
141                        "value": score.response_time_similarity,
142                        "percentage": (score.response_time_similarity * 100.0).round() as u8,
143                        "label": "Response Time Match"
144                    },
145                    "error_pattern_similarity": {
146                        "value": score.error_pattern_similarity,
147                        "percentage": (score.error_pattern_similarity * 100.0).round() as u8,
148                        "label": "Error Pattern Match"
149                    }
150                },
151                "computed_at": score.computed_at,
152                "metadata": score.metadata
153            }
154        })))
155    } else {
156        error!("Fidelity score not found for workspace: {}", workspace_id);
157        Err(StatusCode::NOT_FOUND)
158    }
159}
160
161/// Create fidelity router
162pub fn fidelity_router(state: FidelityState) -> axum::Router {
163    use axum::routing::{get, post};
164
165    axum::Router::new()
166        .route(
167            "/api/v1/workspace/{workspace_id}/fidelity",
168            get(get_fidelity).post(calculate_fidelity),
169        )
170        .with_state(state)
171}