1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
9#[serde(default)]
10pub struct QualityScoreWeights {
11 pub completeness: f64,
12 pub consistency: f64,
13 pub uniqueness: f64,
14 pub accuracy: f64,
15 pub timeliness: f64,
16 pub validity: f64,
17 pub precision: f64,
18}
19
20impl Default for QualityScoreWeights {
21 fn default() -> Self {
22 Self {
23 completeness: 0.25,
24 consistency: 0.20,
25 uniqueness: 0.15,
26 accuracy: 0.15,
27 timeliness: 0.10,
28 validity: 0.10,
29 precision: 0.05,
30 }
31 }
32}
33
34impl QualityScoreWeights {
35 pub fn is_default(&self) -> bool {
36 self == &Self::default()
37 }
38
39 pub fn is_valid(&self) -> bool {
40 let weights = [
41 self.completeness,
42 self.consistency,
43 self.uniqueness,
44 self.accuracy,
45 self.timeliness,
46 self.validity,
47 self.precision,
48 ];
49 weights
50 .iter()
51 .all(|weight| weight.is_finite() && *weight >= 0.0)
52 && weights.iter().any(|weight| *weight > 0.0)
53 }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct IsoQualityConfig {
60 pub max_null_percentage: f64,
62 pub high_cardinality_threshold: f64,
64 pub outlier_iqr_multiplier: f64,
66 pub outlier_min_samples: usize,
68 pub max_data_age_years: f64,
71 #[serde(default)]
73 pub score_weights: QualityScoreWeights,
74}
75
76impl Default for IsoQualityConfig {
77 fn default() -> Self {
78 Self {
79 max_null_percentage: 50.0,
80 high_cardinality_threshold: 95.0,
81 outlier_iqr_multiplier: 1.5,
82 outlier_min_samples: 4,
83 max_data_age_years: 5.0,
84 score_weights: QualityScoreWeights::default(),
85 }
86 }
87}
88
89impl IsoQualityConfig {
90 pub fn strict() -> Self {
92 Self {
93 max_null_percentage: 30.0,
94 high_cardinality_threshold: 98.0,
95 outlier_iqr_multiplier: 1.5,
96 outlier_min_samples: 10,
97 max_data_age_years: 2.0,
98 score_weights: QualityScoreWeights::default(),
99 }
100 }
101
102 pub fn lenient() -> Self {
104 Self {
105 max_null_percentage: 70.0,
106 high_cardinality_threshold: 90.0,
107 outlier_iqr_multiplier: 2.0,
108 outlier_min_samples: 4,
109 max_data_age_years: 10.0,
110 score_weights: QualityScoreWeights::default(),
111 }
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn legacy_quality_config_defaults_score_weights() {
121 let mut value = serde_json::to_value(IsoQualityConfig::default()).unwrap();
122 value
123 .as_object_mut()
124 .expect("quality config object")
125 .remove("score_weights");
126
127 let restored: IsoQualityConfig = serde_json::from_value(value).unwrap();
128 assert_eq!(restored.score_weights, QualityScoreWeights::default());
129 }
130
131 #[test]
132 fn five_dimension_weights_gain_new_dimension_defaults() {
133 let weights: QualityScoreWeights = serde_json::from_str(
134 r#"{
135 "completeness": 0.30,
136 "consistency": 0.25,
137 "uniqueness": 0.20,
138 "accuracy": 0.15,
139 "timeliness": 0.10
140 }"#,
141 )
142 .unwrap();
143
144 assert_eq!(weights.validity, 0.10);
145 assert_eq!(weights.precision, 0.05);
146 }
147
148 #[test]
149 fn all_zero_score_weights_are_invalid() {
150 let weights = QualityScoreWeights {
151 completeness: 0.0,
152 consistency: 0.0,
153 uniqueness: 0.0,
154 accuracy: 0.0,
155 timeliness: 0.0,
156 validity: 0.0,
157 precision: 0.0,
158 };
159
160 assert!(!weights.is_valid());
161 }
162}