1use crate::KernelResult;
5use std::collections::BTreeMap;
6
7use super::action::ProposedAction;
8use super::constraint::ConstraintChannel;
9
10pub trait DomainAdapter: Send + Sync {
16 fn domain_name(&self) -> &str;
18
19 fn constraint_channels(&self) -> Vec<Box<dyn ConstraintChannel>>;
21
22 fn map_action_to_state(&self, action: &dyn ProposedAction) -> KernelResult<Vec<f64>>;
24
25 fn current_state(&self) -> KernelResult<Vec<f64>> {
32 Err(crate::KernelError::AdapterError(
33 "current_state not implemented".into(),
34 ))
35 }
36
37 fn detect_regime_change(&self, current: &[f64], proposed: &[f64]) -> bool;
39
40 fn format_domain_payload(
42 &self,
43 margins: &BTreeMap<String, f64>,
44 ) -> Option<serde_json::Value>;
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use crate::action::{ActionPriority, SimpleAction, StateDelta};
51 use chrono::Utc;
52
53 struct NullAdapter;
54
55 struct NullChannel;
56
57 impl ConstraintChannel for NullChannel {
58 fn name(&self) -> &str {
59 "null"
60 }
61 fn evaluate(&self, _state: &[f64]) -> KernelResult<f64> {
62 Ok(1.0)
63 }
64 fn dimension_names(&self) -> Vec<String> {
65 vec![]
66 }
67 }
68
69 impl DomainAdapter for NullAdapter {
70 fn domain_name(&self) -> &str {
71 "null"
72 }
73 fn constraint_channels(&self) -> Vec<Box<dyn ConstraintChannel>> {
74 vec![Box::new(NullChannel)]
75 }
76 fn map_action_to_state(&self, _action: &dyn ProposedAction) -> KernelResult<Vec<f64>> {
77 Ok(vec![])
78 }
79 fn detect_regime_change(&self, _current: &[f64], _proposed: &[f64]) -> bool {
80 false
81 }
82 fn format_domain_payload(
83 &self,
84 _margins: &BTreeMap<String, f64>,
85 ) -> Option<serde_json::Value> {
86 None
87 }
88 }
89
90 #[test]
91 fn null_adapter_provides_channels() {
92 let adapter = NullAdapter;
93 assert_eq!(adapter.domain_name(), "null");
94 assert_eq!(adapter.constraint_channels().len(), 1);
95 }
96
97 #[test]
98 fn null_adapter_maps_action() {
99 let adapter = NullAdapter;
100 let action = SimpleAction {
101 action_id: "a".into(),
102 agent_id: "b".into(),
103 proposed_at: Utc::now(),
104 state_deltas: vec![StateDelta {
105 dimension: "x".into(),
106 from_value: 0.0,
107 to_value: 1.0,
108 }],
109 priority: ActionPriority::Standard,
110 };
111 let state = adapter.map_action_to_state(&action).unwrap();
112 assert!(state.is_empty());
113 }
114
115 #[test]
116 fn adapter_is_object_safe() {
117 let adapter: Box<dyn DomainAdapter> = Box::new(NullAdapter);
118 assert_eq!(adapter.domain_name(), "null");
119 }
120}