1mod anomaly_scoring;
9mod baselines;
10mod cross_modal;
11mod detectability;
12mod domain_gap;
13mod embedding_readiness;
14mod feature_quality;
15mod features;
16mod gnn_readiness;
17mod graph;
18mod labels;
19mod scheme_detectability;
20mod splits;
21mod temporal_fidelity;
22
23pub use anomaly_scoring::{
24 AnomalyScoringAnalysis, AnomalyScoringAnalyzer, AnomalyScoringThresholds, ScoredRecord,
25};
26pub use baselines::{
27 get_accounting_baseline_tasks, BaselineAlgorithm, BaselineConfig, BaselineEvaluation,
28 BaselineResult, BaselineSummary, BaselineTask, ClassificationMetrics, ExpectedMetrics,
29 MLTaskType, PerformanceGrade, RankingMetrics, RegressionMetrics,
30};
31pub use cross_modal::{
32 CrossModalAnalysis, CrossModalAnalyzer, CrossModalThresholds, EntityModalData,
33};
34pub use detectability::{DetectabilityAnalyzer, DetectabilityReport};
35pub use domain_gap::{
36 DistributionSample, DomainGapAnalysis, DomainGapAnalyzer, DomainGapDetail, DomainGapThresholds,
37};
38pub use embedding_readiness::{
39 EmbeddingInput, EmbeddingReadinessAnalysis, EmbeddingReadinessAnalyzer,
40 EmbeddingReadinessThresholds,
41};
42pub use feature_quality::{
43 FeatureQualityAnalysis, FeatureQualityAnalyzer, FeatureQualityThresholds, FeatureVector,
44};
45pub use features::{FeatureAnalysis, FeatureAnalyzer, FeatureStats};
46pub use gnn_readiness::GraphData as GnnGraphData;
47pub use gnn_readiness::{GnnReadinessAnalysis, GnnReadinessAnalyzer, GnnReadinessThresholds};
48pub use graph::{GraphAnalysis, GraphAnalyzer, GraphMetrics};
49pub use labels::{LabelAnalysis, LabelAnalyzer, LabelDistribution};
50pub use scheme_detectability::{
51 SchemeDetectabilityAnalysis, SchemeDetectabilityAnalyzer, SchemeDetectabilityThresholds,
52 SchemeRecord,
53};
54pub use splits::{SplitAnalysis, SplitAnalyzer, SplitMetrics};
55pub use temporal_fidelity::{
56 TemporalFidelityAnalysis, TemporalFidelityAnalyzer, TemporalFidelityThresholds, TemporalRecord,
57};
58
59use serde::{Deserialize, Serialize};
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct MLReadinessEvaluation {
64 pub features: Option<FeatureAnalysis>,
66 pub labels: Option<LabelAnalysis>,
68 pub splits: Option<SplitAnalysis>,
70 pub graph: Option<GraphAnalysis>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub anomaly_scoring: Option<AnomalyScoringAnalysis>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub feature_quality: Option<FeatureQualityAnalysis>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub gnn_readiness: Option<GnnReadinessAnalysis>,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub domain_gap: Option<DomainGapAnalysis>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub temporal_fidelity: Option<TemporalFidelityAnalysis>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub scheme_detectability: Option<SchemeDetectabilityAnalysis>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub cross_modal: Option<CrossModalAnalysis>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub embedding_readiness: Option<EmbeddingReadinessAnalysis>,
96 pub overall_score: f64,
98 pub passes: bool,
100 pub issues: Vec<String>,
102 pub failures: Vec<String>,
104}
105
106impl MLReadinessEvaluation {
107 pub fn new() -> Self {
109 Self {
110 features: None,
111 labels: None,
112 splits: None,
113 graph: None,
114 anomaly_scoring: None,
115 feature_quality: None,
116 gnn_readiness: None,
117 domain_gap: None,
118 temporal_fidelity: None,
119 scheme_detectability: None,
120 cross_modal: None,
121 embedding_readiness: None,
122 overall_score: 1.0,
123 passes: true,
124 issues: Vec::new(),
125 failures: Vec::new(),
126 }
127 }
128
129 pub fn check_thresholds(&mut self, thresholds: &crate::config::EvaluationThresholds) {
131 self.issues.clear();
132 self.failures.clear();
133 let mut scores = Vec::new();
134
135 if let Some(ref labels) = self.labels {
136 if labels.anomaly_rate < thresholds.anomaly_rate_min {
138 self.issues.push(format!(
139 "Anomaly rate {} < {} (min threshold)",
140 labels.anomaly_rate, thresholds.anomaly_rate_min
141 ));
142 }
143 if labels.anomaly_rate > thresholds.anomaly_rate_max {
144 self.issues.push(format!(
145 "Anomaly rate {} > {} (max threshold)",
146 labels.anomaly_rate, thresholds.anomaly_rate_max
147 ));
148 }
149
150 if labels.label_coverage < thresholds.label_coverage_min {
152 self.issues.push(format!(
153 "Label coverage {} < {} (threshold)",
154 labels.label_coverage, thresholds.label_coverage_min
155 ));
156 }
157
158 scores.push(labels.quality_score);
159 }
160
161 if let Some(ref splits) = self.splits {
162 if !splits.is_valid {
163 self.issues
164 .push("Train/test split validation failed".to_string());
165 }
166 scores.push(if splits.is_valid { 1.0 } else { 0.0 });
167 }
168
169 if let Some(ref graph) = self.graph {
170 if graph.connectivity_score < thresholds.graph_connectivity_min {
171 self.issues.push(format!(
172 "Graph connectivity {} < {} (threshold)",
173 graph.connectivity_score, thresholds.graph_connectivity_min
174 ));
175 }
176 scores.push(graph.connectivity_score);
177 }
178
179 if let Some(ref features) = self.features {
180 scores.push(features.quality_score);
181 }
182
183 if let Some(ref as_eval) = self.anomaly_scoring {
185 if !as_eval.passes {
186 self.issues.extend(as_eval.issues.clone());
187 }
188 scores.push(as_eval.anomaly_separability);
189 }
190 if let Some(ref fq_eval) = self.feature_quality {
191 if !fq_eval.passes {
192 self.issues.extend(fq_eval.issues.clone());
193 }
194 scores.push(fq_eval.feature_quality_score);
195 }
196 if let Some(ref gnn_eval) = self.gnn_readiness {
197 if !gnn_eval.passes {
198 self.issues.extend(gnn_eval.issues.clone());
199 }
200 scores.push(gnn_eval.gnn_readiness_score);
201 }
202 if let Some(ref dg_eval) = self.domain_gap {
203 if !dg_eval.passes {
204 self.issues.extend(dg_eval.issues.clone());
205 }
206 scores.push(1.0 - dg_eval.domain_gap_score);
208 }
209 if let Some(ref tf_eval) = self.temporal_fidelity {
210 if !tf_eval.passes {
211 self.issues.extend(tf_eval.issues.clone());
212 }
213 scores.push(tf_eval.temporal_fidelity_score);
214 }
215 if let Some(ref sd_eval) = self.scheme_detectability {
216 if !sd_eval.passes {
217 self.issues.extend(sd_eval.issues.clone());
218 }
219 scores.push(sd_eval.detectability_score);
220 }
221 if let Some(ref cm_eval) = self.cross_modal {
222 if !cm_eval.passes {
223 self.issues.extend(cm_eval.issues.clone());
224 }
225 scores.push(cm_eval.consistency_score);
226 }
227 if let Some(ref er_eval) = self.embedding_readiness {
228 if !er_eval.passes {
229 self.issues.extend(er_eval.issues.clone());
230 }
231 scores.push(er_eval.embedding_readiness_score);
232 }
233
234 self.overall_score = if scores.is_empty() {
235 1.0
236 } else {
237 scores.iter().sum::<f64>() / scores.len() as f64
238 };
239
240 self.failures = self.issues.clone();
242 self.passes = self.issues.is_empty();
243 }
244}
245
246impl Default for MLReadinessEvaluation {
247 fn default() -> Self {
248 Self::new()
249 }
250}