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)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_strict_profile_has_gates() {
142        let profile = strict_profile();
143        assert_eq!(profile.name, "strict");
144        assert!(!profile.gates.is_empty());
145        assert!(profile.gates.len() >= 5);
146    }
147
148    #[test]
149    fn test_default_profile_has_gates() {
150        let profile = default_profile();
151        assert_eq!(profile.name, "default");
152        assert!(!profile.gates.is_empty());
153    }
154
155    #[test]
156    fn test_lenient_profile_has_gates() {
157        let profile = lenient_profile();
158        assert_eq!(profile.name, "lenient");
159        assert!(!profile.gates.is_empty());
160    }
161
162    #[test]
163    fn test_strict_thresholds_tighter_than_lenient() {
164        let strict = strict_profile();
165        let lenient = lenient_profile();
166
167        // Find Benford MAD gate in both
168        let strict_benford = strict
169            .gates
170            .iter()
171            .find(|g| g.metric == QualityMetric::BenfordMad);
172        let lenient_benford = lenient
173            .gates
174            .iter()
175            .find(|g| g.metric == QualityMetric::BenfordMad);
176
177        if let (Some(s), Some(l)) = (strict_benford, lenient_benford) {
178            // Strict should have a lower (tighter) MAD threshold
179            assert!(
180                s.threshold < l.threshold,
181                "strict MAD ({}) should be < lenient MAD ({})",
182                s.threshold,
183                l.threshold
184            );
185        }
186    }
187
188    #[test]
189    fn test_get_profile_by_name() {
190        assert!(get_profile("strict").is_some());
191        assert!(get_profile("default").is_some());
192        assert!(get_profile("lenient").is_some());
193        assert!(get_profile("nonexistent").is_none());
194    }
195
196    #[test]
197    fn test_profile_serialization_roundtrip() {
198        for name in &["strict", "default", "lenient"] {
199            let profile = get_profile(name).expect("profile should exist");
200            let json = serde_json::to_string(&profile).expect("should serialize");
201            let deser: GateProfile = serde_json::from_str(&json).expect("should deserialize");
202            assert_eq!(deser.name, *name);
203            assert_eq!(deser.gates.len(), profile.gates.len());
204        }
205    }
206}