Skip to main content

datasynth_eval/gates/
profiles.rs

1//! Built-in quality gate profiles.
2//!
3//! Provides strict, default, and lenient profiles with pre-configured thresholds.
4
5use super::engine::{GateProfile, QualityGate, QualityMetric};
6
7/// Strict profile — tight thresholds for production-quality data.
8///
9/// - Benford MAD < 0.01
10/// - Balance coherence >= 0.999
11/// - Document chain integrity >= 0.95
12/// - Completion rate >= 0.99
13/// - Duplicate rate <= 0.001
14/// - Referential integrity >= 0.999
15/// - IC match rate >= 0.99
16/// - Privacy MIA AUC <= 0.55
17pub fn strict_profile() -> GateProfile {
18    GateProfile::new(
19        "strict",
20        vec![
21            QualityGate::lte("benford_compliance", QualityMetric::BenfordMad, 0.01),
22            QualityGate::gte("balance_coherence", QualityMetric::BalanceCoherence, 0.999),
23            QualityGate::gte(
24                "document_chain_integrity",
25                QualityMetric::DocumentChainIntegrity,
26                0.95,
27            ),
28            QualityGate::gte(
29                "temporal_consistency",
30                QualityMetric::TemporalConsistency,
31                0.90,
32            ),
33            QualityGate::gte("completion_rate", QualityMetric::CompletionRate, 0.99),
34            QualityGate::lte("duplicate_rate", QualityMetric::DuplicateRate, 0.001),
35            QualityGate::gte(
36                "referential_integrity",
37                QualityMetric::ReferentialIntegrity,
38                0.999,
39            ),
40            QualityGate::gte("ic_match_rate", QualityMetric::IcMatchRate, 0.99),
41            QualityGate::lte("privacy_mia_auc", QualityMetric::PrivacyMiaAuc, 0.55),
42        ],
43    )
44}
45
46/// Default profile — balanced thresholds suitable for most use cases.
47///
48/// - Benford MAD < 0.015
49/// - Balance coherence >= 0.99
50/// - Document chain integrity >= 0.90
51/// - Completion rate >= 0.95
52/// - Duplicate rate <= 0.01
53/// - Referential integrity >= 0.99
54/// - IC match rate >= 0.95
55/// - Privacy MIA AUC <= 0.60
56pub fn default_profile() -> GateProfile {
57    GateProfile::new(
58        "default",
59        vec![
60            QualityGate::lte("benford_compliance", QualityMetric::BenfordMad, 0.015),
61            QualityGate::gte("balance_coherence", QualityMetric::BalanceCoherence, 0.99),
62            QualityGate::gte(
63                "document_chain_integrity",
64                QualityMetric::DocumentChainIntegrity,
65                0.90,
66            ),
67            QualityGate::gte(
68                "temporal_consistency",
69                QualityMetric::TemporalConsistency,
70                0.80,
71            ),
72            QualityGate::gte("completion_rate", QualityMetric::CompletionRate, 0.95),
73            QualityGate::lte("duplicate_rate", QualityMetric::DuplicateRate, 0.01),
74            QualityGate::gte(
75                "referential_integrity",
76                QualityMetric::ReferentialIntegrity,
77                0.99,
78            ),
79            QualityGate::gte("ic_match_rate", QualityMetric::IcMatchRate, 0.95),
80            QualityGate::lte("privacy_mia_auc", QualityMetric::PrivacyMiaAuc, 0.60),
81        ],
82    )
83}
84
85/// Lenient profile — relaxed thresholds for exploratory or development use.
86///
87/// - Benford MAD < 0.03
88/// - Balance coherence >= 0.95
89/// - Document chain integrity >= 0.80
90/// - Completion rate >= 0.90
91/// - Duplicate rate <= 0.05
92/// - Referential integrity >= 0.95
93/// - IC match rate >= 0.85
94/// - Privacy MIA AUC <= 0.70
95pub fn lenient_profile() -> GateProfile {
96    GateProfile::new(
97        "lenient",
98        vec![
99            QualityGate::lte("benford_compliance", QualityMetric::BenfordMad, 0.03),
100            QualityGate::gte("balance_coherence", QualityMetric::BalanceCoherence, 0.95),
101            QualityGate::gte(
102                "document_chain_integrity",
103                QualityMetric::DocumentChainIntegrity,
104                0.80,
105            ),
106            QualityGate::gte(
107                "temporal_consistency",
108                QualityMetric::TemporalConsistency,
109                0.60,
110            ),
111            QualityGate::gte("completion_rate", QualityMetric::CompletionRate, 0.90),
112            QualityGate::lte("duplicate_rate", QualityMetric::DuplicateRate, 0.05),
113            QualityGate::gte(
114                "referential_integrity",
115                QualityMetric::ReferentialIntegrity,
116                0.95,
117            ),
118            QualityGate::gte("ic_match_rate", QualityMetric::IcMatchRate, 0.85),
119            QualityGate::lte("privacy_mia_auc", QualityMetric::PrivacyMiaAuc, 0.70),
120        ],
121    )
122}
123
124/// Get a profile by name.
125///
126/// Returns `None` for unrecognized names.
127pub fn get_profile(name: &str) -> Option<GateProfile> {
128    match name {
129        "strict" => Some(strict_profile()),
130        "default" => Some(default_profile()),
131        "lenient" => Some(lenient_profile()),
132        _ => None,
133    }
134}
135
136#[cfg(test)]
137#[allow(clippy::unwrap_used)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_strict_profile_has_gates() {
143        let profile = strict_profile();
144        assert_eq!(profile.name, "strict");
145        assert!(!profile.gates.is_empty());
146        assert!(profile.gates.len() >= 5);
147    }
148
149    #[test]
150    fn test_default_profile_has_gates() {
151        let profile = default_profile();
152        assert_eq!(profile.name, "default");
153        assert!(!profile.gates.is_empty());
154    }
155
156    #[test]
157    fn test_lenient_profile_has_gates() {
158        let profile = lenient_profile();
159        assert_eq!(profile.name, "lenient");
160        assert!(!profile.gates.is_empty());
161    }
162
163    #[test]
164    fn test_strict_thresholds_tighter_than_lenient() {
165        let strict = strict_profile();
166        let lenient = lenient_profile();
167
168        // Find Benford MAD gate in both
169        let strict_benford = strict
170            .gates
171            .iter()
172            .find(|g| g.metric == QualityMetric::BenfordMad);
173        let lenient_benford = lenient
174            .gates
175            .iter()
176            .find(|g| g.metric == QualityMetric::BenfordMad);
177
178        if let (Some(s), Some(l)) = (strict_benford, lenient_benford) {
179            // Strict should have a lower (tighter) MAD threshold
180            assert!(
181                s.threshold < l.threshold,
182                "strict MAD ({}) should be < lenient MAD ({})",
183                s.threshold,
184                l.threshold
185            );
186        }
187    }
188
189    #[test]
190    fn test_get_profile_by_name() {
191        assert!(get_profile("strict").is_some());
192        assert!(get_profile("default").is_some());
193        assert!(get_profile("lenient").is_some());
194        assert!(get_profile("nonexistent").is_none());
195    }
196
197    #[test]
198    fn test_profile_serialization_roundtrip() {
199        for name in &["strict", "default", "lenient"] {
200            let profile = get_profile(name).expect("profile should exist");
201            let json = serde_json::to_string(&profile).expect("should serialize");
202            let deser: GateProfile = serde_json::from_str(&json).expect("should deserialize");
203            assert_eq!(deser.name, *name);
204            assert_eq!(deser.gates.len(), profile.gates.len());
205        }
206    }
207}