1use serde::{Deserialize, Serialize};
5
6fn default_min_failures() -> u32 {
7 3
8}
9
10fn default_improve_threshold() -> f64 {
11 0.7
12}
13
14fn default_rollback_threshold() -> f64 {
15 0.5
16}
17
18fn default_min_evaluations() -> u32 {
19 5
20}
21
22fn default_max_versions() -> u32 {
23 10
24}
25
26fn default_cooldown_minutes() -> u64 {
27 60
28}
29
30fn default_correction_detection() -> bool {
31 true
32}
33
34fn default_correction_confidence_threshold() -> f32 {
35 0.6
36}
37
38fn default_judge_adaptive_low() -> f32 {
39 0.5
40}
41
42fn default_judge_adaptive_high() -> f32 {
43 0.8
44}
45
46fn default_correction_recall_limit() -> u32 {
47 3
48}
49
50fn default_correction_min_similarity() -> f32 {
51 0.75
52}
53
54fn default_auto_promote_min_uses() -> u32 {
55 50
56}
57
58fn default_auto_promote_threshold() -> f64 {
59 0.95
60}
61
62fn default_auto_demote_min_uses() -> u32 {
63 30
64}
65
66fn default_auto_demote_threshold() -> f64 {
67 0.40
68}
69
70#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
72#[serde(rename_all = "lowercase")]
73pub enum DetectorMode {
74 #[default]
76 Regex,
77 Judge,
85 Model,
93}
94
95#[derive(Debug, Clone, Deserialize, Serialize)]
96pub struct LearningConfig {
97 #[serde(default)]
98 pub enabled: bool,
99 #[serde(default)]
100 pub auto_activate: bool,
101 #[serde(default = "default_min_failures")]
102 pub min_failures: u32,
103 #[serde(default = "default_improve_threshold")]
104 pub improve_threshold: f64,
105 #[serde(default = "default_rollback_threshold")]
106 pub rollback_threshold: f64,
107 #[serde(default = "default_min_evaluations")]
108 pub min_evaluations: u32,
109 #[serde(default = "default_max_versions")]
110 pub max_versions: u32,
111 #[serde(default = "default_cooldown_minutes")]
112 pub cooldown_minutes: u64,
113 #[serde(default = "default_correction_detection")]
114 pub correction_detection: bool,
115 #[serde(default = "default_correction_confidence_threshold")]
116 pub correction_confidence_threshold: f32,
117 #[serde(default)]
119 pub detector_mode: DetectorMode,
120 #[serde(default)]
122 pub judge_model: String,
123 #[serde(default)]
128 pub feedback_provider: String,
129 #[serde(default = "default_judge_adaptive_low")]
131 pub judge_adaptive_low: f32,
132 #[serde(default = "default_judge_adaptive_high")]
134 pub judge_adaptive_high: f32,
135 #[serde(default = "default_correction_recall_limit")]
136 pub correction_recall_limit: u32,
137 #[serde(default = "default_correction_min_similarity")]
138 pub correction_min_similarity: f32,
139 #[serde(default = "default_auto_promote_min_uses")]
140 pub auto_promote_min_uses: u32,
141 #[serde(default = "default_auto_promote_threshold")]
142 pub auto_promote_threshold: f64,
143 #[serde(default = "default_auto_demote_min_uses")]
144 pub auto_demote_min_uses: u32,
145 #[serde(default = "default_auto_demote_threshold")]
146 pub auto_demote_threshold: f64,
147}
148
149impl Default for LearningConfig {
150 fn default() -> Self {
151 Self {
152 enabled: false,
153 auto_activate: false,
154 min_failures: default_min_failures(),
155 improve_threshold: default_improve_threshold(),
156 rollback_threshold: default_rollback_threshold(),
157 min_evaluations: default_min_evaluations(),
158 max_versions: default_max_versions(),
159 cooldown_minutes: default_cooldown_minutes(),
160 correction_detection: default_correction_detection(),
161 correction_confidence_threshold: default_correction_confidence_threshold(),
162 detector_mode: DetectorMode::default(),
163 judge_model: String::new(),
164 feedback_provider: String::new(),
165 judge_adaptive_low: default_judge_adaptive_low(),
166 judge_adaptive_high: default_judge_adaptive_high(),
167 correction_recall_limit: default_correction_recall_limit(),
168 correction_min_similarity: default_correction_min_similarity(),
169 auto_promote_min_uses: default_auto_promote_min_uses(),
170 auto_promote_threshold: default_auto_promote_threshold(),
171 auto_demote_min_uses: default_auto_demote_min_uses(),
172 auto_demote_threshold: default_auto_demote_threshold(),
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn detector_mode_default_is_regex() {
183 assert_eq!(DetectorMode::default(), DetectorMode::Regex);
184 }
185
186 #[test]
187 fn detector_mode_serde_roundtrip() {
188 for (mode, expected_str) in [
189 (DetectorMode::Regex, "\"regex\""),
190 (DetectorMode::Judge, "\"judge\""),
191 (DetectorMode::Model, "\"model\""),
192 ] {
193 let serialized = serde_json::to_string(&mode).unwrap();
194 assert_eq!(serialized, expected_str, "serialize {mode:?}");
195 let deserialized: DetectorMode = serde_json::from_str(&serialized).unwrap();
196 assert_eq!(deserialized, mode, "deserialize {mode:?}");
197 }
198 }
199
200 #[test]
201 fn learning_config_default_detector_mode_is_regex() {
202 let cfg = LearningConfig::default();
203 assert_eq!(cfg.detector_mode, DetectorMode::Regex);
204 }
205
206 #[test]
207 fn learning_config_default_feedback_provider_is_empty() {
208 let cfg = LearningConfig::default();
209 assert!(cfg.feedback_provider.is_empty());
210 }
211
212 #[test]
213 fn learning_config_deserialize_model_mode() {
214 let toml = r#"detector_mode = "model"
215feedback_provider = "fast""#;
216 let cfg: LearningConfig = toml::from_str(toml).unwrap();
217 assert_eq!(cfg.detector_mode, DetectorMode::Model);
218 assert_eq!(cfg.feedback_provider, "fast");
219 }
220
221 #[test]
222 fn learning_config_deserialize_empty_feedback_provider() {
223 let toml = r#"detector_mode = "model""#;
224 let cfg: LearningConfig = toml::from_str(toml).unwrap();
225 assert_eq!(cfg.detector_mode, DetectorMode::Model);
226 assert!(
227 cfg.feedback_provider.is_empty(),
228 "empty feedback_provider must default to empty string (fallback to primary)"
229 );
230 }
231
232 #[test]
233 fn learning_config_deserialize_empty_section_uses_defaults() {
234 let cfg: LearningConfig = toml::from_str("").unwrap();
235 assert!(!cfg.enabled);
236 assert_eq!(cfg.min_failures, 3);
237 assert_eq!(cfg.detector_mode, DetectorMode::Regex);
238 assert!(cfg.feedback_provider.is_empty());
239 }
240}