mockforge_http/handlers/
fidelity.rs1use 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#[derive(Clone)]
16pub struct FidelityState {
17 scores: Arc<RwLock<HashMap<String, FidelityScore>>>,
19}
20
21#[derive(Debug, Deserialize)]
23pub struct CalculateFidelityRequest {
24 pub mock_schema: Value,
26 pub real_schema: Value,
28 #[serde(default)]
30 pub mock_samples: Vec<Value>,
31 #[serde(default)]
33 pub real_samples: Vec<Value>,
34 #[serde(default)]
36 pub mock_response_times: Option<Vec<u64>>,
37 #[serde(default)]
39 pub real_response_times: Option<Vec<u64>>,
40 #[serde(default)]
42 pub mock_error_patterns: Option<HashMap<String, usize>>,
43 #[serde(default)]
45 pub real_error_patterns: Option<HashMap<String, usize>>,
46}
47
48#[derive(Debug, Serialize)]
50pub struct FidelityResponse {
51 pub success: bool,
53 pub score: FidelityScore,
55}
56
57impl FidelityState {
58 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
72pub 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 {
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
112pub 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 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
161pub 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}