dsfb_semiconductor/policy/
mod.rs1#[cfg(feature = "std")]
2use crate::error::Result;
3use crate::grammar::layer::GrammarState;
4use crate::semantics::SemanticMatch;
5use serde::{Deserialize, Serialize};
6#[cfg(feature = "std")]
7use std::collections::BTreeMap;
8#[cfg(not(feature = "std"))]
9use alloc::{collections::BTreeMap, string::{String, ToString}, vec::Vec};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct PolicyDecision {
13 pub timestamp: f64,
14 pub decision: String,
15}
16
17pub fn derive_policy(
18 semantic_matches: &[SemanticMatch],
19 grammar_states: &[GrammarState],
20) -> Vec<PolicyDecision> {
21 let mut by_timestamp = BTreeMap::<u64, String>::new();
22
23 for state in grammar_states {
24 let fallback = if state.state == "Admissible" {
25 "Silent"
26 } else {
27 "Watch"
28 };
29 by_timestamp
30 .entry(state.timestamp.to_bits())
31 .and_modify(|current| {
32 if decision_rank(fallback) > decision_rank(current) {
33 *current = fallback.to_string();
34 }
35 })
36 .or_insert_with(|| fallback.to_string());
37 }
38
39 for semantic in semantic_matches {
40 by_timestamp
41 .entry(semantic.timestamp.to_bits())
42 .and_modify(|current| {
43 if decision_rank(&semantic.action) > decision_rank(current) {
44 *current = semantic.action.clone();
45 }
46 })
47 .or_insert_with(|| semantic.action.clone());
48 }
49
50 by_timestamp
51 .into_iter()
52 .map(|(timestamp_bits, decision)| PolicyDecision {
53 timestamp: f64::from_bits(timestamp_bits),
54 decision,
55 })
56 .collect()
57}
58
59#[cfg(feature = "std")]
60pub fn write_policy_decisions_csv(
61 path: &std::path::Path,
62 rows: &[PolicyDecision],
63) -> Result<()> {
64 let mut writer = csv::Writer::from_path(path)?;
65 for row in rows {
66 writer.serialize(row)?;
67 }
68 writer.flush()?;
69 Ok(())
70}
71
72fn decision_rank(decision: &str) -> usize {
73 match decision {
74 "Escalate" => 3,
75 "Review" => 2,
76 "Watch" => 1,
77 _ => 0,
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn policy_uses_semantics_and_grammar_only() {
87 let grammar = vec![
88 GrammarState {
89 feature_id: "S059".into(),
90 state: "BoundaryGrazing".into(),
91 timestamp: 1.0,
92 },
93 GrammarState {
94 feature_id: "S059".into(),
95 state: "Admissible".into(),
96 timestamp: 2.0,
97 },
98 ];
99 let semantics = vec![SemanticMatch {
100 timestamp: 1.0,
101 feature_id: "S059".into(),
102 heuristic_id: "failure_slow_drift_review".into(),
103 motif_type: "slow_drift_precursor".into(),
104 grammar_state: "SustainedDrift".into(),
105 action: "Review".into(),
106 }];
107 let decisions = derive_policy(&semantics, &grammar);
108 assert_eq!(decisions[0].decision, "Review");
109 assert_eq!(decisions[1].decision, "Silent");
110 }
111}