Skip to main content

dsfb_semiconductor/semantics/
mod.rs

1#[cfg(feature = "std")]
2use crate::error::Result;
3use crate::grammar::layer::GrammarState;
4use crate::syntax::MotifTimelinePoint;
5use serde::{Deserialize, Serialize};
6#[cfg(feature = "std")]
7use std::collections::BTreeMap;
8#[cfg(not(feature = "std"))]
9use alloc::{collections::BTreeMap, string::String, vec::Vec};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct Heuristic {
13    pub id: String,
14    pub features: Vec<String>,
15    pub motif_signature: Vec<String>,
16    pub grammar_states: Vec<String>,
17    pub action: String,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub struct SemanticMatch {
22    pub timestamp: f64,
23    pub feature_id: String,
24    pub heuristic_id: String,
25    pub motif_type: String,
26    pub grammar_state: String,
27    pub action: String,
28}
29
30pub fn minimal_heuristics_bank() -> Vec<Heuristic> {
31    vec![
32        Heuristic {
33            id: "failure_slow_drift_review".into(),
34            features: vec!["S059".into(), "S133".into(), "S134".into(), "S275".into()],
35            motif_signature: vec!["slow_drift_precursor".into()],
36            grammar_states: vec!["SustainedDrift".into(), "PersistentViolation".into()],
37            action: "Review".into(),
38        },
39        Heuristic {
40            id: "nuisance_boundary_grazing_watch".into(),
41            features: vec!["S059".into(), "S104".into()],
42            motif_signature: vec!["boundary_grazing".into()],
43            grammar_states: vec!["BoundaryGrazing".into()],
44            action: "Watch".into(),
45        },
46        Heuristic {
47            id: "transition_instability_escalate".into(),
48            features: vec!["S123".into(), "S540".into(), "S128".into()],
49            motif_signature: vec![
50                "persistent_instability".into(),
51                "burst_instability".into(),
52                "transient_excursion".into(),
53            ],
54            grammar_states: vec!["TransientViolation".into(), "PersistentViolation".into()],
55            action: "Escalate".into(),
56        },
57        Heuristic {
58            id: "recovery_deescalate_silent".into(),
59            features: Vec::new(),
60            motif_signature: vec!["recovery_pattern".into()],
61            grammar_states: vec!["Recovery".into()],
62            action: "Silent".into(),
63        },
64        Heuristic {
65            id: "noise_suppress_silent".into(),
66            features: Vec::new(),
67            motif_signature: vec!["noise_like".into(), "null".into()],
68            grammar_states: vec!["Admissible".into()],
69            action: "Silent".into(),
70        },
71    ]
72}
73
74pub fn match_semantics(
75    motifs: &[MotifTimelinePoint],
76    grammar_states: &[GrammarState],
77    heuristics: &[Heuristic],
78) -> Vec<SemanticMatch> {
79    let motif_map = motifs.iter().fold(BTreeMap::new(), |mut acc, row| {
80        acc.insert(
81            (row.feature_id.clone(), row.timestamp.to_bits()),
82            row.motif_type.clone(),
83        );
84        acc
85    });
86
87    let mut matches = Vec::new();
88    for state in grammar_states {
89        let Some(motif_type) =
90            motif_map.get(&(state.feature_id.clone(), state.timestamp.to_bits()))
91        else {
92            continue;
93        };
94        for heuristic in heuristics {
95            let feature_match = heuristic.features.is_empty()
96                || heuristic
97                    .features
98                    .iter()
99                    .any(|feature| feature == &state.feature_id);
100            let motif_match = heuristic
101                .motif_signature
102                .iter()
103                .any(|motif| motif == motif_type);
104            let grammar_match = heuristic
105                .grammar_states
106                .iter()
107                .any(|grammar| grammar == &state.state);
108            if feature_match && motif_match && grammar_match {
109                matches.push(SemanticMatch {
110                    timestamp: state.timestamp,
111                    feature_id: state.feature_id.clone(),
112                    heuristic_id: heuristic.id.clone(),
113                    motif_type: motif_type.clone(),
114                    grammar_state: state.state.clone(),
115                    action: heuristic.action.clone(),
116                });
117            }
118        }
119    }
120
121    matches.sort_by(|left, right| {
122        left.timestamp
123            .total_cmp(&right.timestamp)
124            .then_with(|| left.feature_id.cmp(&right.feature_id))
125            .then_with(|| left.heuristic_id.cmp(&right.heuristic_id))
126    });
127    matches
128}
129
130#[cfg(feature = "std")]
131pub fn write_heuristics_bank_json(path: &std::path::Path, rows: &[Heuristic]) -> Result<()> {
132    let file = std::fs::File::create(path)?;
133    serde_json::to_writer_pretty(file, rows)?;
134    Ok(())
135}
136
137#[cfg(feature = "std")]
138pub fn write_semantic_matches_csv(path: &std::path::Path, rows: &[SemanticMatch]) -> Result<()> {
139    let mut writer = csv::Writer::from_path(path)?;
140    for row in rows {
141        writer.serialize(row)?;
142    }
143    writer.flush()?;
144    Ok(())
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn semantics_require_grammar_qualification() {
153        let motifs = vec![MotifTimelinePoint {
154            feature_id: "S059".into(),
155            motif_type: "slow_drift_precursor".into(),
156            timestamp: 1.0,
157        }];
158        let heuristics = minimal_heuristics_bank();
159        let no_grammar = match_semantics(&motifs, &[], &heuristics);
160        assert!(no_grammar.is_empty());
161
162        let grammar = vec![GrammarState {
163            feature_id: "S059".into(),
164            state: "SustainedDrift".into(),
165            timestamp: 1.0,
166        }];
167        let qualified = match_semantics(&motifs, &grammar, &heuristics);
168        assert_eq!(qualified.len(), 1);
169        assert_eq!(qualified[0].action, "Review");
170    }
171}