use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
pub fn encode_rl_state_key(
health_level: u8,
event_rate_q: u8,
activity_count_q: u8,
spc_alert_level: u8,
drift_status: u8,
rework_ratio_q: u8,
circuit_state: u8,
cycle_phase: u8,
) -> u64 {
((health_level & 0x0F) as u64)
| (((event_rate_q & 0x0F) as u64) << 4)
| (((activity_count_q & 0x0F) as u64) << 8)
| (((spc_alert_level & 0x0F) as u64) << 12)
| (((drift_status & 0x07) as u64) << 16)
| (((rework_ratio_q & 0x0F) as u64) << 19)
| (((circuit_state & 0x07) as u64) << 23)
| (((cycle_phase & 0x0F) as u64) << 26)
}
pub fn decode_rl_state_key(key: u64) -> (u8, u8, u8, u8, u8, u8, u8, u8) {
(
(key & 0x0F) as u8, ((key >> 4) & 0x0F) as u8, ((key >> 8) & 0x0F) as u8, ((key >> 12) & 0x0F) as u8, ((key >> 16) & 0x07) as u8, ((key >> 19) & 0x0F) as u8, ((key >> 23) & 0x07) as u8, ((key >> 26) & 0x0F) as u8, )
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializedAgentQTable {
pub agent_type: u8,
pub state_values: BTreeMap<u64, Vec<f32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializedRlState {
pub telemetry: RlTelemetry,
pub active_agent: u8,
pub linucb_enabled: bool,
#[serde(default)]
pub agent_q_tables: Vec<SerializedAgentQTable>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RlTelemetry {
pub cycle_count: u64,
pub last_health_state: u8,
pub last_action_label: String,
pub last_spc_alert_count: usize,
pub cumulative_reward: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_decode_rl_state_key() {
let key = encode_rl_state_key(1, 2, 3, 4, 5, 6, 7, 0);
let (h, e, a, s, d, r, c, p) = decode_rl_state_key(key);
assert_eq!(h, 1);
assert_eq!(e, 2);
assert_eq!(a, 3);
assert_eq!(s, 4);
assert_eq!(d, 5);
assert_eq!(r, 6);
assert_eq!(c, 7);
assert_eq!(p, 0);
}
#[test]
fn test_serialized_agent_q_table() {
let mut state_values = BTreeMap::new();
let key = encode_rl_state_key(1, 2, 3, 4, 5, 6, 7, 0);
state_values.insert(key, vec![0.1, 0.2, 0.3, 0.4, 0.5]);
let table = SerializedAgentQTable {
agent_type: 0,
state_values,
};
let json = serde_json::to_string(&table).unwrap();
let restored: SerializedAgentQTable = serde_json::from_str(&json).unwrap();
assert_eq!(restored.agent_type, 0);
assert_eq!(restored.state_values.len(), 1);
let values = restored.state_values.get(&key).unwrap();
assert_eq!(values.len(), 5);
}
#[test]
fn test_serialized_rl_state_serialization() {
let state = SerializedRlState {
telemetry: RlTelemetry {
cycle_count: 42,
last_health_state: 2,
last_action_label: "ADAPT_TIMEOUT".to_string(),
last_spc_alert_count: 1,
cumulative_reward: -3.5,
},
active_agent: 0,
linucb_enabled: true,
agent_q_tables: vec![],
};
let json = serde_json::to_string(&state).unwrap();
let restored: SerializedRlState = serde_json::from_str(&json).unwrap();
assert_eq!(restored.telemetry.cycle_count, 42);
assert_eq!(restored.telemetry.last_health_state, 2);
assert_eq!(restored.telemetry.last_action_label, "ADAPT_TIMEOUT");
assert_eq!(restored.telemetry.last_spc_alert_count, 1);
assert_eq!(restored.telemetry.cumulative_reward, -3.5);
assert_eq!(restored.active_agent, 0);
assert_eq!(restored.linucb_enabled, true);
assert_eq!(restored.agent_q_tables.len(), 0);
}
#[test]
fn test_rl_telemetry_serialization() {
let telemetry = RlTelemetry {
cycle_count: 100,
last_health_state: 1,
last_action_label: "REDUCE_CONCURRENCY".to_string(),
last_spc_alert_count: 0,
cumulative_reward: 15.7,
};
let json = serde_json::to_string(&telemetry).unwrap();
let restored: RlTelemetry = serde_json::from_str(&json).unwrap();
assert_eq!(restored.cycle_count, 100);
assert_eq!(restored.last_health_state, 1);
assert_eq!(restored.last_action_label, "REDUCE_CONCURRENCY");
assert_eq!(restored.last_spc_alert_count, 0);
assert_eq!(restored.cumulative_reward, 15.7);
}
}