use eml_core::{EmlEvent, EmlModel};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GovernanceScorerModel {
inner: EmlModel,
}
impl Default for GovernanceScorerModel {
fn default() -> Self {
Self::new()
}
}
impl GovernanceScorerModel {
pub fn new() -> Self {
let mut inner = EmlModel::new(3, 5, 1);
inner.set_model_name("governance_scorer");
Self { inner }
}
pub fn drain_events(&mut self) -> Vec<EmlEvent> {
self.inner.drain_events()
}
pub fn is_trained(&self) -> bool {
self.inner.is_trained()
}
pub fn training_sample_count(&self) -> usize {
self.inner.training_sample_count()
}
pub fn predict(&self, risk: f64, fairness: f64, privacy: f64, novelty: f64, security: f64) -> f64 {
if !self.inner.is_trained() {
return (risk * risk
+ fairness * fairness
+ privacy * privacy
+ novelty * novelty
+ security * security)
.sqrt();
}
let inputs = [risk, fairness, privacy, novelty, security];
self.inner.predict_primary(&inputs).max(0.0)
}
pub fn record(
&mut self,
risk: f64,
fairness: f64,
privacy: f64,
novelty: f64,
security: f64,
score: f64,
) {
let inputs = [risk, fairness, privacy, novelty, security];
self.inner.record(&inputs, &[Some(score)]);
}
pub fn train(&mut self) -> bool {
self.inner.train()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RestartStrategyModel {
inner: EmlModel,
}
impl Default for RestartStrategyModel {
fn default() -> Self {
Self::new()
}
}
impl RestartStrategyModel {
pub fn new() -> Self {
let mut inner = EmlModel::new(2, 4, 2);
inner.set_model_name("restart_strategy");
Self { inner }
}
pub fn drain_events(&mut self) -> Vec<EmlEvent> {
self.inner.drain_events()
}
pub fn is_trained(&self) -> bool {
self.inner.is_trained()
}
pub fn training_sample_count(&self) -> usize {
self.inner.training_sample_count()
}
pub fn predict(
&self,
failure_count: u32,
failure_type: u32,
uptime_before_failure_secs: f64,
system_load: f64,
) -> (u64, bool) {
if !self.inner.is_trained() {
let base: u64 = 100;
let exponent = failure_count.saturating_sub(1);
let delay = base.saturating_mul(1u64 << exponent.min(20));
return (delay.min(30_000), true);
}
let inputs = [
failure_count as f64,
failure_type as f64,
uptime_before_failure_secs,
system_load,
];
let heads = self.inner.predict(&inputs);
let delay_ms = (heads[0].max(0.0) * 1000.0) as u64;
let should_retry = heads[1] > 0.5;
(delay_ms.min(60_000), should_retry)
}
pub fn record(
&mut self,
failure_count: u32,
failure_type: u32,
uptime_before_failure_secs: f64,
system_load: f64,
actual_delay_ms: u64,
recovery_succeeded: bool,
) {
let inputs = [
failure_count as f64,
failure_type as f64,
uptime_before_failure_secs,
system_load,
];
let targets = [
Some(actual_delay_ms as f64 / 1000.0),
Some(if recovery_succeeded { 1.0 } else { 0.0 }),
];
self.inner.record(&inputs, &targets);
}
pub fn train(&mut self) -> bool {
self.inner.train()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthThresholdModel {
inner: EmlModel,
}
impl Default for HealthThresholdModel {
fn default() -> Self {
Self::new()
}
}
impl HealthThresholdModel {
pub fn new() -> Self {
let mut inner = EmlModel::new(2, 3, 2);
inner.set_model_name("health_threshold");
Self { inner }
}
pub fn drain_events(&mut self) -> Vec<EmlEvent> {
self.inner.drain_events()
}
pub fn is_trained(&self) -> bool {
self.inner.is_trained()
}
pub fn training_sample_count(&self) -> usize {
self.inner.training_sample_count()
}
pub fn predict(
&self,
service_type: u32,
history_depth: u32,
recent_latency_ms: f64,
) -> (u32, u32) {
if !self.inner.is_trained() {
return (1, 3);
}
let inputs = [
service_type as f64,
history_depth as f64,
recent_latency_ms / 1000.0, ];
let heads = self.inner.predict(&inputs);
let degraded = (heads[0].max(1.0).min(20.0)).round() as u32;
let failed = (heads[1].max(1.0).min(20.0)).round() as u32;
(degraded, failed.max(degraded))
}
pub fn record(
&mut self,
service_type: u32,
history_depth: u32,
recent_latency_ms: f64,
optimal_degraded: u32,
optimal_failed: u32,
) {
let inputs = [
service_type as f64,
history_depth as f64,
recent_latency_ms / 1000.0,
];
let targets = [
Some(optimal_degraded as f64),
Some(optimal_failed as f64),
];
self.inner.record(&inputs, &targets);
}
pub fn train(&mut self) -> bool {
self.inner.train()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeadLetterModel {
inner: EmlModel,
}
impl Default for DeadLetterModel {
fn default() -> Self {
Self::new()
}
}
impl DeadLetterModel {
pub fn new() -> Self {
let mut inner = EmlModel::new(2, 3, 2);
inner.set_model_name("dead_letter");
Self { inner }
}
pub fn drain_events(&mut self) -> Vec<EmlEvent> {
self.inner.drain_events()
}
pub fn is_trained(&self) -> bool {
self.inner.is_trained()
}
pub fn training_sample_count(&self) -> usize {
self.inner.training_sample_count()
}
pub fn predict(
&self,
retry_count: u32,
message_age_ms: u64,
queue_depth: usize,
) -> (u64, bool) {
if !self.inner.is_trained() {
let delay = (1000u64).saturating_mul(1u64 << retry_count.min(20));
return (delay.min(60_000), retry_count >= 5);
}
let inputs = [
retry_count as f64,
message_age_ms as f64 / 1000.0, queue_depth as f64,
];
let heads = self.inner.predict(&inputs);
let delay_ms = (heads[0].max(0.0) * 1000.0) as u64;
let should_discard = heads[1] > 0.5;
(delay_ms.min(120_000), should_discard)
}
pub fn record(
&mut self,
retry_count: u32,
message_age_ms: u64,
queue_depth: usize,
actual_delay_ms: u64,
delivery_succeeded: bool,
) {
let inputs = [
retry_count as f64,
message_age_ms as f64 / 1000.0,
queue_depth as f64,
];
let targets = [
Some(actual_delay_ms as f64 / 1000.0),
Some(if delivery_succeeded { 0.0 } else { 1.0 }), ];
self.inner.record(&inputs, &targets);
}
pub fn train(&mut self) -> bool {
self.inner.train()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GossipTimingModel {
inner: EmlModel,
}
impl Default for GossipTimingModel {
fn default() -> Self {
Self::new()
}
}
impl GossipTimingModel {
pub fn new() -> Self {
let mut inner = EmlModel::new(2, 3, 1);
inner.set_model_name("gossip_timing");
Self { inner }
}
pub fn drain_events(&mut self) -> Vec<EmlEvent> {
self.inner.drain_events()
}
pub fn is_trained(&self) -> bool {
self.inner.is_trained()
}
pub fn training_sample_count(&self) -> usize {
self.inner.training_sample_count()
}
pub fn predict(
&self,
peer_count: usize,
network_latency_ms: f64,
update_frequency_hz: f64,
) -> f64 {
if !self.inner.is_trained() {
return 5.0;
}
let inputs = [
peer_count as f64,
network_latency_ms / 1000.0, update_frequency_hz,
];
self.inner.predict_primary(&inputs).clamp(1.0, 60.0)
}
pub fn record(
&mut self,
peer_count: usize,
network_latency_ms: f64,
update_frequency_hz: f64,
optimal_interval_secs: f64,
) {
let inputs = [
peer_count as f64,
network_latency_ms / 1000.0,
update_frequency_hz,
];
self.inner.record(&inputs, &[Some(optimal_interval_secs)]);
}
pub fn train(&mut self) -> bool {
self.inner.train()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexityModel {
inner: EmlModel,
}
impl Default for ComplexityModel {
fn default() -> Self {
Self::new()
}
}
impl ComplexityModel {
pub fn new() -> Self {
let mut inner = EmlModel::new(2, 3, 1);
inner.set_model_name("complexity");
Self { inner }
}
pub fn drain_events(&mut self) -> Vec<EmlEvent> {
self.inner.drain_events()
}
pub fn is_trained(&self) -> bool {
self.inner.is_trained()
}
pub fn training_sample_count(&self) -> usize {
self.inner.training_sample_count()
}
pub fn predict(
&self,
language_type: u32,
avg_file_size: f64,
team_size_proxy: f64,
) -> usize {
if !self.inner.is_trained() {
return 500;
}
let inputs = [
language_type as f64,
avg_file_size / 1000.0, team_size_proxy,
];
let raw = self.inner.predict_primary(&inputs);
(raw.clamp(100.0, 5000.0)).round() as usize
}
pub fn record(
&mut self,
language_type: u32,
avg_file_size: f64,
team_size_proxy: f64,
optimal_threshold: usize,
) {
let inputs = [
language_type as f64,
avg_file_size / 1000.0,
team_size_proxy,
];
self.inner
.record(&inputs, &[Some(optimal_threshold as f64)]);
}
pub fn train(&mut self) -> bool {
self.inner.train()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn governance_scorer_fallback_matches_l2_norm() {
let model = GovernanceScorerModel::new();
assert!(!model.is_trained());
let score = model.predict(0.5, 0.3, 0.2, 0.1, 0.4);
let expected = (0.5_f64.powi(2)
+ 0.3_f64.powi(2)
+ 0.2_f64.powi(2)
+ 0.1_f64.powi(2)
+ 0.4_f64.powi(2))
.sqrt();
assert!((score - expected).abs() < 1e-10);
}
#[test]
fn governance_scorer_zero_vector() {
let model = GovernanceScorerModel::new();
assert_eq!(model.predict(0.0, 0.0, 0.0, 0.0, 0.0), 0.0);
}
#[test]
fn governance_scorer_record_increments_count() {
let mut model = GovernanceScorerModel::new();
assert_eq!(model.training_sample_count(), 0);
model.record(0.5, 0.3, 0.2, 0.1, 0.4, 0.75);
assert_eq!(model.training_sample_count(), 1);
}
#[test]
fn governance_scorer_serde_roundtrip() {
let model = GovernanceScorerModel::new();
let json = serde_json::to_string(&model).unwrap();
let restored: GovernanceScorerModel = serde_json::from_str(&json).unwrap();
assert!(!restored.is_trained());
assert_eq!(restored.predict(1.0, 0.0, 0.0, 0.0, 0.0), 1.0);
}
#[test]
fn restart_strategy_fallback_exponential_backoff() {
let model = RestartStrategyModel::new();
assert!(!model.is_trained());
let (delay, retry) = model.predict(1, 0, 60.0, 0.5);
assert_eq!(delay, 100);
assert!(retry);
let (delay, _) = model.predict(3, 0, 60.0, 0.5);
assert_eq!(delay, 400);
let (delay, _) = model.predict(10, 0, 60.0, 0.5);
assert_eq!(delay, 30_000);
}
#[test]
fn restart_strategy_record_increments_count() {
let mut model = RestartStrategyModel::new();
model.record(1, 0, 60.0, 0.5, 100, true);
assert_eq!(model.training_sample_count(), 1);
}
#[test]
fn restart_strategy_serde_roundtrip() {
let model = RestartStrategyModel::new();
let json = serde_json::to_string(&model).unwrap();
let restored: RestartStrategyModel = serde_json::from_str(&json).unwrap();
assert!(!restored.is_trained());
}
#[test]
fn health_threshold_fallback_defaults() {
let model = HealthThresholdModel::new();
assert!(!model.is_trained());
let (degraded, failed) = model.predict(0, 100, 50.0);
assert_eq!(degraded, 1);
assert_eq!(failed, 3);
}
#[test]
fn health_threshold_record_increments_count() {
let mut model = HealthThresholdModel::new();
model.record(0, 100, 50.0, 2, 5);
assert_eq!(model.training_sample_count(), 1);
}
#[test]
fn health_threshold_serde_roundtrip() {
let model = HealthThresholdModel::new();
let json = serde_json::to_string(&model).unwrap();
let restored: HealthThresholdModel = serde_json::from_str(&json).unwrap();
assert!(!restored.is_trained());
}
#[test]
fn dead_letter_fallback_exponential() {
let model = DeadLetterModel::new();
assert!(!model.is_trained());
let (delay, discard) = model.predict(0, 5000, 100);
assert_eq!(delay, 1000);
assert!(!discard);
let (delay, discard) = model.predict(3, 10000, 100);
assert_eq!(delay, 8000);
assert!(!discard);
let (_, discard) = model.predict(5, 30000, 100);
assert!(discard);
let (delay, _) = model.predict(7, 60000, 100);
assert_eq!(delay, 60_000);
}
#[test]
fn dead_letter_record_increments_count() {
let mut model = DeadLetterModel::new();
model.record(1, 5000, 50, 2000, true);
assert_eq!(model.training_sample_count(), 1);
}
#[test]
fn dead_letter_serde_roundtrip() {
let model = DeadLetterModel::new();
let json = serde_json::to_string(&model).unwrap();
let restored: DeadLetterModel = serde_json::from_str(&json).unwrap();
assert!(!restored.is_trained());
}
#[test]
fn gossip_timing_fallback_5s() {
let model = GossipTimingModel::new();
assert!(!model.is_trained());
let interval = model.predict(10, 50.0, 1.0);
assert!((interval - 5.0).abs() < 1e-10);
}
#[test]
fn gossip_timing_record_increments_count() {
let mut model = GossipTimingModel::new();
model.record(10, 50.0, 1.0, 3.0);
assert_eq!(model.training_sample_count(), 1);
}
#[test]
fn gossip_timing_serde_roundtrip() {
let model = GossipTimingModel::new();
let json = serde_json::to_string(&model).unwrap();
let restored: GossipTimingModel = serde_json::from_str(&json).unwrap();
assert!(!restored.is_trained());
}
#[test]
fn complexity_fallback_500() {
let model = ComplexityModel::new();
assert!(!model.is_trained());
let threshold = model.predict(0, 200.0, 5.0);
assert_eq!(threshold, 500);
}
#[test]
fn complexity_record_increments_count() {
let mut model = ComplexityModel::new();
model.record(0, 200.0, 5.0, 600);
assert_eq!(model.training_sample_count(), 1);
}
#[test]
fn complexity_serde_roundtrip() {
let model = ComplexityModel::new();
let json = serde_json::to_string(&model).unwrap();
let restored: ComplexityModel = serde_json::from_str(&json).unwrap();
assert!(!restored.is_trained());
assert_eq!(restored.predict(0, 200.0, 5.0), 500);
}
#[test]
fn all_models_default_untrained() {
assert!(!GovernanceScorerModel::default().is_trained());
assert!(!RestartStrategyModel::default().is_trained());
assert!(!HealthThresholdModel::default().is_trained());
assert!(!DeadLetterModel::default().is_trained());
assert!(!GossipTimingModel::default().is_trained());
assert!(!ComplexityModel::default().is_trained());
}
#[test]
fn all_models_zero_initial_samples() {
assert_eq!(GovernanceScorerModel::new().training_sample_count(), 0);
assert_eq!(RestartStrategyModel::new().training_sample_count(), 0);
assert_eq!(HealthThresholdModel::new().training_sample_count(), 0);
assert_eq!(DeadLetterModel::new().training_sample_count(), 0);
assert_eq!(GossipTimingModel::new().training_sample_count(), 0);
assert_eq!(ComplexityModel::new().training_sample_count(), 0);
}
}