dsfb_semiconductor/semantics/
mod.rs1#[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}