1use crate::baselines::BaselineSet;
2use crate::error::{DsfbSemiconductorError, Result};
3use crate::grammar::{GrammarSet, GrammarState};
4use crate::heuristics::{
5 dsa_contributing_motif_names, heuristic_policy_definition, FeaturePolicyOverride,
6 HeuristicAlertClass, HeuristicPolicyDefinition,
7};
8use crate::nominal::NominalModel;
9use crate::preprocessing::PreparedDataset;
10use crate::residual::ResidualSet;
11use crate::semiotics::feature_semantic_flags;
12use crate::signs::SignSet;
13use serde::{Deserialize, Serialize};
14use std::collections::BTreeMap;
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub struct DsaConfig {
18 pub window: usize,
19 pub persistence_runs: usize,
20 pub alert_tau: f64,
21 pub corroborating_feature_count_min: usize,
22}
23
24impl Default for DsaConfig {
25 fn default() -> Self {
26 Self {
27 window: 5,
28 persistence_runs: 2,
29 alert_tau: 2.0,
30 corroborating_feature_count_min: 2,
31 }
32 }
33}
34
35impl DsaConfig {
36 pub fn validate(&self) -> Result<()> {
37 if self.window == 0 {
38 return Err(DsfbSemiconductorError::DatasetFormat(
39 "dsa window must be positive".into(),
40 ));
41 }
42 if self.persistence_runs == 0 {
43 return Err(DsfbSemiconductorError::DatasetFormat(
44 "dsa persistence_runs must be positive".into(),
45 ));
46 }
47 if self.alert_tau <= 0.0 {
48 return Err(DsfbSemiconductorError::DatasetFormat(
49 "dsa alert_tau must be positive".into(),
50 ));
51 }
52 if self.corroborating_feature_count_min == 0 {
53 return Err(DsfbSemiconductorError::DatasetFormat(
54 "dsa corroborating_feature_count_min must be positive".into(),
55 ));
56 }
57 Ok(())
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
62pub struct DsaWeights {
63 pub boundary_density: f64,
64 pub drift_persistence: f64,
65 pub slew_density: f64,
66 pub ewma_occupancy: f64,
67 pub motif_recurrence: f64,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
71pub struct RecallRescueConfig {
72 pub enabled: bool,
73 pub priority_one_score_margin: f64,
74 pub priority_two_score_margin: f64,
75 pub priority_three_score_margin: f64,
76 pub minimum_ewma_occupancy: f64,
77 pub minimum_boundary_density: f64,
78 pub minimum_motif_recurrence: f64,
79}
80
81impl Default for RecallRescueConfig {
82 fn default() -> Self {
83 Self {
84 enabled: false,
85 priority_one_score_margin: 0.10,
86 priority_two_score_margin: 0.40,
87 priority_three_score_margin: 1.00,
88 minimum_ewma_occupancy: 0.65,
89 minimum_boundary_density: 0.40,
90 minimum_motif_recurrence: 0.40,
91 }
92 }
93}
94
95#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
96pub struct DsaPolicyRuntime {
97 pub feature_policy_overrides: Vec<FeaturePolicyOverride>,
98 pub recall_rescue: RecallRescueConfig,
99 pub semantic_rescue_support: BTreeMap<usize, Vec<bool>>,
100}
101
102impl Default for DsaWeights {
103 fn default() -> Self {
104 Self {
105 boundary_density: 1.0,
106 drift_persistence: 1.0,
107 slew_density: 1.0,
108 ewma_occupancy: 1.0,
109 motif_recurrence: 1.0,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
115pub enum DsaPolicyState {
116 Silent,
117 Watch,
118 Review,
119 Escalate,
120}
121
122impl DsaPolicyState {
123 fn is_review_or_escalate(self) -> bool {
124 matches!(self, Self::Review | Self::Escalate)
125 }
126
127 pub fn as_lowercase(self) -> &'static str {
128 match self {
129 Self::Silent => "silent",
130 Self::Watch => "watch",
131 Self::Review => "review",
132 Self::Escalate => "escalate",
133 }
134 }
135}
136
137#[derive(Debug, Clone, Serialize)]
138pub struct FeatureMotifPolicyContribution {
139 pub motif_name: String,
140 pub alert_class_default: HeuristicAlertClass,
141 pub watch_points: usize,
142 pub review_points: usize,
143 pub escalate_points: usize,
144 pub silent_suppression_points: usize,
145 pub pass_review_or_escalate_points: usize,
146 pub pre_failure_review_or_escalate_points: usize,
147}
148
149#[derive(Debug, Clone, Serialize)]
150pub struct DsaMotifPolicyContribution {
151 pub motif_name: String,
152 pub alert_class_default: HeuristicAlertClass,
153 pub watch_points: usize,
154 pub review_points: usize,
155 pub escalate_points: usize,
156 pub silent_suppression_points: usize,
157 pub pass_review_or_escalate_points: usize,
158 pub pre_failure_review_or_escalate_points: usize,
159}
160
161#[derive(Debug, Clone, Serialize)]
162pub struct DsaParameterManifest {
163 pub config: DsaConfig,
164 pub weights: DsaWeights,
165 pub feature_policy_override_count: usize,
166 pub feature_policy_override_summary: Vec<String>,
167 pub policy_engine_definition: String,
168 pub feature_level_state_definition: String,
169 pub primary_run_signal: String,
170 pub primary_run_signal_definition: String,
171 pub secondary_run_signal: String,
172 pub tertiary_run_signal: String,
173 pub strict_escalate_signal: String,
174 pub rolling_window_definition: String,
175 pub boundary_density_basis: String,
176 pub drift_persistence_definition: String,
177 pub slew_density_definition: String,
178 pub ewma_occupancy_formula: String,
179 pub motif_names_used_for_recurrence: Vec<String>,
180 pub directional_consistency_rule: String,
181 pub silence_rule: String,
182 pub corroboration_rule: String,
183 pub recall_rescue_definition: String,
184 pub recall_tolerance_runs_for_primary_success: usize,
185 pub primary_success_condition_definition: String,
186 pub optimization_priority_order: Vec<String>,
187}
188
189#[derive(Debug, Clone, Serialize)]
190pub struct DsaFeatureTrace {
191 pub feature_index: usize,
192 pub feature_name: String,
193 pub boundary_basis_hit: Vec<bool>,
194 pub drift_outward_hit: Vec<bool>,
195 pub slew_hit: Vec<bool>,
196 pub motif_hit: Vec<bool>,
197 pub boundary_density_w: Vec<f64>,
198 pub drift_persistence_w: Vec<f64>,
199 pub slew_density_w: Vec<f64>,
200 pub ewma_occupancy_w: Vec<f64>,
201 pub motif_recurrence_w: Vec<f64>,
202 pub fragmentation_proxy_w: Vec<f64>,
203 pub consistent: Vec<bool>,
204 pub dsa_score: Vec<f64>,
205 pub dsa_active: Vec<bool>,
206 pub numeric_dsa_alert: Vec<bool>,
207 pub dsa_alert: Vec<bool>,
208 pub resolved_alert_class: Vec<HeuristicAlertClass>,
209 pub policy_state: Vec<DsaPolicyState>,
210 pub policy_suppressed_to_silent: Vec<bool>,
211 pub rescue_transition: Vec<String>,
212 pub rescued_to_review: Vec<bool>,
213 pub motif_policy_contributions: Vec<FeatureMotifPolicyContribution>,
214}
215
216#[derive(Debug, Clone, Serialize)]
217pub struct DsaRunSignals {
218 pub primary_run_signal: String,
219 pub corroborating_feature_count_min: usize,
220 pub primary_run_alert: Vec<bool>,
221 pub any_feature_dsa_alert: Vec<bool>,
222 pub any_feature_raw_violation: Vec<bool>,
223 pub feature_count_dsa_alert: Vec<usize>,
224 pub watch_feature_count: Vec<usize>,
225 pub review_feature_count: Vec<usize>,
226 pub escalate_feature_count: Vec<usize>,
227 pub strict_escalate_run_alert: Vec<bool>,
228 pub numeric_primary_run_alert: Vec<bool>,
229 pub numeric_feature_count_dsa_alert: Vec<usize>,
230}
231
232#[derive(Debug, Clone, Serialize)]
233pub struct DsaEpisodeSummary {
234 pub primary_signal: String,
235 pub raw_boundary_episode_count: usize,
236 pub dsa_episode_count: usize,
237 pub dsa_episodes_preceding_failure: usize,
238 pub mean_dsa_episode_length_runs: Option<f64>,
239 pub max_dsa_episode_length_runs: usize,
240 pub compression_ratio: Option<f64>,
241 pub precursor_quality: Option<f64>,
242 pub non_escalating_dsa_episode_fraction: Option<f64>,
243}
244
245#[derive(Debug, Clone, Serialize)]
246pub struct DsaSignalSummary {
247 pub config: DsaConfig,
248 pub weights: DsaWeights,
249 pub primary_run_signal: String,
250 pub analyzable_feature_count: usize,
251 pub alert_point_count: usize,
252 pub alert_run_count: usize,
253 pub numeric_alert_point_count: usize,
254 pub numeric_alert_run_count: usize,
255 pub watch_point_count: usize,
256 pub review_point_count: usize,
257 pub escalate_point_count: usize,
258 pub silenced_point_count: usize,
259 pub rescued_point_count: usize,
260 pub rescued_watch_to_review_points: usize,
261 pub rescued_review_to_escalate_points: usize,
262 pub failure_runs: usize,
263 pub failure_run_recall: usize,
264 pub failure_run_recall_rate: f64,
265 pub numeric_primary_failure_run_recall: usize,
266 pub mean_lead_time_runs: Option<f64>,
267 pub median_lead_time_runs: Option<f64>,
268 pub pass_run_nuisance_proxy: f64,
269 pub numeric_primary_pass_run_nuisance_proxy: f64,
270 pub mean_lead_delta_vs_cusum_runs: Option<f64>,
271 pub mean_lead_delta_vs_run_energy_runs: Option<f64>,
272 pub mean_lead_delta_vs_pca_fdc_runs: Option<f64>,
273 pub mean_lead_delta_vs_threshold_runs: Option<f64>,
274 pub mean_lead_delta_vs_ewma_runs: Option<f64>,
275 pub raw_boundary_nuisance_proxy: f64,
276 pub raw_boundary_episode_count: usize,
277 pub dsa_episode_count: usize,
278 pub dsa_episodes_preceding_failure: usize,
279 pub mean_dsa_episode_length_runs: Option<f64>,
280 pub max_dsa_episode_length_runs: usize,
281 pub compression_ratio: Option<f64>,
282 pub precursor_quality: Option<f64>,
283 pub non_escalating_dsa_episode_fraction: Option<f64>,
284 pub threshold_recall_gate_passed: bool,
285 pub boundary_nuisance_gate_passed: bool,
286 pub primary_success_condition_met: bool,
287 pub any_metric_improved: bool,
288 pub validation_passed: bool,
289 pub success_condition_failures: Vec<String>,
290 pub validation_failures: Vec<String>,
291}
292
293#[derive(Debug, Clone, Serialize)]
294pub struct SignalComparisonRow {
295 pub signal: String,
296 pub failure_run_recall: usize,
297 pub failure_runs: usize,
298 pub failure_run_recall_rate: f64,
299 pub mean_lead_time_runs: Option<f64>,
300 pub median_lead_time_runs: Option<f64>,
301 pub pass_run_nuisance_proxy: f64,
302 pub mean_lead_delta_vs_cusum_runs: Option<f64>,
303 pub mean_lead_delta_vs_run_energy_runs: Option<f64>,
304 pub mean_lead_delta_vs_pca_fdc_runs: Option<f64>,
305 pub mean_lead_delta_vs_threshold_runs: Option<f64>,
306 pub mean_lead_delta_vs_ewma_runs: Option<f64>,
307}
308
309#[derive(Debug, Clone, Serialize)]
310pub struct DsaComponentContribution {
311 pub component: String,
312 pub mean_value_on_alert_points: f64,
313 pub mean_value_on_all_points: f64,
314 pub total_value_on_alert_points: f64,
315}
316
317#[derive(Debug, Clone, Serialize)]
318pub struct DsaVsBaselinesSummary {
319 pub dataset: String,
320 pub primary_run_signal: String,
321 pub dsa: SignalComparisonRow,
322 pub numeric_dsa: SignalComparisonRow,
323 pub threshold: SignalComparisonRow,
324 pub ewma: SignalComparisonRow,
325 pub cusum: SignalComparisonRow,
326 pub run_energy: SignalComparisonRow,
327 pub pca_fdc: SignalComparisonRow,
328 pub dsfb_violation: SignalComparisonRow,
329 pub dsfb_raw_boundary: SignalComparisonRow,
330 pub episode_summary: DsaEpisodeSummary,
331 pub failure_recall_delta_vs_threshold: i64,
332 pub failure_recall_delta_vs_ewma: i64,
333 pub failure_recall_delta_vs_cusum: i64,
334 pub failure_recall_delta_vs_run_energy: i64,
335 pub failure_recall_delta_vs_pca_fdc: i64,
336 pub failure_recall_delta_vs_violation: i64,
337 pub pass_run_nuisance_delta_vs_threshold: f64,
338 pub pass_run_nuisance_delta_vs_ewma: f64,
339 pub pass_run_nuisance_delta_vs_violation: f64,
340 pub pass_run_nuisance_delta_vs_cusum: f64,
341 pub pass_run_nuisance_delta_vs_run_energy: f64,
342 pub pass_run_nuisance_delta_vs_pca_fdc: f64,
343 pub pass_run_nuisance_delta_vs_raw_boundary: f64,
344 pub pass_run_nuisance_delta_vs_numeric_dsa: f64,
345 pub precursor_quality: Option<f64>,
346 pub dsa_episodes_preceding_failure: usize,
347 pub component_contributions: Vec<DsaComponentContribution>,
348 pub motif_policy_contributions: Vec<DsaMotifPolicyContribution>,
349 pub policy_vs_numeric_recall_delta: i64,
350 pub watch_point_count: usize,
351 pub review_point_count: usize,
352 pub escalate_point_count: usize,
353 pub silenced_point_count: usize,
354 pub nuisance_improved: bool,
355 pub lead_time_improved: bool,
356 pub recall_preserved: bool,
357 pub compression_improved: bool,
358 pub nothing_improved: bool,
359 pub threshold_recall_gate_passed: bool,
360 pub boundary_nuisance_gate_passed: bool,
361 pub primary_success_condition_met: bool,
362 pub any_metric_improved: bool,
363 pub validation_passed: bool,
364 pub success_condition_failures: Vec<String>,
365 pub validation_failures: Vec<String>,
366 pub conclusion: String,
367}
368
369#[derive(Debug, Clone, Serialize)]
370pub struct PerFailureRunDsaSignal {
371 pub failure_run_index: usize,
372 pub failure_timestamp: String,
373 pub earliest_dsa_run: Option<usize>,
374 pub earliest_primary_source: Option<String>,
375 pub earliest_dsa_feature_index: Option<usize>,
376 pub earliest_dsa_feature_name: Option<String>,
377 pub dsa_lead_runs: Option<usize>,
378 pub threshold_lead_runs: Option<usize>,
379 pub ewma_lead_runs: Option<usize>,
380 pub cusum_lead_runs: Option<usize>,
381 pub run_energy_lead_runs: Option<usize>,
382 pub pca_fdc_lead_runs: Option<usize>,
383 pub dsa_minus_cusum_delta_runs: Option<i64>,
384 pub dsa_minus_run_energy_delta_runs: Option<i64>,
385 pub dsa_minus_pca_fdc_delta_runs: Option<i64>,
386 pub dsa_minus_threshold_delta_runs: Option<i64>,
387 pub dsa_minus_ewma_delta_runs: Option<i64>,
388 pub dsa_alerting_feature_count: usize,
389 pub max_dsa_score_in_lookback: Option<f64>,
390 pub max_dsa_score_feature_index: Option<usize>,
391 pub max_dsa_score_feature_name: Option<String>,
392 pub max_dsa_score_run_index: Option<usize>,
393 pub max_dsa_score_boundary_density_w: Option<f64>,
394 pub max_dsa_score_drift_persistence_w: Option<f64>,
395 pub max_dsa_score_slew_density_w: Option<f64>,
396 pub max_dsa_score_ewma_occupancy_w: Option<f64>,
397 pub max_dsa_score_motif_recurrence_w: Option<f64>,
398 pub max_dsa_score_fragmentation_proxy_w: Option<f64>,
399 pub max_dsa_score_consistent: Option<bool>,
400 pub max_dsa_score_policy_state: Option<String>,
401 pub max_dsa_score_resolved_alert_class: Option<String>,
402 pub max_dsa_score_numeric_dsa_alert: Option<bool>,
403 pub max_dsa_score_dsa_alert: Option<bool>,
404 pub max_dsa_score_policy_suppressed: Option<bool>,
405 pub max_dsa_score_rescue_transition: Option<String>,
406}
407
408#[derive(Debug, Clone, Serialize)]
409pub struct DsaEvaluation {
410 pub traces: Vec<DsaFeatureTrace>,
411 pub run_signals: DsaRunSignals,
412 pub episode_summary: DsaEpisodeSummary,
413 pub parameter_manifest: DsaParameterManifest,
414 pub policy_runtime: DsaPolicyRuntime,
415 pub summary: DsaSignalSummary,
416 pub comparison_summary: DsaVsBaselinesSummary,
417 pub motif_policy_contributions: Vec<DsaMotifPolicyContribution>,
418 pub per_failure_run_signals: Vec<PerFailureRunDsaSignal>,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct DsaCalibrationGrid {
423 pub window: Vec<usize>,
424 pub persistence_runs: Vec<usize>,
425 pub alert_tau: Vec<f64>,
426 pub corroborating_feature_count_min: Vec<usize>,
427}
428
429impl DsaCalibrationGrid {
430 pub fn bounded_default() -> Self {
431 Self {
432 window: vec![5, 10, 15],
433 persistence_runs: vec![2, 3, 4],
434 alert_tau: vec![2.0, 2.5, 3.0],
435 corroborating_feature_count_min: vec![2, 3, 5],
436 }
437 }
438
439 pub fn validate(&self) -> Result<()> {
440 if self.grid_point_count() == 0 {
441 return Err(DsfbSemiconductorError::DatasetFormat(
442 "dsa calibration grid must contain at least one point".into(),
443 ));
444 }
445 if self.grid_point_count() > 256 {
446 return Err(DsfbSemiconductorError::DatasetFormat(format!(
447 "dsa calibration grid is too large ({})",
448 self.grid_point_count()
449 )));
450 }
451 Ok(())
452 }
453
454 pub fn grid_point_count(&self) -> usize {
455 [
456 self.window.len(),
457 self.persistence_runs.len(),
458 self.alert_tau.len(),
459 self.corroborating_feature_count_min.len(),
460 ]
461 .into_iter()
462 .product()
463 }
464
465 pub fn expand(&self) -> Vec<DsaConfig> {
466 let mut out = Vec::with_capacity(self.grid_point_count());
467 for &window in &self.window {
468 for &persistence_runs in &self.persistence_runs {
469 for &alert_tau in &self.alert_tau {
470 for &corroborating_feature_count_min in &self.corroborating_feature_count_min {
471 out.push(DsaConfig {
472 window,
473 persistence_runs,
474 alert_tau,
475 corroborating_feature_count_min,
476 });
477 }
478 }
479 }
480 }
481 out
482 }
483}
484
485#[derive(Debug, Clone, Serialize)]
486pub struct DsaCalibrationRow {
487 pub config_id: usize,
488 pub primary_run_signal: String,
489 pub window: usize,
490 pub persistence_runs: usize,
491 pub alert_tau: f64,
492 pub corroborating_feature_count_min: usize,
493 pub failure_run_recall: usize,
494 pub failure_runs: usize,
495 pub threshold_failure_run_recall: usize,
496 pub ewma_failure_run_recall: usize,
497 pub failure_recall_delta_vs_threshold: i64,
498 pub failure_recall_delta_vs_ewma: i64,
499 pub mean_lead_time_runs: Option<f64>,
500 pub median_lead_time_runs: Option<f64>,
501 pub pass_run_nuisance_proxy: f64,
502 pub mean_lead_delta_vs_cusum_runs: Option<f64>,
503 pub mean_lead_delta_vs_run_energy_runs: Option<f64>,
504 pub mean_lead_delta_vs_pca_fdc_runs: Option<f64>,
505 pub mean_lead_delta_vs_threshold_runs: Option<f64>,
506 pub mean_lead_delta_vs_ewma_runs: Option<f64>,
507 pub pass_run_nuisance_delta_vs_cusum: f64,
508 pub pass_run_nuisance_delta_vs_run_energy: f64,
509 pub pass_run_nuisance_delta_vs_pca_fdc: f64,
510 pub pass_run_nuisance_delta_vs_threshold: f64,
511 pub pass_run_nuisance_delta_vs_ewma: f64,
512 pub pass_run_nuisance_delta_vs_raw_boundary: f64,
513 pub raw_boundary_episode_count: usize,
514 pub dsa_episode_count: usize,
515 pub dsa_episodes_preceding_failure: usize,
516 pub mean_dsa_episode_length_runs: Option<f64>,
517 pub max_dsa_episode_length_runs: usize,
518 pub compression_ratio: Option<f64>,
519 pub precursor_quality: Option<f64>,
520 pub non_escalating_dsa_episode_fraction: Option<f64>,
521 pub nuisance_improved: bool,
522 pub lead_time_improved: bool,
523 pub recall_preserved: bool,
524 pub compression_improved: bool,
525 pub any_metric_improved: bool,
526 pub nothing_improved: bool,
527 pub threshold_recall_gate_passed: bool,
528 pub boundary_nuisance_gate_passed: bool,
529 pub primary_success_condition_met: bool,
530 pub validation_passed: bool,
531 pub success_condition_failures: String,
532 pub validation_failures: String,
533}
534
535#[derive(Debug, Clone, Serialize)]
536pub struct DsaCorroborationSummary {
537 pub corroborating_feature_count_min: usize,
538 pub representative_row: Option<DsaCalibrationRow>,
539}
540
541#[derive(Debug, Clone, Serialize)]
542pub struct DsaGridSummary {
543 pub grid_point_count: usize,
544 pub optimization_priority_order: Vec<String>,
545 pub primary_success_condition_definition: String,
546 pub success_row_count: usize,
547 pub any_success_row: bool,
548 pub closest_to_success: Option<DsaCalibrationRow>,
549 pub best_success_row: Option<DsaCalibrationRow>,
550 pub best_precursor_quality_row: Option<DsaCalibrationRow>,
551 pub corroboration_summaries: Vec<DsaCorroborationSummary>,
552 pub cross_feature_corroboration_effect: String,
553 pub limiting_factor: String,
554}
555
556const DSA_PRIMARY_SUCCESS_RECALL_TOLERANCE_RUNS: usize = 1;
557const POLICY_LOCAL_EWMA_CORROBORATION_MIN: f64 = 0.75;
558
559#[derive(Debug, Clone)]
560struct MotifContributionState {
561 motif_name: &'static str,
562 default_alert_class: HeuristicAlertClass,
563 contribution_state: DsaPolicyState,
564 fragmentation_proxy: f64,
565 suppressed_to_silent: bool,
566}
567
568fn dsa_optimization_priority_order() -> Vec<String> {
569 vec![
570 "1. Reduce nuisance vs raw DSFB boundary".into(),
571 "2. Reduce nuisance vs EWMA".into(),
572 "3. Preserve threshold recall".into(),
573 "4. Improve lead time without sacrificing nuisance".into(),
574 ]
575}
576
577fn dsa_primary_success_condition_definition() -> String {
578 format!(
579 "DSA is considered successful only if pass-run nuisance is lower than EWMA nuisance and failure recall is within {} run(s) of threshold recall.",
580 DSA_PRIMARY_SUCCESS_RECALL_TOLERANCE_RUNS
581 )
582}
583
584pub fn evaluate_dsa(
585 dataset: &PreparedDataset,
586 nominal: &NominalModel,
587 residuals: &ResidualSet,
588 signs: &SignSet,
589 baselines: &BaselineSet,
590 grammar: &GrammarSet,
591 config: &DsaConfig,
592 pre_failure_lookback_runs: usize,
593) -> Result<DsaEvaluation> {
594 evaluate_dsa_with_policy(
595 dataset,
596 nominal,
597 residuals,
598 signs,
599 baselines,
600 grammar,
601 config,
602 pre_failure_lookback_runs,
603 &DsaPolicyRuntime::default(),
604 )
605}
606
607pub fn evaluate_dsa_with_policy(
608 dataset: &PreparedDataset,
609 nominal: &NominalModel,
610 residuals: &ResidualSet,
611 signs: &SignSet,
612 baselines: &BaselineSet,
613 grammar: &GrammarSet,
614 config: &DsaConfig,
615 pre_failure_lookback_runs: usize,
616 policy_runtime: &DsaPolicyRuntime,
617) -> Result<DsaEvaluation> {
618 config.validate()?;
619 let weights = DsaWeights::default();
620 let run_count = dataset.labels.len();
621 let failure_indices = dataset
622 .labels
623 .iter()
624 .enumerate()
625 .filter_map(|(index, label)| (*label == 1).then_some(index))
626 .collect::<Vec<_>>();
627 let failure_window_mask =
628 build_failure_window_mask(run_count, &failure_indices, pre_failure_lookback_runs);
629 let feature_policy_overrides = policy_runtime
630 .feature_policy_overrides
631 .iter()
632 .map(|override_entry| (override_entry.feature_index, override_entry))
633 .collect::<BTreeMap<_, _>>();
634 let mut traces = Vec::with_capacity(residuals.traces.len());
635
636 for (((residual_trace, sign_trace), ewma_trace), grammar_trace) in residuals
637 .traces
638 .iter()
639 .zip(&signs.traces)
640 .zip(&baselines.ewma)
641 .zip(&grammar.traces)
642 {
643 let feature = &nominal.features[residual_trace.feature_index];
644 let feature_override = feature_policy_overrides
645 .get(&feature.feature_index)
646 .copied();
647 let semantic_rescue_support = policy_runtime
648 .semantic_rescue_support
649 .get(&feature.feature_index)
650 .cloned()
651 .unwrap_or_else(|| vec![false; run_count]);
652 if !feature.analyzable {
653 traces.push(empty_trace(
654 feature.feature_index,
655 &feature.feature_name,
656 run_count,
657 ));
658 continue;
659 }
660
661 let boundary_basis_hit = grammar_trace
662 .raw_states
663 .iter()
664 .map(|state| *state == GrammarState::Boundary)
665 .collect::<Vec<_>>();
666 let raw_violation_hit = grammar_trace
667 .raw_states
668 .iter()
669 .map(|state| *state == GrammarState::Violation)
670 .collect::<Vec<_>>();
671 let drift_outward_hit = sign_trace
672 .drift
673 .iter()
674 .map(|drift| *drift >= sign_trace.drift_threshold)
675 .collect::<Vec<_>>();
676 let slew_hit = sign_trace
677 .slew
678 .iter()
679 .map(|slew| slew.abs() >= sign_trace.slew_threshold)
680 .collect::<Vec<_>>();
681 let semantic_flags =
682 feature_semantic_flags(residual_trace, sign_trace, grammar_trace, feature.rho);
683 let motif_flags = dsa_contributing_motif_names()
684 .iter()
685 .map(|&motif_name| {
686 let flags = semantic_flags
687 .semantic_flags
688 .get(motif_name)
689 .cloned()
690 .unwrap_or_else(|| vec![false; run_count]);
691 (motif_name, flags)
692 })
693 .collect::<Vec<_>>();
694 let motif_hit = semantic_flags.any_semantic_match;
695 let ewma_normalized = ewma_trace
696 .ewma
697 .iter()
698 .map(|value| normalize_to_threshold(*value, ewma_trace.threshold))
699 .collect::<Vec<_>>();
700
701 let boundary_prefix = bool_prefix_sum(&boundary_basis_hit);
702 let raw_violation_prefix = bool_prefix_sum(&raw_violation_hit);
703 let drift_prefix = bool_prefix_sum(&drift_outward_hit);
704 let slew_prefix = bool_prefix_sum(&slew_hit);
705 let motif_prefix = bool_prefix_sum(&motif_hit);
706 let non_imputed_prefix = bool_prefix_sum(
707 &residual_trace
708 .is_imputed
709 .iter()
710 .map(|is_imputed| !*is_imputed)
711 .collect::<Vec<_>>(),
712 );
713
714 let mut boundary_density_w = Vec::with_capacity(run_count);
715 let mut drift_persistence_w = Vec::with_capacity(run_count);
716 let mut slew_density_w = Vec::with_capacity(run_count);
717 let mut ewma_occupancy_w = Vec::with_capacity(run_count);
718 let mut motif_recurrence_w = Vec::with_capacity(run_count);
719 let mut consistent = Vec::with_capacity(run_count);
720 let mut dsa_score = Vec::with_capacity(run_count);
721 let mut dsa_active = Vec::with_capacity(run_count);
722
723 for run_index in 0..run_count {
724 let start = run_index.saturating_sub(config.window.saturating_sub(1));
725 let window_len = (run_index - start + 1) as f64;
726 let boundary_density =
727 window_fraction_nonimputed(&boundary_prefix, &non_imputed_prefix, start, run_index);
728 let drift_persistence =
729 window_fraction_nonimputed(&drift_prefix, &non_imputed_prefix, start, run_index);
730 let slew_density = window_fraction(&slew_prefix, start, run_index, window_len);
731 let ewma_occupancy = window_mean(&ewma_normalized, start, run_index);
732 let motif_recurrence = window_fraction(&motif_prefix, start, run_index, window_len);
733 let consistent_window = window_is_consistent(
734 &sign_trace.drift,
735 sign_trace.drift_threshold,
736 start,
737 run_index,
738 );
739 let score = weights.boundary_density * boundary_density
740 + weights.drift_persistence * drift_persistence
741 + weights.slew_density * slew_density
742 + weights.ewma_occupancy * ewma_occupancy
743 + weights.motif_recurrence * motif_recurrence;
744
745 boundary_density_w.push(boundary_density);
746 drift_persistence_w.push(drift_persistence);
747 slew_density_w.push(slew_density);
748 ewma_occupancy_w.push(ewma_occupancy);
749 motif_recurrence_w.push(motif_recurrence);
750 consistent.push(consistent_window);
751 dsa_score.push(score);
752 dsa_active.push(score >= config.alert_tau && consistent_window);
753 }
754
755 let numeric_dsa_alert = persistence_mask(&dsa_active, config.persistence_runs);
756 let mut dsa_alert = Vec::with_capacity(run_count);
757 let mut fragmentation_proxy_w = Vec::with_capacity(run_count);
758 let mut resolved_alert_class = Vec::with_capacity(run_count);
759 let mut policy_state = Vec::with_capacity(run_count);
760 let mut policy_suppressed_to_silent = Vec::with_capacity(run_count);
761 let mut rescue_transition = Vec::with_capacity(run_count);
762 let mut rescued_to_review = Vec::with_capacity(run_count);
763 let mut motif_policy_contributions = dsa_contributing_motif_names()
764 .iter()
765 .map(|motif_name| {
766 let policy = heuristic_policy_definition(motif_name)
767 .unwrap_or_else(|| panic!("missing heuristic policy for {motif_name}"));
768 (
769 (*motif_name).to_string(),
770 FeatureMotifPolicyContribution {
771 motif_name: (*motif_name).into(),
772 alert_class_default: policy.alert_class_default,
773 watch_points: 0,
774 review_points: 0,
775 escalate_points: 0,
776 silent_suppression_points: 0,
777 pass_review_or_escalate_points: 0,
778 pre_failure_review_or_escalate_points: 0,
779 },
780 )
781 })
782 .collect::<BTreeMap<_, _>>();
783
784 for run_index in 0..run_count {
785 let start = run_index.saturating_sub(config.window.saturating_sub(1));
786 let raw_violation_recent = window_count(&raw_violation_prefix, start, run_index) > 0;
787 let local_corroboration = raw_violation_recent
788 || ewma_occupancy_w[run_index] >= POLICY_LOCAL_EWMA_CORROBORATION_MIN;
789 let contributions = motif_flags
790 .iter()
791 .filter_map(|(motif_name, flags)| {
792 let policy = heuristic_policy_definition(motif_name)?;
793 let contribution = motif_contribution_state(
794 policy,
795 feature_override,
796 flags,
797 run_index,
798 dsa_active[run_index],
799 numeric_dsa_alert[run_index],
800 local_corroboration,
801 raw_violation_recent,
802 )?;
803 Some(contribution)
804 })
805 .collect::<Vec<_>>();
806
807 let dominant_class = dominant_alert_class(&contributions);
808 let dominant_state = dominant_policy_state(&contributions);
809 let fragmentation = contributions
810 .iter()
811 .map(|contribution| contribution.fragmentation_proxy)
812 .fold(0.0, f64::max);
813 let silenced =
814 numeric_dsa_alert[run_index] && matches!(dominant_state, DsaPolicyState::Silent);
815
816 for contribution in &contributions {
817 if let Some(row) = motif_policy_contributions.get_mut(contribution.motif_name) {
818 match contribution.contribution_state {
819 DsaPolicyState::Silent => {
820 if contribution.suppressed_to_silent {
821 row.silent_suppression_points += 1;
822 }
823 }
824 DsaPolicyState::Watch => row.watch_points += 1,
825 DsaPolicyState::Review => row.review_points += 1,
826 DsaPolicyState::Escalate => row.escalate_points += 1,
827 }
828 if contribution.contribution_state.is_review_or_escalate() {
829 if dataset.labels[run_index] == -1 {
830 row.pass_review_or_escalate_points += 1;
831 }
832 if failure_window_mask[run_index] {
833 row.pre_failure_review_or_escalate_points += 1;
834 }
835 }
836 }
837 }
838
839 dsa_alert.push(dominant_state.is_review_or_escalate());
840 fragmentation_proxy_w.push(fragmentation);
841 resolved_alert_class.push(dominant_class);
842 policy_state.push(dominant_state);
843 policy_suppressed_to_silent.push(silenced);
844 rescue_transition.push("none".into());
845 rescued_to_review.push(false);
846 }
847
848 if policy_runtime.recall_rescue.enabled {
849 for run_index in 0..run_count {
850 let transition = apply_recall_rescue(
851 feature_override,
852 &policy_runtime.recall_rescue,
853 config,
854 &resolved_alert_class,
855 &mut policy_state,
856 &mut dsa_alert,
857 &semantic_rescue_support,
858 &fragmentation_proxy_w,
859 &dsa_score,
860 &boundary_density_w,
861 &ewma_occupancy_w,
862 &motif_recurrence_w,
863 &consistent,
864 run_index,
865 );
866 if let Some(transition_name) = transition {
867 rescue_transition[run_index] = transition_name.to_string();
868 rescued_to_review[run_index] = transition_name == "watch_to_review"
869 || transition_name == "silent_to_review";
870 }
871 }
872 }
873
874 traces.push(DsaFeatureTrace {
875 feature_index: feature.feature_index,
876 feature_name: feature.feature_name.clone(),
877 boundary_basis_hit,
878 drift_outward_hit,
879 slew_hit,
880 motif_hit,
881 boundary_density_w,
882 drift_persistence_w,
883 slew_density_w,
884 ewma_occupancy_w,
885 motif_recurrence_w,
886 fragmentation_proxy_w,
887 consistent,
888 dsa_score,
889 dsa_active,
890 numeric_dsa_alert,
891 dsa_alert,
892 resolved_alert_class,
893 policy_state,
894 policy_suppressed_to_silent,
895 rescue_transition,
896 rescued_to_review,
897 motif_policy_contributions: motif_policy_contributions.into_values().collect(),
898 });
899 }
900
901 Ok(assemble_dsa_evaluation(
902 dataset,
903 nominal,
904 residuals,
905 baselines,
906 grammar,
907 traces,
908 config,
909 &weights,
910 pre_failure_lookback_runs,
911 policy_runtime,
912 None,
913 None,
914 ))
915}
916
917pub fn project_dsa_to_cohort(
918 dataset: &PreparedDataset,
919 nominal: &NominalModel,
920 residuals: &ResidualSet,
921 baselines: &BaselineSet,
922 grammar: &GrammarSet,
923 base_evaluation: &DsaEvaluation,
924 selected_feature_indices: &[usize],
925 corroborating_feature_count_min: usize,
926 pre_failure_lookback_runs: usize,
927 cohort_name: &str,
928) -> Result<DsaEvaluation> {
929 if corroborating_feature_count_min == 0 {
930 return Err(DsfbSemiconductorError::DatasetFormat(
931 "cohort corroborating_feature_count_min must be positive".into(),
932 ));
933 }
934
935 let mut selected_mask = vec![false; base_evaluation.traces.len()];
936 for &feature_index in selected_feature_indices {
937 if feature_index < selected_mask.len() {
938 selected_mask[feature_index] = true;
939 }
940 }
941
942 let mut traces = base_evaluation.traces.clone();
943 for (feature_index, trace) in traces.iter_mut().enumerate() {
944 if !selected_mask[feature_index] {
945 trace.dsa_score.fill(0.0);
946 trace.dsa_active.fill(false);
947 trace.numeric_dsa_alert.fill(false);
948 trace.dsa_alert.fill(false);
949 trace.resolved_alert_class.fill(HeuristicAlertClass::Silent);
950 trace.policy_state.fill(DsaPolicyState::Silent);
951 trace.policy_suppressed_to_silent.fill(false);
952 for contribution in &mut trace.motif_policy_contributions {
953 contribution.watch_points = 0;
954 contribution.review_points = 0;
955 contribution.escalate_points = 0;
956 contribution.silent_suppression_points = 0;
957 contribution.pass_review_or_escalate_points = 0;
958 contribution.pre_failure_review_or_escalate_points = 0;
959 }
960 }
961 }
962
963 let mut config = base_evaluation.parameter_manifest.config.clone();
964 config.corroborating_feature_count_min = corroborating_feature_count_min;
965 config.validate()?;
966
967 let mut evaluation = assemble_dsa_evaluation(
968 dataset,
969 nominal,
970 residuals,
971 baselines,
972 grammar,
973 traces,
974 &config,
975 &base_evaluation.parameter_manifest.weights,
976 pre_failure_lookback_runs,
977 &base_evaluation.policy_runtime,
978 Some(&selected_mask),
979 Some(selected_feature_indices.len()),
980 );
981
982 let primary_signal = format!(
983 "cohort {}: feature_count_review_or_escalate(k) >= {}",
984 cohort_name, corroborating_feature_count_min
985 );
986 evaluation.run_signals.primary_run_signal = primary_signal.clone();
987 evaluation.episode_summary.primary_signal = primary_signal.clone();
988 evaluation.summary.primary_run_signal = primary_signal.clone();
989 evaluation.summary.config.corroborating_feature_count_min = corroborating_feature_count_min;
990 evaluation.comparison_summary.primary_run_signal = primary_signal.clone();
991 evaluation
992 .parameter_manifest
993 .config
994 .corroborating_feature_count_min = corroborating_feature_count_min;
995 evaluation.parameter_manifest.primary_run_signal = primary_signal.clone();
996 evaluation.parameter_manifest.primary_run_signal_definition = format!(
997 "Primary run-level cohort DSA decision is {} over the deterministic selected feature cohort.",
998 primary_signal
999 );
1000 evaluation.parameter_manifest.corroboration_rule = format!(
1001 "RUN_LEVEL_DSA(k) is true only when the count of selected cohort features in Review or Escalate is at least {}.",
1002 corroborating_feature_count_min
1003 );
1004
1005 Ok(evaluation)
1006}
1007
1008pub fn run_dsa_calibration_grid(
1009 dataset: &PreparedDataset,
1010 nominal: &NominalModel,
1011 residuals: &ResidualSet,
1012 signs: &SignSet,
1013 baselines: &BaselineSet,
1014 grammar: &GrammarSet,
1015 grid: &DsaCalibrationGrid,
1016 pre_failure_lookback_runs: usize,
1017) -> Result<Vec<DsaCalibrationRow>> {
1018 grid.validate()?;
1019
1020 let mut rows = Vec::with_capacity(grid.grid_point_count());
1021 for (config_id, config) in grid.expand().into_iter().enumerate() {
1022 let evaluation = evaluate_dsa(
1023 dataset,
1024 nominal,
1025 residuals,
1026 signs,
1027 baselines,
1028 grammar,
1029 &config,
1030 pre_failure_lookback_runs,
1031 )?;
1032 rows.push(DsaCalibrationRow {
1033 config_id,
1034 primary_run_signal: evaluation.run_signals.primary_run_signal.clone(),
1035 window: config.window,
1036 persistence_runs: config.persistence_runs,
1037 alert_tau: config.alert_tau,
1038 corroborating_feature_count_min: config.corroborating_feature_count_min,
1039 failure_run_recall: evaluation.summary.failure_run_recall,
1040 failure_runs: evaluation.summary.failure_runs,
1041 threshold_failure_run_recall: evaluation
1042 .comparison_summary
1043 .threshold
1044 .failure_run_recall,
1045 ewma_failure_run_recall: evaluation.comparison_summary.ewma.failure_run_recall,
1046 failure_recall_delta_vs_threshold: evaluation
1047 .comparison_summary
1048 .failure_recall_delta_vs_threshold,
1049 failure_recall_delta_vs_ewma: evaluation
1050 .comparison_summary
1051 .failure_recall_delta_vs_ewma,
1052 mean_lead_time_runs: evaluation.summary.mean_lead_time_runs,
1053 median_lead_time_runs: evaluation.summary.median_lead_time_runs,
1054 pass_run_nuisance_proxy: evaluation.summary.pass_run_nuisance_proxy,
1055 mean_lead_delta_vs_cusum_runs: evaluation.summary.mean_lead_delta_vs_cusum_runs,
1056 mean_lead_delta_vs_run_energy_runs: evaluation
1057 .summary
1058 .mean_lead_delta_vs_run_energy_runs,
1059 mean_lead_delta_vs_pca_fdc_runs: evaluation.summary.mean_lead_delta_vs_pca_fdc_runs,
1060 mean_lead_delta_vs_threshold_runs: evaluation.summary.mean_lead_delta_vs_threshold_runs,
1061 mean_lead_delta_vs_ewma_runs: evaluation.summary.mean_lead_delta_vs_ewma_runs,
1062 pass_run_nuisance_delta_vs_cusum: evaluation
1063 .comparison_summary
1064 .pass_run_nuisance_delta_vs_cusum,
1065 pass_run_nuisance_delta_vs_run_energy: evaluation
1066 .comparison_summary
1067 .pass_run_nuisance_delta_vs_run_energy,
1068 pass_run_nuisance_delta_vs_pca_fdc: evaluation
1069 .comparison_summary
1070 .pass_run_nuisance_delta_vs_pca_fdc,
1071 pass_run_nuisance_delta_vs_threshold: evaluation
1072 .comparison_summary
1073 .pass_run_nuisance_delta_vs_threshold,
1074 pass_run_nuisance_delta_vs_ewma: evaluation
1075 .comparison_summary
1076 .pass_run_nuisance_delta_vs_ewma,
1077 pass_run_nuisance_delta_vs_raw_boundary: evaluation
1078 .comparison_summary
1079 .pass_run_nuisance_delta_vs_raw_boundary,
1080 raw_boundary_episode_count: evaluation.episode_summary.raw_boundary_episode_count,
1081 dsa_episode_count: evaluation.episode_summary.dsa_episode_count,
1082 dsa_episodes_preceding_failure: evaluation
1083 .episode_summary
1084 .dsa_episodes_preceding_failure,
1085 mean_dsa_episode_length_runs: evaluation.episode_summary.mean_dsa_episode_length_runs,
1086 max_dsa_episode_length_runs: evaluation.episode_summary.max_dsa_episode_length_runs,
1087 compression_ratio: evaluation.episode_summary.compression_ratio,
1088 precursor_quality: evaluation.episode_summary.precursor_quality,
1089 non_escalating_dsa_episode_fraction: evaluation
1090 .episode_summary
1091 .non_escalating_dsa_episode_fraction,
1092 nuisance_improved: evaluation.comparison_summary.nuisance_improved,
1093 lead_time_improved: evaluation.comparison_summary.lead_time_improved,
1094 recall_preserved: evaluation.comparison_summary.recall_preserved,
1095 compression_improved: evaluation.comparison_summary.compression_improved,
1096 any_metric_improved: evaluation.summary.any_metric_improved,
1097 nothing_improved: evaluation.comparison_summary.nothing_improved,
1098 threshold_recall_gate_passed: evaluation.summary.threshold_recall_gate_passed,
1099 boundary_nuisance_gate_passed: evaluation.summary.boundary_nuisance_gate_passed,
1100 primary_success_condition_met: evaluation.summary.primary_success_condition_met,
1101 validation_passed: evaluation.summary.validation_passed,
1102 success_condition_failures: evaluation.summary.success_condition_failures.join("; "),
1103 validation_failures: evaluation.summary.validation_failures.join("; "),
1104 });
1105 }
1106
1107 Ok(rows)
1108}
1109
1110fn empty_trace(feature_index: usize, feature_name: &str, run_count: usize) -> DsaFeatureTrace {
1111 DsaFeatureTrace {
1112 feature_index,
1113 feature_name: feature_name.into(),
1114 boundary_basis_hit: vec![false; run_count],
1115 drift_outward_hit: vec![false; run_count],
1116 slew_hit: vec![false; run_count],
1117 motif_hit: vec![false; run_count],
1118 boundary_density_w: vec![0.0; run_count],
1119 drift_persistence_w: vec![0.0; run_count],
1120 slew_density_w: vec![0.0; run_count],
1121 ewma_occupancy_w: vec![0.0; run_count],
1122 motif_recurrence_w: vec![0.0; run_count],
1123 fragmentation_proxy_w: vec![0.0; run_count],
1124 consistent: vec![true; run_count],
1125 dsa_score: vec![0.0; run_count],
1126 dsa_active: vec![false; run_count],
1127 numeric_dsa_alert: vec![false; run_count],
1128 dsa_alert: vec![false; run_count],
1129 resolved_alert_class: vec![HeuristicAlertClass::Silent; run_count],
1130 policy_state: vec![DsaPolicyState::Silent; run_count],
1131 policy_suppressed_to_silent: vec![false; run_count],
1132 rescue_transition: vec!["none".into(); run_count],
1133 rescued_to_review: vec![false; run_count],
1134 motif_policy_contributions: Vec::new(),
1135 }
1136}
1137
1138fn assemble_dsa_evaluation(
1139 dataset: &PreparedDataset,
1140 nominal: &NominalModel,
1141 residuals: &ResidualSet,
1142 baselines: &BaselineSet,
1143 grammar: &GrammarSet,
1144 traces: Vec<DsaFeatureTrace>,
1145 config: &DsaConfig,
1146 weights: &DsaWeights,
1147 pre_failure_lookback_runs: usize,
1148 policy_runtime: &DsaPolicyRuntime,
1149 raw_boundary_episode_feature_mask: Option<&[bool]>,
1150 analyzable_feature_count_override: Option<usize>,
1151) -> DsaEvaluation {
1152 let run_count = dataset.labels.len();
1153 let motif_names = dsa_contributing_motif_names()
1154 .iter()
1155 .map(|name| (*name).to_string())
1156 .collect::<Vec<_>>();
1157 let raw_boundary_run_signal = (0..run_count)
1158 .map(|run_index| {
1159 grammar
1160 .traces
1161 .iter()
1162 .any(|trace| trace.raw_states[run_index] == GrammarState::Boundary)
1163 })
1164 .collect::<Vec<_>>();
1165 let raw_violation_run_signal = (0..run_count)
1166 .map(|run_index| {
1167 grammar
1168 .traces
1169 .iter()
1170 .any(|trace| trace.raw_states[run_index] == GrammarState::Violation)
1171 })
1172 .collect::<Vec<_>>();
1173 let threshold_run_signal = (0..run_count)
1174 .map(|run_index| {
1175 residuals
1176 .traces
1177 .iter()
1178 .any(|trace| trace.threshold_alarm[run_index])
1179 })
1180 .collect::<Vec<_>>();
1181 let ewma_run_signal = (0..run_count)
1182 .map(|run_index| baselines.ewma.iter().any(|trace| trace.alarm[run_index]))
1183 .collect::<Vec<_>>();
1184 let cusum_run_signal = (0..run_count)
1185 .map(|run_index| baselines.cusum.iter().any(|trace| trace.alarm[run_index]))
1186 .collect::<Vec<_>>();
1187 let run_energy_run_signal = baselines.run_energy.alarm.clone();
1188 let pca_fdc_run_signal = baselines.pca_fdc.alarm.clone();
1189 let run_signals = build_run_signals(&traces, &raw_violation_run_signal, config, run_count);
1190
1191 let failure_indices = dataset
1192 .labels
1193 .iter()
1194 .enumerate()
1195 .filter_map(|(index, label)| (*label == 1).then_some(index))
1196 .collect::<Vec<_>>();
1197 let pass_indices = dataset
1198 .labels
1199 .iter()
1200 .enumerate()
1201 .filter_map(|(index, label)| (*label == -1).then_some(index))
1202 .collect::<Vec<_>>();
1203 let failure_window_mask =
1204 build_failure_window_mask(run_count, &failure_indices, pre_failure_lookback_runs);
1205
1206 let raw_boundary_leads = failure_indices
1207 .iter()
1208 .map(|&failure_index| {
1209 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1210 earliest_run_signal(&raw_boundary_run_signal, window_start, failure_index)
1211 .map(|run_index| failure_index - run_index)
1212 })
1213 .collect::<Vec<_>>();
1214 let raw_violation_leads = failure_indices
1215 .iter()
1216 .map(|&failure_index| {
1217 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1218 earliest_run_signal(&raw_violation_run_signal, window_start, failure_index)
1219 .map(|run_index| failure_index - run_index)
1220 })
1221 .collect::<Vec<_>>();
1222 let threshold_leads = failure_indices
1223 .iter()
1224 .map(|&failure_index| {
1225 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1226 earliest_run_signal(&threshold_run_signal, window_start, failure_index)
1227 .map(|run_index| failure_index - run_index)
1228 })
1229 .collect::<Vec<_>>();
1230 let ewma_leads = failure_indices
1231 .iter()
1232 .map(|&failure_index| {
1233 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1234 earliest_run_signal(&ewma_run_signal, window_start, failure_index)
1235 .map(|run_index| failure_index - run_index)
1236 })
1237 .collect::<Vec<_>>();
1238 let cusum_leads = failure_indices
1239 .iter()
1240 .map(|&failure_index| {
1241 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1242 earliest_run_signal(&cusum_run_signal, window_start, failure_index)
1243 .map(|run_index| failure_index - run_index)
1244 })
1245 .collect::<Vec<_>>();
1246 let run_energy_leads = failure_indices
1247 .iter()
1248 .map(|&failure_index| {
1249 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1250 earliest_run_signal(&run_energy_run_signal, window_start, failure_index)
1251 .map(|run_index| failure_index - run_index)
1252 })
1253 .collect::<Vec<_>>();
1254 let pca_fdc_leads = failure_indices
1255 .iter()
1256 .map(|&failure_index| {
1257 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1258 earliest_run_signal(&pca_fdc_run_signal, window_start, failure_index)
1259 .map(|run_index| failure_index - run_index)
1260 })
1261 .collect::<Vec<_>>();
1262 let numeric_dsa_leads = failure_indices
1263 .iter()
1264 .map(|&failure_index| {
1265 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1266 earliest_run_signal(
1267 &run_signals.numeric_primary_run_alert,
1268 window_start,
1269 failure_index,
1270 )
1271 .map(|run_index| failure_index - run_index)
1272 })
1273 .collect::<Vec<_>>();
1274
1275 let per_failure_run_signals = failure_indices
1276 .iter()
1277 .map(|&failure_index| {
1278 let window_start = failure_index.saturating_sub(pre_failure_lookback_runs);
1279 let earliest_dsa = earliest_primary_signal(
1280 &traces,
1281 &run_signals.primary_run_alert,
1282 window_start,
1283 failure_index,
1284 );
1285 let earliest_threshold_run =
1286 earliest_run_signal(&threshold_run_signal, window_start, failure_index);
1287 let earliest_ewma_run =
1288 earliest_run_signal(&ewma_run_signal, window_start, failure_index);
1289 let earliest_cusum_run =
1290 earliest_run_signal(&cusum_run_signal, window_start, failure_index);
1291 let earliest_run_energy_run =
1292 earliest_run_signal(&run_energy_run_signal, window_start, failure_index);
1293 let earliest_pca_fdc_run =
1294 earliest_run_signal(&pca_fdc_run_signal, window_start, failure_index);
1295 let dsa_lead_runs = earliest_dsa
1296 .as_ref()
1297 .map(|signal| failure_index - signal.run_index);
1298 let threshold_lead_runs = earliest_threshold_run.map(|index| failure_index - index);
1299 let ewma_lead_runs = earliest_ewma_run.map(|index| failure_index - index);
1300 let cusum_lead_runs = earliest_cusum_run.map(|index| failure_index - index);
1301 let run_energy_lead_runs = earliest_run_energy_run.map(|index| failure_index - index);
1302 let pca_fdc_lead_runs = earliest_pca_fdc_run.map(|index| failure_index - index);
1303 let alerting_feature_count = traces
1304 .iter()
1305 .filter(|trace| {
1306 trace.dsa_alert[window_start..failure_index]
1307 .iter()
1308 .any(|flag| *flag)
1309 })
1310 .count();
1311 let max_score = max_dsa_score(&traces, window_start, failure_index);
1312
1313 PerFailureRunDsaSignal {
1314 failure_run_index: failure_index,
1315 failure_timestamp: dataset.timestamps[failure_index]
1316 .format("%Y-%m-%d %H:%M:%S")
1317 .to_string(),
1318 earliest_dsa_run: earliest_dsa.as_ref().map(|signal| signal.run_index),
1319 earliest_primary_source: earliest_dsa.as_ref().map(|signal| signal.source.clone()),
1320 earliest_dsa_feature_index: earliest_dsa
1321 .as_ref()
1322 .map(|signal| signal.feature_index),
1323 earliest_dsa_feature_name: earliest_dsa
1324 .as_ref()
1325 .map(|signal| signal.feature_name.clone()),
1326 dsa_lead_runs,
1327 threshold_lead_runs,
1328 ewma_lead_runs,
1329 cusum_lead_runs,
1330 run_energy_lead_runs,
1331 pca_fdc_lead_runs,
1332 dsa_minus_cusum_delta_runs: paired_delta(dsa_lead_runs, cusum_lead_runs),
1333 dsa_minus_run_energy_delta_runs: paired_delta(dsa_lead_runs, run_energy_lead_runs),
1334 dsa_minus_pca_fdc_delta_runs: paired_delta(dsa_lead_runs, pca_fdc_lead_runs),
1335 dsa_minus_threshold_delta_runs: paired_delta(dsa_lead_runs, threshold_lead_runs),
1336 dsa_minus_ewma_delta_runs: paired_delta(dsa_lead_runs, ewma_lead_runs),
1337 dsa_alerting_feature_count: alerting_feature_count,
1338 max_dsa_score_in_lookback: max_score.as_ref().map(|score| score.score),
1339 max_dsa_score_feature_index: max_score.as_ref().map(|score| score.feature_index),
1340 max_dsa_score_feature_name: max_score
1341 .as_ref()
1342 .map(|score| score.feature_name.clone()),
1343 max_dsa_score_run_index: max_score.as_ref().map(|score| score.run_index),
1344 max_dsa_score_boundary_density_w: max_score
1345 .as_ref()
1346 .map(|score| score.boundary_density_w),
1347 max_dsa_score_drift_persistence_w: max_score
1348 .as_ref()
1349 .map(|score| score.drift_persistence_w),
1350 max_dsa_score_slew_density_w: max_score.as_ref().map(|score| score.slew_density_w),
1351 max_dsa_score_ewma_occupancy_w: max_score
1352 .as_ref()
1353 .map(|score| score.ewma_occupancy_w),
1354 max_dsa_score_motif_recurrence_w: max_score
1355 .as_ref()
1356 .map(|score| score.motif_recurrence_w),
1357 max_dsa_score_fragmentation_proxy_w: max_score
1358 .as_ref()
1359 .map(|score| score.fragmentation_proxy_w),
1360 max_dsa_score_consistent: max_score.as_ref().map(|score| score.consistent),
1361 max_dsa_score_policy_state: max_score
1362 .as_ref()
1363 .map(|score| score.policy_state.as_lowercase().to_string()),
1364 max_dsa_score_resolved_alert_class: max_score
1365 .as_ref()
1366 .map(|score| format!("{:?}", score.resolved_alert_class)),
1367 max_dsa_score_numeric_dsa_alert: max_score
1368 .as_ref()
1369 .map(|score| score.numeric_dsa_alert),
1370 max_dsa_score_dsa_alert: max_score.as_ref().map(|score| score.dsa_alert),
1371 max_dsa_score_policy_suppressed: max_score
1372 .as_ref()
1373 .map(|score| score.policy_suppressed_to_silent),
1374 max_dsa_score_rescue_transition: max_score.as_ref().and_then(|score| {
1375 (score.rescue_transition != "none").then_some(score.rescue_transition.clone())
1376 }),
1377 }
1378 })
1379 .collect::<Vec<_>>();
1380
1381 let alert_point_count = traces
1382 .iter()
1383 .map(|trace| trace.dsa_alert.iter().filter(|flag| **flag).count())
1384 .sum::<usize>();
1385 let alert_run_count = run_signals
1386 .primary_run_alert
1387 .iter()
1388 .filter(|flag| **flag)
1389 .count();
1390 let numeric_alert_point_count = traces
1391 .iter()
1392 .map(|trace| trace.numeric_dsa_alert.iter().filter(|flag| **flag).count())
1393 .sum::<usize>();
1394 let numeric_alert_run_count = run_signals
1395 .numeric_primary_run_alert
1396 .iter()
1397 .filter(|flag| **flag)
1398 .count();
1399 let watch_point_count = traces
1400 .iter()
1401 .map(|trace| {
1402 trace
1403 .policy_state
1404 .iter()
1405 .filter(|state| **state == DsaPolicyState::Watch)
1406 .count()
1407 })
1408 .sum::<usize>();
1409 let review_point_count = traces
1410 .iter()
1411 .map(|trace| {
1412 trace
1413 .policy_state
1414 .iter()
1415 .filter(|state| **state == DsaPolicyState::Review)
1416 .count()
1417 })
1418 .sum::<usize>();
1419 let escalate_point_count = traces
1420 .iter()
1421 .map(|trace| {
1422 trace
1423 .policy_state
1424 .iter()
1425 .filter(|state| **state == DsaPolicyState::Escalate)
1426 .count()
1427 })
1428 .sum::<usize>();
1429 let silenced_point_count = traces
1430 .iter()
1431 .map(|trace| {
1432 trace
1433 .policy_suppressed_to_silent
1434 .iter()
1435 .filter(|flag| **flag)
1436 .count()
1437 })
1438 .sum::<usize>();
1439 let rescued_point_count = traces
1440 .iter()
1441 .map(|trace| {
1442 trace
1443 .rescue_transition
1444 .iter()
1445 .filter(|transition| transition.as_str() != "none")
1446 .count()
1447 })
1448 .sum::<usize>();
1449 let rescued_watch_to_review_points = traces
1450 .iter()
1451 .map(|trace| {
1452 trace
1453 .rescue_transition
1454 .iter()
1455 .filter(|transition| transition.as_str() == "watch_to_review")
1456 .count()
1457 })
1458 .sum::<usize>();
1459 let rescued_review_to_escalate_points = traces
1460 .iter()
1461 .map(|trace| {
1462 trace
1463 .rescue_transition
1464 .iter()
1465 .filter(|transition| transition.as_str() == "review_to_escalate")
1466 .count()
1467 })
1468 .sum::<usize>();
1469 let failure_run_recall = per_failure_run_signals
1470 .iter()
1471 .filter(|signal| signal.earliest_dsa_run.is_some())
1472 .count();
1473 let dsa_row = SignalComparisonRow {
1474 signal: "DSA".into(),
1475 failure_run_recall,
1476 failure_runs: failure_indices.len(),
1477 failure_run_recall_rate: rate(failure_run_recall, failure_indices.len()),
1478 mean_lead_time_runs: mean_option_usize(
1479 &per_failure_run_signals
1480 .iter()
1481 .map(|signal| signal.dsa_lead_runs)
1482 .collect::<Vec<_>>(),
1483 ),
1484 median_lead_time_runs: median_option_usize(
1485 &per_failure_run_signals
1486 .iter()
1487 .map(|signal| signal.dsa_lead_runs)
1488 .collect::<Vec<_>>(),
1489 ),
1490 pass_run_nuisance_proxy: rate(
1491 pass_indices
1492 .iter()
1493 .filter(|&&run_index| run_signals.primary_run_alert[run_index])
1494 .count(),
1495 pass_indices.len(),
1496 ),
1497 mean_lead_delta_vs_cusum_runs: mean_option_i64(
1498 &per_failure_run_signals
1499 .iter()
1500 .map(|signal| signal.dsa_minus_cusum_delta_runs)
1501 .collect::<Vec<_>>(),
1502 ),
1503 mean_lead_delta_vs_run_energy_runs: mean_option_i64(
1504 &per_failure_run_signals
1505 .iter()
1506 .map(|signal| signal.dsa_minus_run_energy_delta_runs)
1507 .collect::<Vec<_>>(),
1508 ),
1509 mean_lead_delta_vs_pca_fdc_runs: mean_option_i64(
1510 &per_failure_run_signals
1511 .iter()
1512 .map(|signal| signal.dsa_minus_pca_fdc_delta_runs)
1513 .collect::<Vec<_>>(),
1514 ),
1515 mean_lead_delta_vs_threshold_runs: mean_option_i64(
1516 &per_failure_run_signals
1517 .iter()
1518 .map(|signal| signal.dsa_minus_threshold_delta_runs)
1519 .collect::<Vec<_>>(),
1520 ),
1521 mean_lead_delta_vs_ewma_runs: mean_option_i64(
1522 &per_failure_run_signals
1523 .iter()
1524 .map(|signal| signal.dsa_minus_ewma_delta_runs)
1525 .collect::<Vec<_>>(),
1526 ),
1527 };
1528 let numeric_dsa_row = SignalComparisonRow {
1529 signal: "Numeric-only DSA".into(),
1530 failure_run_recall: count_present(numeric_dsa_leads.iter().copied()),
1531 failure_runs: failure_indices.len(),
1532 failure_run_recall_rate: rate(
1533 count_present(numeric_dsa_leads.iter().copied()),
1534 failure_indices.len(),
1535 ),
1536 mean_lead_time_runs: mean_option_usize(&numeric_dsa_leads),
1537 median_lead_time_runs: median_option_usize(&numeric_dsa_leads),
1538 pass_run_nuisance_proxy: rate(
1539 pass_indices
1540 .iter()
1541 .filter(|&&run_index| run_signals.numeric_primary_run_alert[run_index])
1542 .count(),
1543 pass_indices.len(),
1544 ),
1545 mean_lead_delta_vs_cusum_runs: None,
1546 mean_lead_delta_vs_run_energy_runs: None,
1547 mean_lead_delta_vs_pca_fdc_runs: None,
1548 mean_lead_delta_vs_threshold_runs: None,
1549 mean_lead_delta_vs_ewma_runs: None,
1550 };
1551 let motif_policy_contributions = aggregate_motif_policy_contributions(&traces);
1552 let threshold_row = baseline_row(
1553 "Threshold",
1554 failure_indices.len(),
1555 &threshold_leads,
1556 rate(
1557 pass_indices
1558 .iter()
1559 .filter(|&&run_index| threshold_run_signal[run_index])
1560 .count(),
1561 pass_indices.len(),
1562 ),
1563 );
1564 let ewma_row = baseline_row(
1565 "EWMA",
1566 failure_indices.len(),
1567 &ewma_leads,
1568 rate(
1569 pass_indices
1570 .iter()
1571 .filter(|&&run_index| ewma_run_signal[run_index])
1572 .count(),
1573 pass_indices.len(),
1574 ),
1575 );
1576 let cusum_row = baseline_row(
1577 "CUSUM",
1578 failure_indices.len(),
1579 &cusum_leads,
1580 rate(
1581 pass_indices
1582 .iter()
1583 .filter(|&&run_index| cusum_run_signal[run_index])
1584 .count(),
1585 pass_indices.len(),
1586 ),
1587 );
1588 let run_energy_row = baseline_row(
1589 "Run energy",
1590 failure_indices.len(),
1591 &run_energy_leads,
1592 rate(
1593 pass_indices
1594 .iter()
1595 .filter(|&&run_index| run_energy_run_signal[run_index])
1596 .count(),
1597 pass_indices.len(),
1598 ),
1599 );
1600 let pca_fdc_row = baseline_row(
1601 "PCA T2/SPE",
1602 failure_indices.len(),
1603 &pca_fdc_leads,
1604 rate(
1605 pass_indices
1606 .iter()
1607 .filter(|&&run_index| pca_fdc_run_signal[run_index])
1608 .count(),
1609 pass_indices.len(),
1610 ),
1611 );
1612 let dsfb_violation_row = baseline_row(
1613 "DSFB Violation",
1614 failure_indices.len(),
1615 &raw_violation_leads,
1616 rate(
1617 pass_indices
1618 .iter()
1619 .filter(|&&run_index| raw_violation_run_signal[run_index])
1620 .count(),
1621 pass_indices.len(),
1622 ),
1623 );
1624 let dsfb_raw_boundary_row = baseline_row(
1625 "DSFB Raw Boundary",
1626 failure_indices.len(),
1627 &raw_boundary_leads,
1628 rate(
1629 pass_indices
1630 .iter()
1631 .filter(|&&run_index| raw_boundary_run_signal[run_index])
1632 .count(),
1633 pass_indices.len(),
1634 ),
1635 );
1636
1637 let raw_boundary_episode_count = grammar
1638 .traces
1639 .iter()
1640 .enumerate()
1641 .filter(|(feature_index, _)| {
1642 raw_boundary_episode_feature_mask
1643 .map(|mask| mask.get(*feature_index).copied().unwrap_or(false))
1644 .unwrap_or(true)
1645 })
1646 .map(|(_, trace)| {
1647 episode_ranges(
1648 &trace
1649 .raw_states
1650 .iter()
1651 .map(|state| *state == GrammarState::Boundary)
1652 .collect::<Vec<_>>(),
1653 )
1654 .len()
1655 })
1656 .sum::<usize>();
1657 let episode_summary = compute_episode_summary(
1658 &run_signals.primary_run_signal,
1659 &run_signals.primary_run_alert,
1660 raw_boundary_episode_count,
1661 &raw_violation_run_signal,
1662 &failure_window_mask,
1663 );
1664 let component_contributions = component_contributions(&traces);
1665 let nuisance_improved = dsa_row.pass_run_nuisance_proxy < threshold_row.pass_run_nuisance_proxy
1666 || dsa_row.pass_run_nuisance_proxy < ewma_row.pass_run_nuisance_proxy
1667 || dsa_row.pass_run_nuisance_proxy < cusum_row.pass_run_nuisance_proxy
1668 || dsa_row.pass_run_nuisance_proxy < run_energy_row.pass_run_nuisance_proxy
1669 || dsa_row.pass_run_nuisance_proxy < pca_fdc_row.pass_run_nuisance_proxy
1670 || dsa_row.pass_run_nuisance_proxy < dsfb_raw_boundary_row.pass_run_nuisance_proxy;
1671 let lead_time_improved = matches!(dsa_row.mean_lead_delta_vs_threshold_runs, Some(delta) if delta > 0.0)
1672 || matches!(dsa_row.mean_lead_delta_vs_ewma_runs, Some(delta) if delta > 0.0);
1673 let recall_preserved = dsa_row.failure_run_recall >= threshold_row.failure_run_recall
1674 && dsa_row.failure_run_recall >= ewma_row.failure_run_recall
1675 && dsa_row.failure_run_recall >= dsfb_violation_row.failure_run_recall;
1676 let recall_improved = dsa_row.failure_run_recall > threshold_row.failure_run_recall
1677 || dsa_row.failure_run_recall > ewma_row.failure_run_recall
1678 || dsa_row.failure_run_recall > dsfb_violation_row.failure_run_recall;
1679 let compression_improved =
1680 matches!(episode_summary.compression_ratio, Some(ratio) if ratio > 1.0);
1681 let any_metric_improved =
1682 nuisance_improved || lead_time_improved || recall_improved || compression_improved;
1683 let nothing_improved = !any_metric_improved;
1684 let threshold_recall_gate_passed =
1685 dsa_row.failure_run_recall >= threshold_row.failure_run_recall;
1686 let boundary_nuisance_gate_passed =
1687 dsa_row.pass_run_nuisance_proxy < dsfb_raw_boundary_row.pass_run_nuisance_proxy;
1688 let success_condition_failures =
1689 success_condition_failures(&dsa_row, &threshold_row, &ewma_row);
1690 let primary_success_condition_met = success_condition_failures.is_empty();
1691 let validation_failures = validation_failures(
1692 &dsa_row,
1693 &threshold_row,
1694 dsfb_raw_boundary_row.pass_run_nuisance_proxy,
1695 threshold_recall_gate_passed,
1696 boundary_nuisance_gate_passed,
1697 any_metric_improved,
1698 );
1699 let validation_passed = validation_failures.is_empty();
1700 let conclusion = dsa_conclusion(
1701 &dsa_row,
1702 &threshold_row,
1703 &ewma_row,
1704 &dsfb_violation_row,
1705 &dsfb_raw_boundary_row,
1706 &episode_summary,
1707 primary_success_condition_met,
1708 nuisance_improved,
1709 lead_time_improved,
1710 recall_preserved,
1711 compression_improved,
1712 nothing_improved,
1713 &component_contributions,
1714 &success_condition_failures,
1715 &validation_failures,
1716 );
1717
1718 let comparison_summary = DsaVsBaselinesSummary {
1719 dataset: "SECOM".into(),
1720 primary_run_signal: run_signals.primary_run_signal.clone(),
1721 dsa: dsa_row.clone(),
1722 numeric_dsa: numeric_dsa_row.clone(),
1723 threshold: threshold_row.clone(),
1724 ewma: ewma_row.clone(),
1725 cusum: cusum_row.clone(),
1726 run_energy: run_energy_row.clone(),
1727 pca_fdc: pca_fdc_row.clone(),
1728 dsfb_violation: dsfb_violation_row.clone(),
1729 dsfb_raw_boundary: dsfb_raw_boundary_row.clone(),
1730 episode_summary: episode_summary.clone(),
1731 failure_recall_delta_vs_threshold: dsa_row.failure_run_recall as i64
1732 - threshold_row.failure_run_recall as i64,
1733 failure_recall_delta_vs_ewma: dsa_row.failure_run_recall as i64
1734 - ewma_row.failure_run_recall as i64,
1735 failure_recall_delta_vs_cusum: dsa_row.failure_run_recall as i64
1736 - cusum_row.failure_run_recall as i64,
1737 failure_recall_delta_vs_run_energy: dsa_row.failure_run_recall as i64
1738 - run_energy_row.failure_run_recall as i64,
1739 failure_recall_delta_vs_pca_fdc: dsa_row.failure_run_recall as i64
1740 - pca_fdc_row.failure_run_recall as i64,
1741 failure_recall_delta_vs_violation: dsa_row.failure_run_recall as i64
1742 - dsfb_violation_row.failure_run_recall as i64,
1743 pass_run_nuisance_delta_vs_threshold: dsa_row.pass_run_nuisance_proxy
1744 - threshold_row.pass_run_nuisance_proxy,
1745 pass_run_nuisance_delta_vs_ewma: dsa_row.pass_run_nuisance_proxy
1746 - ewma_row.pass_run_nuisance_proxy,
1747 pass_run_nuisance_delta_vs_violation: dsa_row.pass_run_nuisance_proxy
1748 - dsfb_violation_row.pass_run_nuisance_proxy,
1749 pass_run_nuisance_delta_vs_cusum: dsa_row.pass_run_nuisance_proxy
1750 - cusum_row.pass_run_nuisance_proxy,
1751 pass_run_nuisance_delta_vs_run_energy: dsa_row.pass_run_nuisance_proxy
1752 - run_energy_row.pass_run_nuisance_proxy,
1753 pass_run_nuisance_delta_vs_pca_fdc: dsa_row.pass_run_nuisance_proxy
1754 - pca_fdc_row.pass_run_nuisance_proxy,
1755 pass_run_nuisance_delta_vs_raw_boundary: dsa_row.pass_run_nuisance_proxy
1756 - dsfb_raw_boundary_row.pass_run_nuisance_proxy,
1757 pass_run_nuisance_delta_vs_numeric_dsa: dsa_row.pass_run_nuisance_proxy
1758 - numeric_dsa_row.pass_run_nuisance_proxy,
1759 precursor_quality: episode_summary.precursor_quality,
1760 dsa_episodes_preceding_failure: episode_summary.dsa_episodes_preceding_failure,
1761 component_contributions: component_contributions.clone(),
1762 motif_policy_contributions: motif_policy_contributions.clone(),
1763 policy_vs_numeric_recall_delta: dsa_row.failure_run_recall as i64
1764 - numeric_dsa_row.failure_run_recall as i64,
1765 watch_point_count,
1766 review_point_count,
1767 escalate_point_count,
1768 silenced_point_count,
1769 nuisance_improved,
1770 lead_time_improved,
1771 recall_preserved,
1772 compression_improved,
1773 nothing_improved,
1774 threshold_recall_gate_passed,
1775 boundary_nuisance_gate_passed,
1776 primary_success_condition_met,
1777 any_metric_improved,
1778 validation_passed,
1779 success_condition_failures: success_condition_failures.clone(),
1780 validation_failures: validation_failures.clone(),
1781 conclusion,
1782 };
1783 let parameter_manifest = DsaParameterManifest {
1784 config: config.clone(),
1785 weights: weights.clone(),
1786 feature_policy_override_count: policy_runtime.feature_policy_overrides.len(),
1787 feature_policy_override_summary: policy_runtime
1788 .feature_policy_overrides
1789 .iter()
1790 .map(|override_entry| {
1791 format!(
1792 "{}: rescue_eligible={}, rescue_priority={}, allow_watch_only={}, allow_review_without_escalate={}, suppress_if_isolated={}, override_reason={}",
1793 override_entry.feature_name,
1794 override_entry.rescue_eligible,
1795 override_entry.rescue_priority,
1796 override_entry
1797 .allow_watch_only
1798 .map(|value| value.to_string())
1799 .unwrap_or_else(|| "default".into()),
1800 override_entry
1801 .allow_review_without_escalate
1802 .map(|value| value.to_string())
1803 .unwrap_or_else(|| "default".into()),
1804 override_entry
1805 .suppress_if_isolated
1806 .map(|value| value.to_string())
1807 .unwrap_or_else(|| "default".into()),
1808 override_entry.override_reason
1809 )
1810 })
1811 .collect(),
1812 policy_engine_definition:
1813 "Deterministic heuristics-governed policy engine over structural DSA candidates with explicit Silent/Watch/Review/Escalate feature states."
1814 .into(),
1815 feature_level_state_definition:
1816 "Feature state is resolved from active motif policies, structural score >= tau, persistence gating, directional consistency, local corroboration, and explicit silence suppression."
1817 .into(),
1818 primary_run_signal: run_signals.primary_run_signal.clone(),
1819 primary_run_signal_definition: format!(
1820 "Primary run-level DSA decision is feature_count_review_or_escalate(k) >= {}. This is a cross-feature corroboration gate above the frozen DSFB states and scalar baselines.",
1821 config.corroborating_feature_count_min
1822 ),
1823 secondary_run_signal: "any_feature_review_or_escalate(k)".into(),
1824 tertiary_run_signal: "feature_count_escalate(k)".into(),
1825 strict_escalate_signal: format!(
1826 "feature_count_escalate(k) >= {}",
1827 config.corroborating_feature_count_min
1828 ),
1829 rolling_window_definition: format!(
1830 "Trailing inclusive rolling window of up to {} runs per analyzable feature.",
1831 config.window
1832 ),
1833 boundary_density_basis:
1834 "Fraction of the trailing window where the feature is in the raw DSFB Boundary state."
1835 .into(),
1836 drift_persistence_definition:
1837 "Fraction of the trailing window where drift >= drift threshold and the drift sign is outward (positive residual-norm drift)."
1838 .into(),
1839 slew_density_definition:
1840 "Fraction of the trailing window where absolute slew >= slew threshold.".into(),
1841 ewma_occupancy_formula:
1842 "Mean over the trailing window of clamp(EWMA / EWMA_threshold, 0, 1).".into(),
1843 motif_names_used_for_recurrence: motif_names,
1844 directional_consistency_rule:
1845 "CONSISTENT is true only when thresholded drift signs in the window are never inward and the nonzero drift sign never flips within the window."
1846 .into(),
1847 silence_rule:
1848 "A structural candidate remains Silent when no motif policy activates, when motif hits stay below deterministic minimum_hits/minimum_window rules, when fragmentation exceeds the motif ceiling, or when persistence/corroboration gates fail."
1849 .into(),
1850 corroboration_rule: format!(
1851 "RUN_LEVEL_DSA(k) is true only when the count of features in Review or Escalate is at least {}.",
1852 config.corroborating_feature_count_min
1853 ),
1854 recall_rescue_definition: if policy_runtime.recall_rescue.enabled {
1855 format!(
1856 "Bounded recall rescue is enabled for explicit feature overrides only. Rescue promotes grammar-qualified near-miss structure when dsa_score >= tau - margin, boundary_density >= {:.2}, motif_recurrence >= {:.2}, ewma_occupancy >= {:.2}, and the override-specific watch-hit / fragmentation guards pass. Priority-2 overrides may rescue repeated Watch-class structure even when the directional-consistency rule fails, and priority-3 overrides may recover explicit grammar-qualified semantic support features from Silent when a failure-local override is present.",
1857 policy_runtime.recall_rescue.minimum_boundary_density,
1858 policy_runtime.recall_rescue.minimum_motif_recurrence,
1859 policy_runtime.recall_rescue.minimum_ewma_occupancy,
1860 )
1861 } else {
1862 "Bounded recall rescue is disabled.".into()
1863 },
1864 recall_tolerance_runs_for_primary_success: DSA_PRIMARY_SUCCESS_RECALL_TOLERANCE_RUNS,
1865 primary_success_condition_definition: dsa_primary_success_condition_definition(),
1866 optimization_priority_order: dsa_optimization_priority_order(),
1867 };
1868
1869 DsaEvaluation {
1870 traces,
1871 run_signals: run_signals.clone(),
1872 episode_summary: episode_summary.clone(),
1873 parameter_manifest,
1874 policy_runtime: policy_runtime.clone(),
1875 summary: DsaSignalSummary {
1876 config: config.clone(),
1877 weights: weights.clone(),
1878 primary_run_signal: run_signals.primary_run_signal.clone(),
1879 analyzable_feature_count: analyzable_feature_count_override.unwrap_or_else(|| {
1880 nominal
1881 .features
1882 .iter()
1883 .filter(|feature| feature.analyzable)
1884 .count()
1885 }),
1886 alert_point_count,
1887 alert_run_count,
1888 numeric_alert_point_count,
1889 numeric_alert_run_count,
1890 watch_point_count,
1891 review_point_count,
1892 escalate_point_count,
1893 silenced_point_count,
1894 rescued_point_count,
1895 rescued_watch_to_review_points,
1896 rescued_review_to_escalate_points,
1897 failure_runs: failure_indices.len(),
1898 failure_run_recall,
1899 failure_run_recall_rate: dsa_row.failure_run_recall_rate,
1900 numeric_primary_failure_run_recall: numeric_dsa_row.failure_run_recall,
1901 mean_lead_time_runs: dsa_row.mean_lead_time_runs,
1902 median_lead_time_runs: dsa_row.median_lead_time_runs,
1903 pass_run_nuisance_proxy: dsa_row.pass_run_nuisance_proxy,
1904 numeric_primary_pass_run_nuisance_proxy: numeric_dsa_row.pass_run_nuisance_proxy,
1905 mean_lead_delta_vs_cusum_runs: dsa_row.mean_lead_delta_vs_cusum_runs,
1906 mean_lead_delta_vs_run_energy_runs: dsa_row.mean_lead_delta_vs_run_energy_runs,
1907 mean_lead_delta_vs_pca_fdc_runs: dsa_row.mean_lead_delta_vs_pca_fdc_runs,
1908 mean_lead_delta_vs_threshold_runs: dsa_row.mean_lead_delta_vs_threshold_runs,
1909 mean_lead_delta_vs_ewma_runs: dsa_row.mean_lead_delta_vs_ewma_runs,
1910 raw_boundary_nuisance_proxy: dsfb_raw_boundary_row.pass_run_nuisance_proxy,
1911 raw_boundary_episode_count: episode_summary.raw_boundary_episode_count,
1912 dsa_episode_count: episode_summary.dsa_episode_count,
1913 dsa_episodes_preceding_failure: episode_summary.dsa_episodes_preceding_failure,
1914 mean_dsa_episode_length_runs: episode_summary.mean_dsa_episode_length_runs,
1915 max_dsa_episode_length_runs: episode_summary.max_dsa_episode_length_runs,
1916 compression_ratio: episode_summary.compression_ratio,
1917 precursor_quality: episode_summary.precursor_quality,
1918 non_escalating_dsa_episode_fraction: episode_summary
1919 .non_escalating_dsa_episode_fraction,
1920 threshold_recall_gate_passed,
1921 boundary_nuisance_gate_passed,
1922 primary_success_condition_met,
1923 any_metric_improved,
1924 validation_passed,
1925 success_condition_failures,
1926 validation_failures,
1927 },
1928 comparison_summary,
1929 motif_policy_contributions,
1930 per_failure_run_signals,
1931 }
1932}
1933
1934fn motif_contribution_state(
1935 policy: HeuristicPolicyDefinition,
1936 feature_override: Option<&FeaturePolicyOverride>,
1937 flags: &[bool],
1938 run_index: usize,
1939 structural_active: bool,
1940 numeric_alert: bool,
1941 local_corroboration: bool,
1942 raw_violation_recent: bool,
1943) -> Option<MotifContributionState> {
1944 let minimum_window = feature_override
1945 .and_then(|override_entry| override_entry.minimum_window_override)
1946 .unwrap_or(policy.minimum_window);
1947 let minimum_hits = feature_override
1948 .and_then(|override_entry| override_entry.minimum_hits_override)
1949 .unwrap_or(policy.minimum_hits);
1950 let maximum_allowed_fragmentation = feature_override
1951 .and_then(|override_entry| override_entry.maximum_allowed_fragmentation_override)
1952 .unwrap_or_else(|| policy.maximum_allowed_fragmentation());
1953 let alert_class_default = feature_override
1954 .and_then(|override_entry| override_entry.alert_class_override)
1955 .unwrap_or(policy.alert_class_default);
1956 let requires_persistence = feature_override
1957 .and_then(|override_entry| override_entry.requires_persistence_override)
1958 .unwrap_or(policy.requires_persistence);
1959 let requires_corroboration = feature_override
1960 .and_then(|override_entry| override_entry.requires_corroboration_override)
1961 .unwrap_or(policy.requires_corroboration);
1962 let allow_watch_only = feature_override
1963 .and_then(|override_entry| override_entry.allow_watch_only)
1964 .unwrap_or(false);
1965 let suppress_if_isolated = feature_override
1966 .and_then(|override_entry| override_entry.suppress_if_isolated)
1967 .unwrap_or(false);
1968 let start = run_index.saturating_sub(minimum_window.saturating_sub(1));
1969 let hits = flags[start..=run_index]
1970 .iter()
1971 .filter(|flag| **flag)
1972 .count();
1973 if hits == 0 {
1974 return None;
1975 }
1976
1977 let fragmentation_proxy = episode_ranges(&flags[start..=run_index]).len() as f64 / hits as f64;
1978 let policy_active =
1979 hits >= minimum_hits && fragmentation_proxy <= maximum_allowed_fragmentation;
1980 let suppressed_to_silent = structural_active && !policy_active;
1981 let mut contribution_state = if !structural_active || !policy_active {
1982 DsaPolicyState::Silent
1983 } else {
1984 match alert_class_default {
1985 HeuristicAlertClass::Silent => {
1986 if policy.promotes_alert && numeric_alert && local_corroboration {
1987 if raw_violation_recent {
1988 DsaPolicyState::Review
1989 } else {
1990 DsaPolicyState::Watch
1991 }
1992 } else {
1993 DsaPolicyState::Silent
1994 }
1995 }
1996 HeuristicAlertClass::Watch => {
1997 if requires_persistence && !numeric_alert {
1998 DsaPolicyState::Silent
1999 } else if requires_corroboration && !local_corroboration {
2000 if policy.suppresses_alert {
2001 DsaPolicyState::Silent
2002 } else {
2003 DsaPolicyState::Watch
2004 }
2005 } else if policy.promotes_alert && numeric_alert && hits > minimum_hits {
2006 DsaPolicyState::Review
2007 } else {
2008 DsaPolicyState::Watch
2009 }
2010 }
2011 HeuristicAlertClass::Review => {
2012 if requires_persistence && !numeric_alert {
2013 if policy.suppresses_alert {
2014 DsaPolicyState::Silent
2015 } else {
2016 DsaPolicyState::Watch
2017 }
2018 } else {
2019 DsaPolicyState::Review
2020 }
2021 }
2022 HeuristicAlertClass::Escalate => {
2023 if numeric_alert && local_corroboration {
2024 DsaPolicyState::Escalate
2025 } else if numeric_alert {
2026 DsaPolicyState::Review
2027 } else {
2028 DsaPolicyState::Watch
2029 }
2030 }
2031 }
2032 };
2033
2034 if contribution_state == DsaPolicyState::Review
2035 && policy.promotes_alert
2036 && numeric_alert
2037 && local_corroboration
2038 && raw_violation_recent
2039 {
2040 contribution_state = DsaPolicyState::Escalate;
2041 }
2042 if allow_watch_only && contribution_state > DsaPolicyState::Watch {
2043 contribution_state = DsaPolicyState::Watch;
2044 }
2045 if suppress_if_isolated && !local_corroboration {
2046 contribution_state = DsaPolicyState::Silent;
2047 }
2048
2049 Some(MotifContributionState {
2050 motif_name: policy.motif_name,
2051 default_alert_class: alert_class_default,
2052 contribution_state,
2053 fragmentation_proxy,
2054 suppressed_to_silent,
2055 })
2056}
2057
2058fn apply_recall_rescue(
2059 feature_override: Option<&FeaturePolicyOverride>,
2060 rescue_config: &RecallRescueConfig,
2061 config: &DsaConfig,
2062 resolved_alert_class: &[HeuristicAlertClass],
2063 policy_state: &mut [DsaPolicyState],
2064 dsa_alert: &mut [bool],
2065 semantic_rescue_support: &[bool],
2066 fragmentation_proxy_w: &[f64],
2067 dsa_score: &[f64],
2068 boundary_density_w: &[f64],
2069 ewma_occupancy_w: &[f64],
2070 motif_recurrence_w: &[f64],
2071 consistent: &[bool],
2072 run_index: usize,
2073) -> Option<&'static str> {
2074 let override_entry = feature_override?;
2075 if !override_entry.rescue_eligible {
2076 return None;
2077 }
2078 if policy_state[run_index].is_review_or_escalate() {
2079 return None;
2080 }
2081 let semantic_support_active = semantic_rescue_support
2082 .get(run_index)
2083 .copied()
2084 .unwrap_or(false);
2085 let silent_semantic_support_rescue = override_entry.rescue_priority >= 3
2086 && matches!(resolved_alert_class[run_index], HeuristicAlertClass::Silent)
2087 && semantic_support_active;
2088 let resolved_alert_is_rescuable = matches!(
2089 resolved_alert_class[run_index],
2090 HeuristicAlertClass::Watch | HeuristicAlertClass::Review
2091 );
2092 if !resolved_alert_is_rescuable && !silent_semantic_support_rescue {
2093 return None;
2094 }
2095 let minimum_window = override_entry
2096 .minimum_window_override
2097 .unwrap_or(config.window);
2098 let minimum_hits = override_entry.minimum_hits_override.unwrap_or(3);
2099 let fragmentation_ceiling = override_entry
2100 .maximum_allowed_fragmentation_override
2101 .unwrap_or(0.5);
2102 let score_margin = if override_entry.rescue_priority >= 3 {
2103 rescue_config.priority_three_score_margin
2104 } else if override_entry.rescue_priority >= 2 {
2105 rescue_config.priority_two_score_margin
2106 } else {
2107 rescue_config.priority_one_score_margin
2108 };
2109 let recent_start = run_index.saturating_sub(minimum_window.saturating_sub(1));
2110 let recent_watch_hits = (recent_start..=run_index)
2111 .filter(|&index| matches!(resolved_alert_class[index], HeuristicAlertClass::Watch))
2112 .count();
2113 let recent_semantic_support_hits = semantic_rescue_support[recent_start..=run_index]
2114 .iter()
2115 .filter(|flag| **flag)
2116 .count();
2117 let score_floor = (config.alert_tau - score_margin).max(0.0);
2118 let consistency_satisfied = consistent[run_index]
2119 || (override_entry.rescue_priority >= 2
2120 && matches!(resolved_alert_class[run_index], HeuristicAlertClass::Watch));
2121 let support_hits_satisfied =
2122 if override_entry.rescue_priority >= 3 && !resolved_alert_is_rescuable {
2123 recent_semantic_support_hits >= minimum_hits
2124 } else {
2125 recent_watch_hits >= minimum_hits
2126 };
2127 let structural_density_satisfied = if silent_semantic_support_rescue {
2128 semantic_support_active
2129 || (boundary_density_w[run_index] >= rescue_config.minimum_boundary_density
2130 && motif_recurrence_w[run_index] >= rescue_config.minimum_motif_recurrence)
2131 } else {
2132 boundary_density_w[run_index] >= rescue_config.minimum_boundary_density
2133 && motif_recurrence_w[run_index] >= rescue_config.minimum_motif_recurrence
2134 };
2135
2136 if !consistency_satisfied
2137 || !support_hits_satisfied
2138 || fragmentation_proxy_w[run_index] > fragmentation_ceiling
2139 || !structural_density_satisfied
2140 || ewma_occupancy_w[run_index] < rescue_config.minimum_ewma_occupancy
2141 || dsa_score[run_index] < score_floor
2142 {
2143 return None;
2144 }
2145
2146 if policy_state[run_index] == DsaPolicyState::Silent {
2147 if override_entry.allow_watch_only.unwrap_or(false) {
2148 policy_state[run_index] = DsaPolicyState::Watch;
2149 return Some("silent_to_watch");
2150 }
2151 if override_entry
2152 .allow_review_without_escalate
2153 .unwrap_or(false)
2154 {
2155 policy_state[run_index] = DsaPolicyState::Review;
2156 dsa_alert[run_index] = true;
2157 return Some(if resolved_alert_is_rescuable {
2158 "watch_to_review"
2159 } else {
2160 "silent_to_review"
2161 });
2162 }
2163 }
2164
2165 if policy_state[run_index] == DsaPolicyState::Review
2166 && !override_entry
2167 .allow_review_without_escalate
2168 .unwrap_or(false)
2169 {
2170 policy_state[run_index] = DsaPolicyState::Escalate;
2171 dsa_alert[run_index] = true;
2172 return Some("review_to_escalate");
2173 }
2174
2175 None
2176}
2177
2178fn dominant_alert_class(contributions: &[MotifContributionState]) -> HeuristicAlertClass {
2179 contributions
2180 .iter()
2181 .map(|contribution| contribution.default_alert_class)
2182 .max()
2183 .unwrap_or(HeuristicAlertClass::Silent)
2184}
2185
2186fn dominant_policy_state(contributions: &[MotifContributionState]) -> DsaPolicyState {
2187 contributions
2188 .iter()
2189 .map(|contribution| contribution.contribution_state)
2190 .max()
2191 .unwrap_or(DsaPolicyState::Silent)
2192}
2193
2194fn normalize_to_threshold(value: f64, threshold: f64) -> f64 {
2195 if threshold <= 0.0 {
2196 0.0
2197 } else {
2198 (value / threshold).clamp(0.0, 1.0)
2199 }
2200}
2201
2202fn bool_prefix_sum(flags: &[bool]) -> Vec<usize> {
2203 let mut prefix = Vec::with_capacity(flags.len() + 1);
2204 prefix.push(0);
2205 let mut total = 0usize;
2206 for flag in flags {
2207 total += usize::from(*flag);
2208 prefix.push(total);
2209 }
2210 prefix
2211}
2212
2213fn window_count(prefix: &[usize], start: usize, end: usize) -> usize {
2214 prefix[end + 1] - prefix[start]
2215}
2216
2217fn window_fraction(prefix: &[usize], start: usize, end: usize, window_len: f64) -> f64 {
2218 window_count(prefix, start, end) as f64 / window_len
2219}
2220
2221fn window_fraction_nonimputed(
2222 hit_prefix: &[usize],
2223 non_imputed_prefix: &[usize],
2224 start: usize,
2225 end: usize,
2226) -> f64 {
2227 let total_non_imputed = window_count(non_imputed_prefix, start, end);
2228 if total_non_imputed == 0 {
2229 0.0
2230 } else {
2231 window_count(hit_prefix, start, end) as f64 / total_non_imputed as f64
2232 }
2233}
2234
2235fn window_mean(values: &[f64], start: usize, end: usize) -> f64 {
2236 let slice = &values[start..=end];
2237 slice.iter().sum::<f64>() / slice.len() as f64
2238}
2239
2240fn window_is_consistent(drift: &[f64], drift_threshold: f64, start: usize, end: usize) -> bool {
2241 let mut previous_thresholded = 0i8;
2242
2243 for run_index in start..=end {
2244 let thresholded_sign = if drift[run_index] >= drift_threshold {
2245 1
2246 } else if drift[run_index] <= -drift_threshold {
2247 -1
2248 } else {
2249 0
2250 };
2251 if thresholded_sign < 0 {
2252 return false;
2253 }
2254 if thresholded_sign != 0 {
2255 if previous_thresholded != 0 && thresholded_sign != previous_thresholded {
2256 return false;
2257 }
2258 previous_thresholded = thresholded_sign;
2259 }
2260 }
2261
2262 true
2263}
2264
2265fn persistence_mask(values: &[bool], persistence_runs: usize) -> Vec<bool> {
2266 let mut out = Vec::with_capacity(values.len());
2267 let mut consecutive = 0usize;
2268 for value in values {
2269 if *value {
2270 consecutive += 1;
2271 out.push(consecutive >= persistence_runs);
2272 } else {
2273 consecutive = 0;
2274 out.push(false);
2275 }
2276 }
2277 out
2278}
2279
2280fn build_run_signals(
2281 traces: &[DsaFeatureTrace],
2282 raw_violation_run_signal: &[bool],
2283 config: &DsaConfig,
2284 run_count: usize,
2285) -> DsaRunSignals {
2286 let mut any_feature_dsa_alert = Vec::with_capacity(run_count);
2287 let mut primary_run_alert = Vec::with_capacity(run_count);
2288 let mut feature_count_dsa_alert = Vec::with_capacity(run_count);
2289 let mut watch_feature_count = Vec::with_capacity(run_count);
2290 let mut review_feature_count = Vec::with_capacity(run_count);
2291 let mut escalate_feature_count = Vec::with_capacity(run_count);
2292 let mut strict_escalate_run_alert = Vec::with_capacity(run_count);
2293 let mut numeric_primary_run_alert = Vec::with_capacity(run_count);
2294 let mut numeric_feature_count_dsa_alert = Vec::with_capacity(run_count);
2295
2296 for run_index in 0..run_count {
2297 let review_count = traces
2298 .iter()
2299 .filter(|trace| trace.dsa_alert[run_index])
2300 .count();
2301 let watch_count = traces
2302 .iter()
2303 .filter(|trace| trace.policy_state[run_index] == DsaPolicyState::Watch)
2304 .count();
2305 let escalate_count = traces
2306 .iter()
2307 .filter(|trace| trace.policy_state[run_index] == DsaPolicyState::Escalate)
2308 .count();
2309 let numeric_count = traces
2310 .iter()
2311 .filter(|trace| trace.numeric_dsa_alert[run_index])
2312 .count();
2313 feature_count_dsa_alert.push(review_count);
2314 watch_feature_count.push(watch_count);
2315 review_feature_count.push(review_count.saturating_sub(escalate_count));
2316 escalate_feature_count.push(escalate_count);
2317 numeric_feature_count_dsa_alert.push(numeric_count);
2318 strict_escalate_run_alert.push(escalate_count >= config.corroborating_feature_count_min);
2319 numeric_primary_run_alert.push(numeric_count >= config.corroborating_feature_count_min);
2320 let any_dsa = review_count > 0;
2321 any_feature_dsa_alert.push(any_dsa);
2322 primary_run_alert.push(review_count >= config.corroborating_feature_count_min);
2323 }
2324
2325 DsaRunSignals {
2326 primary_run_signal: format!(
2327 "feature_count_review_or_escalate(k) >= {}",
2328 config.corroborating_feature_count_min
2329 ),
2330 corroborating_feature_count_min: config.corroborating_feature_count_min,
2331 primary_run_alert,
2332 any_feature_dsa_alert,
2333 any_feature_raw_violation: raw_violation_run_signal.to_vec(),
2334 feature_count_dsa_alert,
2335 watch_feature_count,
2336 review_feature_count,
2337 escalate_feature_count,
2338 strict_escalate_run_alert,
2339 numeric_primary_run_alert,
2340 numeric_feature_count_dsa_alert,
2341 }
2342}
2343
2344fn compute_episode_summary(
2345 primary_signal_name: &str,
2346 dsa_signal: &[bool],
2347 raw_boundary_episode_count: usize,
2348 raw_violation_signal: &[bool],
2349 failure_window_mask: &[bool],
2350) -> DsaEpisodeSummary {
2351 let dsa_episodes = episode_ranges(dsa_signal);
2352 let dsa_lengths = dsa_episodes
2353 .iter()
2354 .map(|(start, end)| end - start + 1)
2355 .collect::<Vec<_>>();
2356 let dsa_episodes_preceding_failure = dsa_episodes
2357 .iter()
2358 .filter(|(start, end)| (*start..=*end).any(|run| failure_window_mask[run]))
2359 .count();
2360 let non_escalating_dsa_episode_fraction = if dsa_episodes.is_empty() {
2361 None
2362 } else {
2363 Some(
2364 dsa_episodes
2365 .iter()
2366 .filter(|(start, end)| !(*start..=*end).any(|run| raw_violation_signal[run]))
2367 .count() as f64
2368 / dsa_episodes.len() as f64,
2369 )
2370 };
2371
2372 DsaEpisodeSummary {
2373 primary_signal: primary_signal_name.into(),
2374 raw_boundary_episode_count,
2375 dsa_episode_count: dsa_episodes.len(),
2376 dsa_episodes_preceding_failure,
2377 mean_dsa_episode_length_runs: mean_usize(&dsa_lengths),
2378 max_dsa_episode_length_runs: dsa_lengths.iter().copied().max().unwrap_or(0),
2379 compression_ratio: if dsa_episodes.is_empty() {
2380 None
2381 } else {
2382 Some(raw_boundary_episode_count as f64 / dsa_episodes.len() as f64)
2383 },
2384 precursor_quality: if dsa_episodes.is_empty() {
2385 None
2386 } else {
2387 Some(dsa_episodes_preceding_failure as f64 / dsa_episodes.len() as f64)
2388 },
2389 non_escalating_dsa_episode_fraction,
2390 }
2391}
2392
2393fn episode_ranges(signal: &[bool]) -> Vec<(usize, usize)> {
2394 let mut episodes = Vec::new();
2395 let mut current_start: Option<usize> = None;
2396
2397 for (run_index, flag) in signal.iter().copied().enumerate() {
2398 match (current_start, flag) {
2399 (None, true) => current_start = Some(run_index),
2400 (Some(start), false) => {
2401 episodes.push((start, run_index - 1));
2402 current_start = None;
2403 }
2404 _ => {}
2405 }
2406 }
2407
2408 if let Some(start) = current_start {
2409 episodes.push((start, signal.len().saturating_sub(1)));
2410 }
2411
2412 episodes
2413}
2414
2415#[derive(Debug, Clone)]
2416struct EarliestPrimarySignal {
2417 run_index: usize,
2418 feature_index: usize,
2419 feature_name: String,
2420 score: f64,
2421 source: String,
2422}
2423
2424fn earliest_primary_signal(
2425 traces: &[DsaFeatureTrace],
2426 primary_signal: &[bool],
2427 start: usize,
2428 end: usize,
2429) -> Option<EarliestPrimarySignal> {
2430 let run_index = earliest_run_signal(primary_signal, start, end)?;
2431 traces
2432 .iter()
2433 .filter(|trace| trace.dsa_alert[run_index])
2434 .map(|trace| EarliestPrimarySignal {
2435 run_index,
2436 feature_index: trace.feature_index,
2437 feature_name: trace.feature_name.clone(),
2438 score: trace.dsa_score[run_index],
2439 source: "DSA".into(),
2440 })
2441 .max_by(|left, right| {
2442 left.score
2443 .partial_cmp(&right.score)
2444 .unwrap_or(std::cmp::Ordering::Equal)
2445 .then_with(|| right.feature_index.cmp(&left.feature_index))
2446 })
2447}
2448
2449fn build_failure_window_mask(
2450 run_count: usize,
2451 failure_indices: &[usize],
2452 pre_failure_lookback_runs: usize,
2453) -> Vec<bool> {
2454 let mut mask = vec![false; run_count];
2455 for &failure_index in failure_indices {
2456 let start = failure_index.saturating_sub(pre_failure_lookback_runs);
2457 for slot in &mut mask[start..failure_index] {
2458 *slot = true;
2459 }
2460 }
2461 mask
2462}
2463
2464#[derive(Debug, Clone)]
2465struct MaxDsaScore {
2466 feature_index: usize,
2467 feature_name: String,
2468 run_index: usize,
2469 score: f64,
2470 boundary_density_w: f64,
2471 drift_persistence_w: f64,
2472 slew_density_w: f64,
2473 ewma_occupancy_w: f64,
2474 motif_recurrence_w: f64,
2475 fragmentation_proxy_w: f64,
2476 consistent: bool,
2477 policy_state: DsaPolicyState,
2478 resolved_alert_class: HeuristicAlertClass,
2479 numeric_dsa_alert: bool,
2480 dsa_alert: bool,
2481 policy_suppressed_to_silent: bool,
2482 rescue_transition: String,
2483}
2484
2485fn max_dsa_score(traces: &[DsaFeatureTrace], start: usize, end: usize) -> Option<MaxDsaScore> {
2486 let mut max_score: Option<MaxDsaScore> = None;
2487 for trace in traces {
2488 for run_index in start..end {
2489 let candidate = MaxDsaScore {
2490 feature_index: trace.feature_index,
2491 feature_name: trace.feature_name.clone(),
2492 run_index,
2493 score: trace.dsa_score[run_index],
2494 boundary_density_w: trace.boundary_density_w[run_index],
2495 drift_persistence_w: trace.drift_persistence_w[run_index],
2496 slew_density_w: trace.slew_density_w[run_index],
2497 ewma_occupancy_w: trace.ewma_occupancy_w[run_index],
2498 motif_recurrence_w: trace.motif_recurrence_w[run_index],
2499 fragmentation_proxy_w: trace.fragmentation_proxy_w[run_index],
2500 consistent: trace.consistent[run_index],
2501 policy_state: trace.policy_state[run_index],
2502 resolved_alert_class: trace.resolved_alert_class[run_index],
2503 numeric_dsa_alert: trace.numeric_dsa_alert[run_index],
2504 dsa_alert: trace.dsa_alert[run_index],
2505 policy_suppressed_to_silent: trace.policy_suppressed_to_silent[run_index],
2506 rescue_transition: trace.rescue_transition[run_index].clone(),
2507 };
2508 let should_replace = match &max_score {
2509 None => true,
2510 Some(current) => {
2511 candidate.score > current.score
2512 || (candidate.score == current.score
2513 && candidate.feature_index < current.feature_index)
2514 }
2515 };
2516 if should_replace {
2517 max_score = Some(candidate);
2518 }
2519 }
2520 }
2521 max_score
2522}
2523
2524fn earliest_run_signal(signal: &[bool], start: usize, end: usize) -> Option<usize> {
2525 (start..end).find(|&run_index| signal[run_index])
2526}
2527
2528fn paired_delta(left: Option<usize>, right: Option<usize>) -> Option<i64> {
2529 Some(left? as i64 - right? as i64)
2530}
2531
2532fn baseline_row(
2533 signal: &str,
2534 failure_runs: usize,
2535 lead_values: &[Option<usize>],
2536 nuisance: f64,
2537) -> SignalComparisonRow {
2538 let recall = lead_values.iter().filter(|value| value.is_some()).count();
2539 SignalComparisonRow {
2540 signal: signal.into(),
2541 failure_run_recall: recall,
2542 failure_runs,
2543 failure_run_recall_rate: rate(recall, failure_runs),
2544 mean_lead_time_runs: mean_option_usize(lead_values),
2545 median_lead_time_runs: median_option_usize(lead_values),
2546 pass_run_nuisance_proxy: nuisance,
2547 mean_lead_delta_vs_cusum_runs: None,
2548 mean_lead_delta_vs_run_energy_runs: None,
2549 mean_lead_delta_vs_pca_fdc_runs: None,
2550 mean_lead_delta_vs_threshold_runs: None,
2551 mean_lead_delta_vs_ewma_runs: None,
2552 }
2553}
2554
2555fn component_contributions(traces: &[DsaFeatureTrace]) -> Vec<DsaComponentContribution> {
2556 let total_points = traces
2557 .iter()
2558 .map(|trace| trace.dsa_score.len())
2559 .sum::<usize>()
2560 .max(1);
2561 let alert_points = traces
2562 .iter()
2563 .map(|trace| trace.dsa_alert.iter().filter(|flag| **flag).count())
2564 .sum::<usize>()
2565 .max(1);
2566
2567 let component_rows = [
2568 (
2569 "boundary_density_W",
2570 traces
2571 .iter()
2572 .flat_map(|trace| trace.boundary_density_w.iter().copied())
2573 .sum::<f64>(),
2574 traces
2575 .iter()
2576 .map(|trace| {
2577 trace
2578 .boundary_density_w
2579 .iter()
2580 .zip(&trace.dsa_alert)
2581 .filter_map(|(value, flag)| flag.then_some(*value))
2582 .sum::<f64>()
2583 })
2584 .sum::<f64>(),
2585 ),
2586 (
2587 "drift_persistence_W",
2588 traces
2589 .iter()
2590 .flat_map(|trace| trace.drift_persistence_w.iter().copied())
2591 .sum::<f64>(),
2592 traces
2593 .iter()
2594 .map(|trace| {
2595 trace
2596 .drift_persistence_w
2597 .iter()
2598 .zip(&trace.dsa_alert)
2599 .filter_map(|(value, flag)| flag.then_some(*value))
2600 .sum::<f64>()
2601 })
2602 .sum::<f64>(),
2603 ),
2604 (
2605 "slew_density_W",
2606 traces
2607 .iter()
2608 .flat_map(|trace| trace.slew_density_w.iter().copied())
2609 .sum::<f64>(),
2610 traces
2611 .iter()
2612 .map(|trace| {
2613 trace
2614 .slew_density_w
2615 .iter()
2616 .zip(&trace.dsa_alert)
2617 .filter_map(|(value, flag)| flag.then_some(*value))
2618 .sum::<f64>()
2619 })
2620 .sum::<f64>(),
2621 ),
2622 (
2623 "ewma_occupancy_W",
2624 traces
2625 .iter()
2626 .flat_map(|trace| trace.ewma_occupancy_w.iter().copied())
2627 .sum::<f64>(),
2628 traces
2629 .iter()
2630 .map(|trace| {
2631 trace
2632 .ewma_occupancy_w
2633 .iter()
2634 .zip(&trace.dsa_alert)
2635 .filter_map(|(value, flag)| flag.then_some(*value))
2636 .sum::<f64>()
2637 })
2638 .sum::<f64>(),
2639 ),
2640 (
2641 "motif_recurrence_W",
2642 traces
2643 .iter()
2644 .flat_map(|trace| trace.motif_recurrence_w.iter().copied())
2645 .sum::<f64>(),
2646 traces
2647 .iter()
2648 .map(|trace| {
2649 trace
2650 .motif_recurrence_w
2651 .iter()
2652 .zip(&trace.dsa_alert)
2653 .filter_map(|(value, flag)| flag.then_some(*value))
2654 .sum::<f64>()
2655 })
2656 .sum::<f64>(),
2657 ),
2658 ];
2659
2660 let mut out = component_rows
2661 .into_iter()
2662 .map(|(component, all_sum, alert_sum)| DsaComponentContribution {
2663 component: component.into(),
2664 mean_value_on_alert_points: alert_sum / alert_points as f64,
2665 mean_value_on_all_points: all_sum / total_points as f64,
2666 total_value_on_alert_points: alert_sum,
2667 })
2668 .collect::<Vec<_>>();
2669 out.sort_by(|left, right| {
2670 right
2671 .mean_value_on_alert_points
2672 .partial_cmp(&left.mean_value_on_alert_points)
2673 .unwrap_or(std::cmp::Ordering::Equal)
2674 .then_with(|| left.component.cmp(&right.component))
2675 });
2676 out
2677}
2678
2679fn aggregate_motif_policy_contributions(
2680 traces: &[DsaFeatureTrace],
2681) -> Vec<DsaMotifPolicyContribution> {
2682 let mut aggregated = BTreeMap::<String, DsaMotifPolicyContribution>::new();
2683 for trace in traces {
2684 for contribution in &trace.motif_policy_contributions {
2685 let entry = aggregated
2686 .entry(contribution.motif_name.clone())
2687 .or_insert_with(|| DsaMotifPolicyContribution {
2688 motif_name: contribution.motif_name.clone(),
2689 alert_class_default: contribution.alert_class_default,
2690 watch_points: 0,
2691 review_points: 0,
2692 escalate_points: 0,
2693 silent_suppression_points: 0,
2694 pass_review_or_escalate_points: 0,
2695 pre_failure_review_or_escalate_points: 0,
2696 });
2697 entry.watch_points += contribution.watch_points;
2698 entry.review_points += contribution.review_points;
2699 entry.escalate_points += contribution.escalate_points;
2700 entry.silent_suppression_points += contribution.silent_suppression_points;
2701 entry.pass_review_or_escalate_points += contribution.pass_review_or_escalate_points;
2702 entry.pre_failure_review_or_escalate_points +=
2703 contribution.pre_failure_review_or_escalate_points;
2704 }
2705 }
2706
2707 aggregated.into_values().collect::<Vec<_>>()
2708}
2709
2710fn success_condition_failures(
2711 dsa: &SignalComparisonRow,
2712 threshold: &SignalComparisonRow,
2713 ewma: &SignalComparisonRow,
2714) -> Vec<String> {
2715 let mut failures = Vec::new();
2716 if dsa.pass_run_nuisance_proxy >= ewma.pass_run_nuisance_proxy {
2717 failures.push(format!(
2718 "pass-run nuisance {:.4} is not below EWMA nuisance {:.4}",
2719 dsa.pass_run_nuisance_proxy, ewma.pass_run_nuisance_proxy
2720 ));
2721 }
2722 let recall_floor = threshold
2723 .failure_run_recall
2724 .saturating_sub(DSA_PRIMARY_SUCCESS_RECALL_TOLERANCE_RUNS);
2725 if dsa.failure_run_recall < recall_floor {
2726 failures.push(format!(
2727 "failure recall {}/{} is more than {} run(s) below threshold recall {}/{}",
2728 dsa.failure_run_recall,
2729 dsa.failure_runs,
2730 DSA_PRIMARY_SUCCESS_RECALL_TOLERANCE_RUNS,
2731 threshold.failure_run_recall,
2732 threshold.failure_runs,
2733 ));
2734 }
2735 failures
2736}
2737
2738fn validation_failures(
2739 dsa: &SignalComparisonRow,
2740 threshold: &SignalComparisonRow,
2741 raw_boundary_nuisance_proxy: f64,
2742 threshold_recall_gate_passed: bool,
2743 boundary_nuisance_gate_passed: bool,
2744 any_metric_improved: bool,
2745) -> Vec<String> {
2746 let mut failures = Vec::new();
2747 if !threshold_recall_gate_passed {
2748 failures.push(format!(
2749 "failure recall {}/{} is below threshold recall {}/{}",
2750 dsa.failure_run_recall,
2751 dsa.failure_runs,
2752 threshold.failure_run_recall,
2753 threshold.failure_runs,
2754 ));
2755 }
2756 if !boundary_nuisance_gate_passed {
2757 failures.push(format!(
2758 "pass-run nuisance {:.4} is not below raw DSFB boundary nuisance {:.4}",
2759 dsa.pass_run_nuisance_proxy, raw_boundary_nuisance_proxy,
2760 ));
2761 }
2762 if !any_metric_improved {
2763 failures.push(
2764 "no saved DSA metric improves nuisance, lead time, recall, or compression relative to the logged baselines"
2765 .into(),
2766 );
2767 }
2768 failures
2769}
2770
2771fn dsa_conclusion(
2772 dsa: &SignalComparisonRow,
2773 threshold: &SignalComparisonRow,
2774 ewma: &SignalComparisonRow,
2775 dsfb_violation: &SignalComparisonRow,
2776 raw_boundary: &SignalComparisonRow,
2777 episode_summary: &DsaEpisodeSummary,
2778 primary_success_condition_met: bool,
2779 nuisance_improved: bool,
2780 lead_time_improved: bool,
2781 recall_preserved: bool,
2782 compression_improved: bool,
2783 nothing_improved: bool,
2784 component_contributions: &[DsaComponentContribution],
2785 success_condition_failures: &[String],
2786 validation_failures: &[String],
2787) -> String {
2788 let top_components = component_contributions
2789 .iter()
2790 .take(3)
2791 .map(|row| format!("{}={:.4}", row.component, row.mean_value_on_alert_points))
2792 .collect::<Vec<_>>()
2793 .join(", ");
2794
2795 if primary_success_condition_met {
2796 if !validation_failures.is_empty() {
2797 return format!(
2798 "DSA satisfies the crate's legacy one-run-tolerance nuisance/recall sweep gate: pass-run nuisance {:.4} is below EWMA nuisance {:.4}, failure recall is {}/{}, mean lead deltas are threshold={} and EWMA={}, precursor quality is {}, and compression ratio is {}. It still fails the stricter validation gates ({}), and this legacy sweep gate is not the stronger predeclared delta target used in the optimization report. No superiority claim is made and DSFB Violation remains the frozen instantaneous envelope-exit comparator.",
2799 dsa.pass_run_nuisance_proxy,
2800 ewma.pass_run_nuisance_proxy,
2801 dsa.failure_run_recall,
2802 dsa.failure_runs,
2803 format_option_f64(dsa.mean_lead_delta_vs_threshold_runs),
2804 format_option_f64(dsa.mean_lead_delta_vs_ewma_runs),
2805 format_option_f64(episode_summary.precursor_quality),
2806 format_option_f64(episode_summary.compression_ratio),
2807 validation_failures.join("; "),
2808 );
2809 }
2810 return format!(
2811 "DSA satisfies the crate's legacy one-run-tolerance nuisance/recall sweep gate: pass-run nuisance {:.4} is below EWMA nuisance {:.4}, failure recall is {}/{}, mean lead deltas are threshold={} and EWMA={}, precursor quality is {}, and compression ratio is {}. This legacy sweep gate is not the stronger predeclared delta target used in the optimization report. DSFB Violation remains the frozen instantaneous envelope-exit comparator.",
2812 dsa.pass_run_nuisance_proxy,
2813 ewma.pass_run_nuisance_proxy,
2814 dsa.failure_run_recall,
2815 dsa.failure_runs,
2816 format_option_f64(dsa.mean_lead_delta_vs_threshold_runs),
2817 format_option_f64(dsa.mean_lead_delta_vs_ewma_runs),
2818 format_option_f64(episode_summary.precursor_quality),
2819 format_option_f64(episode_summary.compression_ratio),
2820 );
2821 }
2822
2823 if !validation_failures.is_empty() {
2824 if nuisance_improved && !lead_time_improved {
2825 return format!(
2826 "DSA improves nuisance relative to at least one comparator but does not satisfy the primary success condition ({}) and also fails validation gates ({}). Recall is {}/{}, threshold recall is {}/{}, EWMA recall is {}/{}, DSFB Violation recall is {}/{}, raw-boundary nuisance delta is {:.4}, precursor quality is {}, compression ratio is {}, and the strongest DSA components were {}. No superiority claim is made.",
2827 success_condition_failures.join("; "),
2828 validation_failures.join("; "),
2829 dsa.failure_run_recall,
2830 dsa.failure_runs,
2831 threshold.failure_run_recall,
2832 threshold.failure_runs,
2833 ewma.failure_run_recall,
2834 ewma.failure_runs,
2835 dsfb_violation.failure_run_recall,
2836 dsfb_violation.failure_runs,
2837 dsa.pass_run_nuisance_proxy - raw_boundary.pass_run_nuisance_proxy,
2838 format_option_f64(episode_summary.precursor_quality),
2839 format_option_f64(episode_summary.compression_ratio),
2840 top_components,
2841 );
2842 }
2843
2844 if nothing_improved {
2845 return format!(
2846 "DSA fails to improve nuisance, lead time, recall, or compression and fails both the primary success condition ({}) and validation gates ({}). Recall is {}/{}, pass-run nuisance is {:.4}, mean lead deltas are threshold={} and EWMA={}, precursor quality is {}, compression ratio is {}, and the strongest DSA components were {}.",
2847 success_condition_failures.join("; "),
2848 validation_failures.join("; "),
2849 dsa.failure_run_recall,
2850 dsa.failure_runs,
2851 dsa.pass_run_nuisance_proxy,
2852 format_option_f64(dsa.mean_lead_delta_vs_threshold_runs),
2853 format_option_f64(dsa.mean_lead_delta_vs_ewma_runs),
2854 format_option_f64(episode_summary.precursor_quality),
2855 format_option_f64(episode_summary.compression_ratio),
2856 top_components,
2857 );
2858 }
2859
2860 return format!(
2861 "DSA shows mixed trade-offs but does not satisfy the primary success condition ({}) and fails validation gates ({}). Nuisance improved: {}, lead time improved: {}, recall preserved: {}, compression improved: {}, precursor quality is {}, and the strongest DSA components were {}. No superiority claim is made.",
2862 success_condition_failures.join("; "),
2863 validation_failures.join("; "),
2864 nuisance_improved,
2865 lead_time_improved,
2866 recall_preserved,
2867 compression_improved,
2868 format_option_f64(episode_summary.precursor_quality),
2869 top_components,
2870 );
2871 }
2872
2873 if nuisance_improved && !lead_time_improved {
2874 return format!(
2875 "DSA reduces nuisance relative to at least one comparator, but it does not satisfy the primary success condition ({}). Recall is {}/{}, threshold recall is {}/{}, pass-run nuisance is {:.4}, mean lead deltas are threshold={} and EWMA={}, precursor quality is {}, compression ratio is {}, and the strongest DSA components were {}. No superiority claim is made.",
2876 success_condition_failures.join("; "),
2877 dsa.failure_run_recall,
2878 dsa.failure_runs,
2879 threshold.failure_run_recall,
2880 threshold.failure_runs,
2881 dsa.pass_run_nuisance_proxy,
2882 format_option_f64(dsa.mean_lead_delta_vs_threshold_runs),
2883 format_option_f64(dsa.mean_lead_delta_vs_ewma_runs),
2884 format_option_f64(episode_summary.precursor_quality),
2885 format_option_f64(episode_summary.compression_ratio),
2886 top_components,
2887 );
2888 }
2889
2890 if nothing_improved {
2891 return format!(
2892 "DSA fails to improve nuisance, lead time, recall, or compression relative to the logged baselines, and it does not satisfy the primary success condition ({}). Recall is {}/{}, pass-run nuisance is {:.4}, mean lead deltas are threshold={} and EWMA={}, precursor quality is {}, compression ratio is {}, and the strongest DSA components were {}.",
2893 success_condition_failures.join("; "),
2894 dsa.failure_run_recall,
2895 dsa.failure_runs,
2896 dsa.pass_run_nuisance_proxy,
2897 format_option_f64(dsa.mean_lead_delta_vs_threshold_runs),
2898 format_option_f64(dsa.mean_lead_delta_vs_ewma_runs),
2899 format_option_f64(episode_summary.precursor_quality),
2900 format_option_f64(episode_summary.compression_ratio),
2901 top_components,
2902 );
2903 }
2904
2905 format!(
2906 "DSA shows mixed trade-offs without satisfying the primary success condition ({}). Recall preserved: {}, nuisance improved: {}, lead time improved: {}, precursor quality is {}, compression ratio is {}, and the strongest DSA components were {}.",
2907 success_condition_failures.join("; "),
2908 recall_preserved,
2909 nuisance_improved,
2910 lead_time_improved,
2911 format_option_f64(episode_summary.precursor_quality),
2912 format_option_f64(episode_summary.compression_ratio),
2913 top_components,
2914 )
2915}
2916
2917pub fn summarize_dsa_grid(rows: &[DsaCalibrationRow]) -> DsaGridSummary {
2918 let mut by_corroboration: BTreeMap<usize, Vec<DsaCalibrationRow>> = BTreeMap::new();
2919 for row in rows {
2920 by_corroboration
2921 .entry(row.corroborating_feature_count_min)
2922 .or_default()
2923 .push(row.clone());
2924 }
2925 let corroboration_summaries = by_corroboration
2926 .iter()
2927 .map(
2928 |(&corroborating_feature_count_min, grouped_rows)| DsaCorroborationSummary {
2929 corroborating_feature_count_min,
2930 representative_row: choose_closest_to_success(grouped_rows),
2931 },
2932 )
2933 .collect::<Vec<_>>();
2934 let success_row_count = rows
2935 .iter()
2936 .filter(|row| row.primary_success_condition_met)
2937 .count();
2938 DsaGridSummary {
2939 grid_point_count: rows.len(),
2940 optimization_priority_order: dsa_optimization_priority_order(),
2941 primary_success_condition_definition: dsa_primary_success_condition_definition(),
2942 success_row_count,
2943 any_success_row: success_row_count > 0,
2944 closest_to_success: choose_closest_to_success(rows),
2945 best_success_row: choose_best_success_row(rows),
2946 best_precursor_quality_row: choose_best_precursor_quality_row(rows),
2947 cross_feature_corroboration_effect: cross_feature_corroboration_effect(
2948 &corroboration_summaries,
2949 ),
2950 limiting_factor: limiting_factor(rows),
2951 corroboration_summaries,
2952 }
2953}
2954
2955fn choose_closest_to_success(rows: &[DsaCalibrationRow]) -> Option<DsaCalibrationRow> {
2956 if let Some(best_success_row) = choose_best_success_row(rows) {
2957 return Some(best_success_row);
2958 }
2959 rows.iter()
2960 .cloned()
2961 .max_by(compare_dsa_rows_by_success_closeness)
2962}
2963
2964fn choose_best_success_row(rows: &[DsaCalibrationRow]) -> Option<DsaCalibrationRow> {
2965 rows.iter()
2966 .filter(|row| row.primary_success_condition_met)
2967 .cloned()
2968 .max_by(compare_dsa_rows_by_priority)
2969}
2970
2971fn choose_best_precursor_quality_row(rows: &[DsaCalibrationRow]) -> Option<DsaCalibrationRow> {
2972 rows.iter().cloned().max_by(|left, right| {
2973 compare_option_f64(left.precursor_quality, right.precursor_quality)
2974 .then_with(|| compare_dsa_rows_by_priority(left, right))
2975 })
2976}
2977
2978fn compare_dsa_rows_by_priority(
2979 left: &DsaCalibrationRow,
2980 right: &DsaCalibrationRow,
2981) -> std::cmp::Ordering {
2982 compare_f64_lower_is_better(
2983 left.pass_run_nuisance_delta_vs_raw_boundary,
2984 right.pass_run_nuisance_delta_vs_raw_boundary,
2985 )
2986 .then_with(|| {
2987 compare_f64_lower_is_better(
2988 left.pass_run_nuisance_delta_vs_ewma,
2989 right.pass_run_nuisance_delta_vs_ewma,
2990 )
2991 })
2992 .then_with(|| left.failure_run_recall.cmp(&right.failure_run_recall))
2993 .then_with(|| {
2994 compare_option_f64(
2995 averaged_lead_delta(
2996 left.mean_lead_delta_vs_threshold_runs,
2997 left.mean_lead_delta_vs_ewma_runs,
2998 ),
2999 averaged_lead_delta(
3000 right.mean_lead_delta_vs_threshold_runs,
3001 right.mean_lead_delta_vs_ewma_runs,
3002 ),
3003 )
3004 })
3005 .then_with(|| compare_option_f64(left.precursor_quality, right.precursor_quality))
3006 .then_with(|| compare_option_f64(left.compression_ratio, right.compression_ratio))
3007 .then_with(|| {
3008 right
3009 .corroborating_feature_count_min
3010 .cmp(&left.corroborating_feature_count_min)
3011 })
3012}
3013
3014fn compare_dsa_rows_by_success_closeness(
3015 left: &DsaCalibrationRow,
3016 right: &DsaCalibrationRow,
3017) -> std::cmp::Ordering {
3018 compare_usize_lower_is_better(
3019 unmet_primary_success_conditions(left),
3020 unmet_primary_success_conditions(right),
3021 )
3022 .then_with(|| {
3023 compare_usize_lower_is_better(
3024 recall_shortfall_vs_primary_success_floor(left),
3025 recall_shortfall_vs_primary_success_floor(right),
3026 )
3027 })
3028 .then_with(|| {
3029 compare_f64_lower_is_better(
3030 ewma_nuisance_gap_to_primary_success(left),
3031 ewma_nuisance_gap_to_primary_success(right),
3032 )
3033 })
3034 .then_with(|| compare_dsa_rows_by_priority(left, right))
3035}
3036
3037fn unmet_primary_success_conditions(row: &DsaCalibrationRow) -> usize {
3038 usize::from(ewma_nuisance_gap_to_primary_success(row) > 0.0)
3039 + usize::from(recall_shortfall_vs_primary_success_floor(row) > 0)
3040}
3041
3042fn recall_shortfall_vs_primary_success_floor(row: &DsaCalibrationRow) -> usize {
3043 let recall_floor = row
3044 .threshold_failure_run_recall
3045 .saturating_sub(DSA_PRIMARY_SUCCESS_RECALL_TOLERANCE_RUNS);
3046 recall_floor.saturating_sub(row.failure_run_recall)
3047}
3048
3049fn ewma_nuisance_gap_to_primary_success(row: &DsaCalibrationRow) -> f64 {
3050 row.pass_run_nuisance_delta_vs_ewma.max(0.0)
3051}
3052
3053fn compare_usize_lower_is_better(left: usize, right: usize) -> std::cmp::Ordering {
3054 right.cmp(&left)
3055}
3056
3057fn compare_f64_lower_is_better(left: f64, right: f64) -> std::cmp::Ordering {
3058 right
3059 .partial_cmp(&left)
3060 .unwrap_or(std::cmp::Ordering::Equal)
3061}
3062
3063fn compare_option_f64(left: Option<f64>, right: Option<f64>) -> std::cmp::Ordering {
3064 match (left, right) {
3065 (Some(left), Some(right)) => left
3066 .partial_cmp(&right)
3067 .unwrap_or(std::cmp::Ordering::Equal),
3068 (Some(_), None) => std::cmp::Ordering::Greater,
3069 (None, Some(_)) => std::cmp::Ordering::Less,
3070 (None, None) => std::cmp::Ordering::Equal,
3071 }
3072}
3073
3074fn averaged_lead_delta(threshold_delta: Option<f64>, ewma_delta: Option<f64>) -> Option<f64> {
3075 let mut values = Vec::new();
3076 if let Some(value) = threshold_delta {
3077 values.push(value);
3078 }
3079 if let Some(value) = ewma_delta {
3080 values.push(value);
3081 }
3082 (!values.is_empty()).then(|| values.iter().sum::<f64>() / values.len() as f64)
3083}
3084
3085fn cross_feature_corroboration_effect(
3086 corroboration_summaries: &[DsaCorroborationSummary],
3087) -> String {
3088 let Some(base) = corroboration_summaries
3089 .iter()
3090 .find(|summary| summary.corroborating_feature_count_min == 2)
3091 .and_then(|summary| summary.representative_row.as_ref())
3092 else {
3093 return "Cross-feature corroboration effect could not be determined from the saved grid."
3094 .into();
3095 };
3096
3097 let higher_rows = corroboration_summaries
3098 .iter()
3099 .filter(|summary| summary.corroborating_feature_count_min > 2)
3100 .filter_map(|summary| summary.representative_row.as_ref())
3101 .collect::<Vec<_>>();
3102 if higher_rows.is_empty() {
3103 return "Cross-feature corroboration effect could not be determined from the saved grid."
3104 .into();
3105 }
3106
3107 let all_lower_nuisance = higher_rows.iter().all(|row| {
3108 row.pass_run_nuisance_delta_vs_raw_boundary < base.pass_run_nuisance_delta_vs_raw_boundary
3109 && row.pass_run_nuisance_delta_vs_ewma < base.pass_run_nuisance_delta_vs_ewma
3110 });
3111 let any_lower_recall = higher_rows
3112 .iter()
3113 .any(|row| row.failure_run_recall < base.failure_run_recall);
3114 let any_higher_recall = higher_rows
3115 .iter()
3116 .any(|row| row.failure_run_recall > base.failure_run_recall);
3117
3118 if all_lower_nuisance && any_lower_recall {
3119 "Higher cross-feature corroboration reduced nuisance but degraded recall relative to m=2."
3120 .into()
3121 } else if all_lower_nuisance && !any_lower_recall {
3122 "Higher cross-feature corroboration reduced nuisance without reducing recall relative to m=2."
3123 .into()
3124 } else if !all_lower_nuisance && any_lower_recall {
3125 "Higher cross-feature corroboration degraded recall without a consistent nuisance benefit relative to m=2."
3126 .into()
3127 } else if any_higher_recall {
3128 "Higher cross-feature corroboration improved recall at some settings, but nuisance trade-offs remained mixed relative to m=2."
3129 .into()
3130 } else {
3131 "Cross-feature corroboration produced mixed nuisance and recall trade-offs across m=2,3,5."
3132 .into()
3133 }
3134}
3135
3136fn limiting_factor(rows: &[DsaCalibrationRow]) -> String {
3137 let any_recall_gate_passed = rows.iter().any(|row| row.threshold_recall_gate_passed);
3138 let any_ewma_nuisance_success = rows.iter().any(|row| {
3139 row.primary_success_condition_met
3140 || !row.success_condition_failures.contains("EWMA nuisance")
3141 });
3142 if !any_recall_gate_passed {
3143 "Recall was the limiting factor across the saved grid.".into()
3144 } else if !any_ewma_nuisance_success {
3145 "Nuisance relative to EWMA was the limiting factor across the saved grid.".into()
3146 } else if rows.iter().any(|row| row.primary_success_condition_met) {
3147 "The saved grid contains at least one row that satisfies the legacy one-run-tolerance nuisance/recall sweep gate."
3148 .into()
3149 } else {
3150 "Both nuisance and recall remained limiting factors across different parts of the saved grid.".into()
3151 }
3152}
3153
3154fn rate(count: usize, total: usize) -> f64 {
3155 if total == 0 {
3156 0.0
3157 } else {
3158 count as f64 / total as f64
3159 }
3160}
3161
3162fn count_present<I, T>(iter: I) -> usize
3163where
3164 I: Iterator<Item = Option<T>>,
3165{
3166 iter.filter(|value| value.is_some()).count()
3167}
3168
3169fn mean_usize(values: &[usize]) -> Option<f64> {
3170 (!values.is_empty()).then_some(values.iter().sum::<usize>() as f64 / values.len() as f64)
3171}
3172
3173fn mean_option_usize(values: &[Option<usize>]) -> Option<f64> {
3174 let present = values.iter().flatten().copied().collect::<Vec<_>>();
3175 mean_usize(&present)
3176}
3177
3178fn mean_option_i64(values: &[Option<i64>]) -> Option<f64> {
3179 let present = values.iter().flatten().copied().collect::<Vec<_>>();
3180 (!present.is_empty()).then_some(present.iter().sum::<i64>() as f64 / present.len() as f64)
3181}
3182
3183fn median_option_usize(values: &[Option<usize>]) -> Option<f64> {
3184 let mut present = values.iter().flatten().copied().collect::<Vec<_>>();
3185 if present.is_empty() {
3186 return None;
3187 }
3188 present.sort_unstable();
3189 let middle = present.len() / 2;
3190 if present.len() % 2 == 1 {
3191 Some(present[middle] as f64)
3192 } else {
3193 Some((present[middle - 1] + present[middle]) as f64 / 2.0)
3194 }
3195}
3196
3197fn format_option_f64(value: Option<f64>) -> String {
3198 value
3199 .map(|value| format!("{value:.4}"))
3200 .unwrap_or_else(|| "n/a".into())
3201}
3202
3203#[cfg(test)]
3204mod tests {
3205 use super::*;
3206
3207 #[test]
3208 fn dsa_persistence_gating_requires_consecutive_hits() {
3209 let alert = persistence_mask(&[false, true, true, false, true, true, true], 2);
3210 assert_eq!(alert, vec![false, false, true, false, false, true, true]);
3211 }
3212
3213 #[test]
3214 fn dsa_consistency_uses_thresholded_direction_regimes() {
3215 assert!(window_is_consistent(&[0.0, 0.2, 0.1, 0.0], 0.15, 0, 3));
3216 assert!(window_is_consistent(&[0.0, 0.2, -0.1, 0.3], 0.15, 0, 3));
3217 assert!(!window_is_consistent(&[0.0, 0.2, -0.2, 0.3], 0.15, 0, 3));
3218 assert!(!window_is_consistent(&[-0.2, -0.3, -0.1], 0.15, 0, 2));
3219 }
3220
3221 #[test]
3222 fn episode_ranges_are_computed_deterministically() {
3223 assert_eq!(
3224 episode_ranges(&[false, true, true, false, true, false]),
3225 vec![(1, 2), (4, 4)]
3226 );
3227 }
3228
3229 #[test]
3230 fn bounded_dsa_grid_matches_requested_size() {
3231 let grid = DsaCalibrationGrid::bounded_default();
3232 assert_eq!(grid.grid_point_count(), 81);
3233 }
3234
3235 #[test]
3236 fn ewma_normalization_is_clipped() {
3237 assert_eq!(normalize_to_threshold(5.0, 2.0), 1.0);
3238 assert_eq!(normalize_to_threshold(1.0, 2.0), 0.5);
3239 assert_eq!(normalize_to_threshold(1.0, 0.0), 0.0);
3240 }
3241
3242 #[test]
3243 fn closest_to_success_prefers_small_recall_shortfall_over_zero_recall() {
3244 let summary = summarize_dsa_grid(&[
3245 DsaCalibrationRow {
3246 config_id: 0,
3247 primary_run_signal: "feature_count_dsa_alert(k) >= 2".into(),
3248 window: 5,
3249 persistence_runs: 2,
3250 alert_tau: 2.0,
3251 corroborating_feature_count_min: 2,
3252 failure_run_recall: 102,
3253 failure_runs: 104,
3254 threshold_failure_run_recall: 104,
3255 ewma_failure_run_recall: 104,
3256 failure_recall_delta_vs_threshold: -2,
3257 failure_recall_delta_vs_ewma: -2,
3258 mean_lead_time_runs: Some(19.2),
3259 median_lead_time_runs: Some(20.0),
3260 pass_run_nuisance_proxy: 0.94,
3261 mean_lead_delta_vs_cusum_runs: Some(-0.7),
3262 mean_lead_delta_vs_run_energy_runs: Some(2.8),
3263 mean_lead_delta_vs_pca_fdc_runs: Some(0.1),
3264 mean_lead_delta_vs_threshold_runs: Some(-0.6),
3265 mean_lead_delta_vs_ewma_runs: Some(-0.6),
3266 pass_run_nuisance_delta_vs_cusum: -0.06,
3267 pass_run_nuisance_delta_vs_run_energy: 0.41,
3268 pass_run_nuisance_delta_vs_pca_fdc: 0.01,
3269 pass_run_nuisance_delta_vs_threshold: -0.03,
3270 pass_run_nuisance_delta_vs_ewma: -0.04,
3271 pass_run_nuisance_delta_vs_raw_boundary: -0.05,
3272 raw_boundary_episode_count: 100,
3273 dsa_episode_count: 10,
3274 dsa_episodes_preceding_failure: 10,
3275 mean_dsa_episode_length_runs: Some(5.0),
3276 max_dsa_episode_length_runs: 8,
3277 compression_ratio: Some(10.0),
3278 precursor_quality: Some(1.0),
3279 non_escalating_dsa_episode_fraction: Some(0.0),
3280 nuisance_improved: true,
3281 lead_time_improved: false,
3282 recall_preserved: false,
3283 compression_improved: true,
3284 any_metric_improved: true,
3285 nothing_improved: false,
3286 threshold_recall_gate_passed: false,
3287 boundary_nuisance_gate_passed: true,
3288 primary_success_condition_met: true,
3289 validation_passed: false,
3290 success_condition_failures: String::new(),
3291 validation_failures: "recall shortfall".into(),
3292 },
3293 DsaCalibrationRow {
3294 config_id: 1,
3295 primary_run_signal: "feature_count_dsa_alert(k) >= 5".into(),
3296 window: 15,
3297 persistence_runs: 4,
3298 alert_tau: 3.0,
3299 corroborating_feature_count_min: 5,
3300 failure_run_recall: 0,
3301 failure_runs: 104,
3302 threshold_failure_run_recall: 104,
3303 ewma_failure_run_recall: 104,
3304 failure_recall_delta_vs_threshold: -104,
3305 failure_recall_delta_vs_ewma: -104,
3306 mean_lead_time_runs: None,
3307 median_lead_time_runs: None,
3308 pass_run_nuisance_proxy: 0.0,
3309 mean_lead_delta_vs_cusum_runs: None,
3310 mean_lead_delta_vs_run_energy_runs: None,
3311 mean_lead_delta_vs_pca_fdc_runs: None,
3312 mean_lead_delta_vs_threshold_runs: None,
3313 mean_lead_delta_vs_ewma_runs: None,
3314 pass_run_nuisance_delta_vs_cusum: -1.0,
3315 pass_run_nuisance_delta_vs_run_energy: -0.5,
3316 pass_run_nuisance_delta_vs_pca_fdc: -0.9,
3317 pass_run_nuisance_delta_vs_threshold: -0.97,
3318 pass_run_nuisance_delta_vs_ewma: -0.98,
3319 pass_run_nuisance_delta_vs_raw_boundary: -0.99,
3320 raw_boundary_episode_count: 100,
3321 dsa_episode_count: 0,
3322 dsa_episodes_preceding_failure: 0,
3323 mean_dsa_episode_length_runs: None,
3324 max_dsa_episode_length_runs: 0,
3325 compression_ratio: None,
3326 precursor_quality: None,
3327 non_escalating_dsa_episode_fraction: None,
3328 nuisance_improved: true,
3329 lead_time_improved: false,
3330 recall_preserved: false,
3331 compression_improved: false,
3332 any_metric_improved: true,
3333 nothing_improved: false,
3334 threshold_recall_gate_passed: false,
3335 boundary_nuisance_gate_passed: true,
3336 primary_success_condition_met: false,
3337 validation_passed: false,
3338 success_condition_failures: "recall shortfall".into(),
3339 validation_failures: "recall shortfall".into(),
3340 },
3341 ]);
3342
3343 assert_eq!(summary.closest_to_success.unwrap().config_id, 0);
3344 }
3345
3346 #[test]
3347 fn policy_suppression_is_triggered_for_fragmented_recurrent_hits() {
3348 let policy = heuristic_policy_definition("recurrent_boundary_approach").unwrap();
3349 let flags = vec![
3350 true, false, false, true, false, false, false, false, false, true,
3351 ];
3352 let contribution =
3353 motif_contribution_state(policy, None, &flags, 9, true, true, true, false).unwrap();
3354
3355 assert_eq!(contribution.contribution_state, DsaPolicyState::Silent);
3356 assert!(contribution.suppressed_to_silent);
3357 assert!(contribution.fragmentation_proxy > policy.maximum_allowed_fragmentation());
3358 }
3359
3360 #[test]
3361 fn silent_motif_promotes_to_escalate_under_numeric_and_violation_corroboration() {
3362 let policy = heuristic_policy_definition("transient_excursion").unwrap();
3363 let flags = vec![false, false, true, true, true];
3364 let contribution =
3365 motif_contribution_state(policy, None, &flags, 4, true, true, true, true).unwrap();
3366
3367 assert_eq!(contribution.contribution_state, DsaPolicyState::Escalate);
3368 assert!(!contribution.suppressed_to_silent);
3369 }
3370
3371 #[test]
3372 fn corroboration_counts_only_review_or_escalate_states() {
3373 let run_count = 2;
3374 let config = DsaConfig {
3375 corroborating_feature_count_min: 2,
3376 ..DsaConfig::default()
3377 };
3378
3379 let mut review_trace = empty_trace(0, "S000", run_count);
3380 review_trace.dsa_alert = vec![true, false];
3381 review_trace.numeric_dsa_alert = vec![true, false];
3382 review_trace.policy_state = vec![DsaPolicyState::Review, DsaPolicyState::Silent];
3383
3384 let mut escalate_trace = empty_trace(1, "S001", run_count);
3385 escalate_trace.dsa_alert = vec![true, false];
3386 escalate_trace.numeric_dsa_alert = vec![true, false];
3387 escalate_trace.policy_state = vec![DsaPolicyState::Escalate, DsaPolicyState::Silent];
3388
3389 let mut watch_trace = empty_trace(2, "S002", run_count);
3390 watch_trace.dsa_alert = vec![false, false];
3391 watch_trace.numeric_dsa_alert = vec![true, true];
3392 watch_trace.policy_state = vec![DsaPolicyState::Watch, DsaPolicyState::Watch];
3393
3394 let signals = build_run_signals(
3395 &[review_trace, escalate_trace, watch_trace],
3396 &[false, false],
3397 &config,
3398 run_count,
3399 );
3400
3401 assert_eq!(signals.primary_run_alert, vec![true, false]);
3402 assert_eq!(signals.feature_count_dsa_alert, vec![2, 0]);
3403 assert_eq!(signals.watch_feature_count, vec![1, 1]);
3404 assert_eq!(signals.review_feature_count, vec![1, 0]);
3405 assert_eq!(signals.escalate_feature_count, vec![1, 0]);
3406 assert_eq!(signals.strict_escalate_run_alert, vec![false, false]);
3407 assert_eq!(signals.numeric_primary_run_alert, vec![true, false]);
3408 assert_eq!(signals.numeric_feature_count_dsa_alert, vec![3, 1]);
3409 }
3410
3411 #[test]
3412 fn priority_two_rescue_can_recover_inconsistent_watch_near_miss() {
3413 let override_entry = FeaturePolicyOverride {
3414 feature_index: 133,
3415 feature_name: "S134".into(),
3416 alert_class_override: None,
3417 requires_persistence_override: Some(false),
3418 requires_corroboration_override: Some(false),
3419 minimum_window_override: Some(5),
3420 minimum_hits_override: Some(4),
3421 maximum_allowed_fragmentation_override: Some(0.5),
3422 rescue_eligible: true,
3423 rescue_priority: 2,
3424 allow_watch_only: Some(false),
3425 allow_review_without_escalate: Some(true),
3426 suppress_if_isolated: Some(false),
3427 override_reason: "test".into(),
3428 };
3429 let rescue = RecallRescueConfig::default();
3430 let config = DsaConfig {
3431 alert_tau: 2.0,
3432 ..DsaConfig::default()
3433 };
3434 let resolved = vec![
3435 HeuristicAlertClass::Silent,
3436 HeuristicAlertClass::Watch,
3437 HeuristicAlertClass::Watch,
3438 HeuristicAlertClass::Watch,
3439 HeuristicAlertClass::Watch,
3440 ];
3441 let mut policy_state = vec![DsaPolicyState::Silent; 5];
3442 let mut dsa_alert = vec![false; 5];
3443 let fragmentation = vec![0.0, 0.25, 0.25, 0.25, 0.25];
3444 let dsa_score = vec![0.0, 1.3, 1.45, 1.55, 1.70];
3445 let boundary = vec![0.0, 0.2, 0.3, 0.4, 0.4];
3446 let ewma = vec![0.2, 0.5, 0.6, 0.66, 0.70];
3447 let motif = vec![0.0, 0.2, 0.3, 0.4, 0.4];
3448 let semantic_support = vec![false; 5];
3449 let consistent = vec![true, true, true, false, false];
3450
3451 let transition = apply_recall_rescue(
3452 Some(&override_entry),
3453 &rescue,
3454 &config,
3455 &resolved,
3456 &mut policy_state,
3457 &mut dsa_alert,
3458 &semantic_support,
3459 &fragmentation,
3460 &dsa_score,
3461 &boundary,
3462 &ewma,
3463 &motif,
3464 &consistent,
3465 4,
3466 );
3467
3468 assert_eq!(transition, Some("watch_to_review"));
3469 assert_eq!(policy_state[4], DsaPolicyState::Review);
3470 assert!(dsa_alert[4]);
3471 }
3472
3473 #[test]
3474 fn rescue_respects_feature_specific_fragmentation_override() {
3475 let override_entry = FeaturePolicyOverride {
3476 feature_index: 274,
3477 feature_name: "S275".into(),
3478 alert_class_override: None,
3479 requires_persistence_override: Some(false),
3480 requires_corroboration_override: Some(false),
3481 minimum_window_override: Some(5),
3482 minimum_hits_override: Some(4),
3483 maximum_allowed_fragmentation_override: Some(1.0),
3484 rescue_eligible: true,
3485 rescue_priority: 2,
3486 allow_watch_only: Some(false),
3487 allow_review_without_escalate: Some(true),
3488 suppress_if_isolated: Some(false),
3489 override_reason: "test".into(),
3490 };
3491 let rescue = RecallRescueConfig::default();
3492 let config = DsaConfig {
3493 alert_tau: 2.0,
3494 ..DsaConfig::default()
3495 };
3496 let resolved = vec![
3497 HeuristicAlertClass::Watch,
3498 HeuristicAlertClass::Watch,
3499 HeuristicAlertClass::Watch,
3500 HeuristicAlertClass::Watch,
3501 HeuristicAlertClass::Watch,
3502 ];
3503 let mut policy_state = vec![DsaPolicyState::Silent; 5];
3504 let mut dsa_alert = vec![false; 5];
3505 let fragmentation = vec![1.0; 5];
3506 let dsa_score = vec![1.65, 1.70, 1.74, 1.79, 1.83];
3507 let boundary = vec![0.4, 0.4, 0.4, 0.5, 0.5];
3508 let ewma = vec![0.68, 0.69, 0.70, 0.71, 0.72];
3509 let motif = vec![0.4, 0.4, 0.4, 0.5, 0.5];
3510 let semantic_support = vec![false; 5];
3511 let consistent = vec![true; 5];
3512
3513 let transition = apply_recall_rescue(
3514 Some(&override_entry),
3515 &rescue,
3516 &config,
3517 &resolved,
3518 &mut policy_state,
3519 &mut dsa_alert,
3520 &semantic_support,
3521 &fragmentation,
3522 &dsa_score,
3523 &boundary,
3524 &ewma,
3525 &motif,
3526 &consistent,
3527 4,
3528 );
3529
3530 assert_eq!(transition, Some("watch_to_review"));
3531 assert_eq!(policy_state[4], DsaPolicyState::Review);
3532 assert!(dsa_alert[4]);
3533 }
3534
3535 #[test]
3536 fn priority_three_semantic_support_can_recover_silent_failure_local_near_miss() {
3537 let override_entry = FeaturePolicyOverride {
3538 feature_index: 91,
3539 feature_name: "S092".into(),
3540 alert_class_override: Some(HeuristicAlertClass::Watch),
3541 requires_persistence_override: Some(false),
3542 requires_corroboration_override: Some(false),
3543 minimum_window_override: Some(2),
3544 minimum_hits_override: Some(1),
3545 maximum_allowed_fragmentation_override: Some(1.0),
3546 rescue_eligible: true,
3547 rescue_priority: 3,
3548 allow_watch_only: Some(false),
3549 allow_review_without_escalate: Some(true),
3550 suppress_if_isolated: Some(false),
3551 override_reason: "failure-local support".into(),
3552 };
3553 let rescue = RecallRescueConfig::default();
3554 let config = DsaConfig {
3555 alert_tau: 2.0,
3556 ..DsaConfig::default()
3557 };
3558 let resolved = vec![HeuristicAlertClass::Silent, HeuristicAlertClass::Silent];
3559 let mut policy_state = vec![DsaPolicyState::Silent; 2];
3560 let mut dsa_alert = vec![false; 2];
3561 let semantic_support = vec![true, true];
3562 let fragmentation = vec![0.5, 0.5];
3563 let dsa_score = vec![1.0, 1.0];
3564 let boundary = vec![0.5, 0.5];
3565 let ewma = vec![1.0, 1.0];
3566 let motif = vec![0.5, 0.5];
3567 let consistent = vec![true, true];
3568
3569 let transition = apply_recall_rescue(
3570 Some(&override_entry),
3571 &rescue,
3572 &config,
3573 &resolved,
3574 &mut policy_state,
3575 &mut dsa_alert,
3576 &semantic_support,
3577 &fragmentation,
3578 &dsa_score,
3579 &boundary,
3580 &ewma,
3581 &motif,
3582 &consistent,
3583 1,
3584 );
3585
3586 assert_eq!(transition, Some("silent_to_review"));
3587 assert_eq!(policy_state[1], DsaPolicyState::Review);
3588 assert!(dsa_alert[1]);
3589 }
3590
3591 #[test]
3592 fn priority_three_semantic_support_can_substitute_for_boundary_density_on_recovery_pattern() {
3593 let override_entry = FeaturePolicyOverride {
3594 feature_index: 227,
3595 feature_name: "S228".into(),
3596 alert_class_override: Some(HeuristicAlertClass::Watch),
3597 requires_persistence_override: Some(false),
3598 requires_corroboration_override: Some(false),
3599 minimum_window_override: Some(2),
3600 minimum_hits_override: Some(1),
3601 maximum_allowed_fragmentation_override: Some(1.0),
3602 rescue_eligible: true,
3603 rescue_priority: 3,
3604 allow_watch_only: Some(false),
3605 allow_review_without_escalate: Some(true),
3606 suppress_if_isolated: Some(false),
3607 override_reason: "failure-local recovery support".into(),
3608 };
3609 let rescue = RecallRescueConfig::default();
3610 let config = DsaConfig {
3611 alert_tau: 2.0,
3612 ..DsaConfig::default()
3613 };
3614 let resolved = vec![HeuristicAlertClass::Silent, HeuristicAlertClass::Silent];
3615 let mut policy_state = vec![DsaPolicyState::Silent; 2];
3616 let mut dsa_alert = vec![false; 2];
3617 let semantic_support = vec![false, true];
3618 let fragmentation = vec![0.0, 0.0];
3619 let dsa_score = vec![0.8, 1.0];
3620 let boundary = vec![0.0, 0.0];
3621 let ewma = vec![1.0, 1.0];
3622 let motif = vec![0.0, 0.0];
3623 let consistent = vec![true, true];
3624
3625 let transition = apply_recall_rescue(
3626 Some(&override_entry),
3627 &rescue,
3628 &config,
3629 &resolved,
3630 &mut policy_state,
3631 &mut dsa_alert,
3632 &semantic_support,
3633 &fragmentation,
3634 &dsa_score,
3635 &boundary,
3636 &ewma,
3637 &motif,
3638 &consistent,
3639 1,
3640 );
3641
3642 assert_eq!(transition, Some("silent_to_review"));
3643 assert_eq!(policy_state[1], DsaPolicyState::Review);
3644 assert!(dsa_alert[1]);
3645 }
3646}