use crate::composition::rules::{CompositionContext, CompositionResult, CompositionRule};
use crate::models::capability::{Capability, CapabilityType};
use crate::models::CapabilityExt;
use crate::Result;
use async_trait::async_trait;
use serde_json::{json, Value};
pub struct DetectionReliabilityRule {
min_sensors: usize,
min_confidence: f32,
}
impl DetectionReliabilityRule {
pub fn new(min_sensors: usize, min_confidence: f32) -> Self {
Self {
min_sensors,
min_confidence,
}
}
fn combined_confidence(&self, confidences: &[f32]) -> f32 {
let failure_prob: f32 = confidences.iter().map(|c| 1.0 - c).product();
1.0 - failure_prob
}
}
impl Default for DetectionReliabilityRule {
fn default() -> Self {
Self::new(2, 0.6)
}
}
#[async_trait]
impl CompositionRule for DetectionReliabilityRule {
fn name(&self) -> &str {
"detection_reliability"
}
fn description(&self) -> &str {
"Improves detection confidence through redundant sensor coverage"
}
fn applies_to(&self, capabilities: &[Capability]) -> bool {
let qualified_sensors = capabilities
.iter()
.filter(|c| {
c.get_capability_type() == CapabilityType::Sensor
&& c.confidence >= self.min_confidence
})
.count();
qualified_sensors >= self.min_sensors
}
async fn compose(
&self,
capabilities: &[Capability],
_context: &CompositionContext,
) -> Result<CompositionResult> {
let sensors: Vec<&Capability> = capabilities
.iter()
.filter(|c| {
c.get_capability_type() == CapabilityType::Sensor
&& c.confidence >= self.min_confidence
})
.collect();
if sensors.len() < self.min_sensors {
return Ok(CompositionResult::new(vec![], 0.0));
}
let confidences: Vec<f32> = sensors.iter().map(|s| s.confidence).collect();
let combined_confidence = self.combined_confidence(&confidences);
let total_coverage: f64 = sensors
.iter()
.filter_map(|cap| {
serde_json::from_str::<Value>(&cap.metadata_json)
.ok()
.and_then(|v| v.get("coverage_area").and_then(|c| c.as_f64()))
})
.sum();
let mut composed = Capability::new(
format!("redundant_detection_{}", uuid::Uuid::new_v4()),
"Redundant Detection".to_string(),
CapabilityType::Emergent,
combined_confidence,
);
composed.metadata_json = serde_json::to_string(&json!({
"composition_type": "redundant",
"pattern": "detection_reliability",
"sensor_count": sensors.len(),
"coverage_area": total_coverage,
"individual_confidences": confidences,
"reliability_improvement": combined_confidence - confidences.iter().cloned().fold(0.0, f32::max),
"description": "Improved detection through sensor redundancy"
}))
.unwrap_or_default();
let contributor_ids: Vec<String> = sensors.iter().map(|c| c.id.clone()).collect();
Ok(CompositionResult::new(vec![composed], combined_confidence)
.with_contributors(contributor_ids))
}
}
pub struct ContinuousCoverageRule {
min_platforms: usize,
#[allow(dead_code)]
min_overlap: f32,
}
impl ContinuousCoverageRule {
pub fn new(min_platforms: usize, min_overlap: f32) -> Self {
Self {
min_platforms,
min_overlap: min_overlap.clamp(0.0, 1.0),
}
}
}
impl Default for ContinuousCoverageRule {
fn default() -> Self {
Self::new(2, 0.3) }
}
#[async_trait]
impl CompositionRule for ContinuousCoverageRule {
fn name(&self) -> &str {
"continuous_coverage"
}
fn description(&self) -> &str {
"Ensures continuous area coverage through temporal overlap of multiple platforms"
}
fn applies_to(&self, capabilities: &[Capability]) -> bool {
let qualified_sensors = capabilities
.iter()
.filter(|c| {
c.get_capability_type() == CapabilityType::Sensor
&& serde_json::from_str::<Value>(&c.metadata_json)
.ok()
.and_then(|v| v.get("coverage_area").cloned())
.is_some()
})
.count();
qualified_sensors >= self.min_platforms
}
async fn compose(
&self,
capabilities: &[Capability],
context: &CompositionContext,
) -> Result<CompositionResult> {
let sensors: Vec<&Capability> = capabilities
.iter()
.filter(|c| {
c.get_capability_type() == CapabilityType::Sensor
&& serde_json::from_str::<Value>(&c.metadata_json)
.ok()
.and_then(|v| v.get("coverage_area").cloned())
.is_some()
})
.collect();
if sensors.len() < self.min_platforms {
return Ok(CompositionResult::new(vec![], 0.0));
}
let total_coverage: f64 = sensors
.iter()
.filter_map(|cap| {
serde_json::from_str::<Value>(&cap.metadata_json)
.ok()
.and_then(|v| v.get("coverage_area").and_then(|c| c.as_f64()))
})
.sum();
let overlap_factor = if sensors.len() > 1 {
0.2 + (sensors.len() as f32 - 1.0) * 0.1
} else {
0.0
};
let avg_confidence: f32 =
sensors.iter().map(|s| s.confidence).sum::<f32>() / sensors.len() as f32;
let continuous_confidence = (avg_confidence + overlap_factor * 0.2).min(1.0);
let mut composed = Capability::new(
format!("continuous_coverage_{}", uuid::Uuid::new_v4()),
"Continuous Coverage".to_string(),
CapabilityType::Emergent,
continuous_confidence,
);
composed.metadata_json = serde_json::to_string(&json!({
"composition_type": "redundant",
"pattern": "continuous_coverage",
"platform_count": sensors.len(),
"total_coverage_area": total_coverage,
"estimated_overlap": overlap_factor,
"coverage_redundancy": (sensors.len() as f32 * overlap_factor),
"cell_id": context.cell_id,
"description": "Continuous monitoring through overlapping coverage"
}))
.unwrap_or_default();
let contributor_ids: Vec<String> = sensors.iter().map(|c| c.id.clone()).collect();
Ok(
CompositionResult::new(vec![composed], continuous_confidence)
.with_contributors(contributor_ids),
)
}
}
pub struct FaultToleranceRule {
min_redundancy: usize,
capability_type: CapabilityType,
}
impl FaultToleranceRule {
pub fn new(min_redundancy: usize, capability_type: CapabilityType) -> Self {
Self {
min_redundancy,
capability_type,
}
}
fn system_reliability(&self, component_confidences: &[f32]) -> f32 {
let failure_prob: f32 = component_confidences.iter().map(|c| 1.0 - c).product();
1.0 - failure_prob
}
}
impl Default for FaultToleranceRule {
fn default() -> Self {
Self::new(3, CapabilityType::Communication) }
}
#[async_trait]
impl CompositionRule for FaultToleranceRule {
fn name(&self) -> &str {
"fault_tolerance"
}
fn description(&self) -> &str {
"Provides fault-tolerant capability through redundant systems"
}
fn applies_to(&self, capabilities: &[Capability]) -> bool {
let redundant_count = capabilities
.iter()
.filter(|c| c.get_capability_type() == self.capability_type)
.count();
redundant_count >= self.min_redundancy
}
async fn compose(
&self,
capabilities: &[Capability],
_context: &CompositionContext,
) -> Result<CompositionResult> {
let redundant_caps: Vec<&Capability> = capabilities
.iter()
.filter(|c| c.get_capability_type() == self.capability_type)
.collect();
if redundant_caps.len() < self.min_redundancy {
return Ok(CompositionResult::new(vec![], 0.0));
}
let confidences: Vec<f32> = redundant_caps.iter().map(|c| c.confidence).collect();
let system_reliability = self.system_reliability(&confidences);
let mut composed = Capability::new(
format!(
"fault_tolerant_{:?}_{}",
self.capability_type,
uuid::Uuid::new_v4()
),
format!("Fault-Tolerant {:?}", self.capability_type),
CapabilityType::Emergent,
system_reliability,
);
composed.metadata_json = serde_json::to_string(&json!({
"composition_type": "redundant",
"pattern": "fault_tolerance",
"base_capability_type": format!("{:?}", self.capability_type),
"redundancy_level": redundant_caps.len(),
"system_reliability": system_reliability,
"individual_reliabilities": confidences,
"description": format!("Fault-tolerant {:?} with {}-way redundancy",
self.capability_type, redundant_caps.len())
}))
.unwrap_or_default();
let contributor_ids: Vec<String> = redundant_caps.iter().map(|c| c.id.clone()).collect();
Ok(CompositionResult::new(vec![composed], system_reliability)
.with_contributors(contributor_ids))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[tokio::test]
async fn test_detection_reliability_two_sensors() {
let rule = DetectionReliabilityRule::default();
let mut sensor1 = Capability::new(
"sensor1".to_string(),
"Camera 1".to_string(),
CapabilityType::Sensor,
0.7,
);
sensor1.metadata_json =
serde_json::to_string(&json!({"coverage_area": 100.0})).unwrap_or_default();
let mut sensor2 = Capability::new(
"sensor2".to_string(),
"Camera 2".to_string(),
CapabilityType::Sensor,
0.7,
);
sensor2.metadata_json =
serde_json::to_string(&json!({"coverage_area": 100.0})).unwrap_or_default();
let caps = vec![sensor1, sensor2];
let context = CompositionContext::new(vec!["node1".to_string()]);
let result = rule.compose(&caps, &context).await.unwrap();
assert!(result.has_compositions());
let composed = &result.composed_capabilities[0];
assert!((composed.confidence - 0.91).abs() < 0.01);
let metadata: Value = serde_json::from_str(&composed.metadata_json).unwrap();
assert_eq!(metadata["sensor_count"].as_u64().unwrap(), 2);
}
#[tokio::test]
async fn test_detection_reliability_three_sensors() {
let rule = DetectionReliabilityRule::new(3, 0.6);
let sensors: Vec<Capability> = (0..3)
.map(|i| {
Capability::new(
format!("sensor{}", i),
format!("Sensor {}", i),
CapabilityType::Sensor,
0.7,
)
})
.collect();
let context = CompositionContext::new(vec!["node1".to_string()]);
let result = rule.compose(&sensors, &context).await.unwrap();
assert!(result.has_compositions());
let composed = &result.composed_capabilities[0];
assert!((composed.confidence - 0.973).abs() < 0.01);
}
#[tokio::test]
async fn test_detection_reliability_insufficient_sensors() {
let rule = DetectionReliabilityRule::default();
let sensor1 = Capability::new(
"sensor1".to_string(),
"Single Sensor".to_string(),
CapabilityType::Sensor,
0.8,
);
let caps = vec![sensor1];
let context = CompositionContext::new(vec!["node1".to_string()]);
let result = rule.compose(&caps, &context).await.unwrap();
assert!(!result.has_compositions());
}
#[tokio::test]
async fn test_continuous_coverage() {
let rule = ContinuousCoverageRule::default();
let mut sensor1 = Capability::new(
"sensor1".to_string(),
"Platform 1".to_string(),
CapabilityType::Sensor,
0.85,
);
sensor1.metadata_json =
serde_json::to_string(&json!({"coverage_area": 200.0})).unwrap_or_default();
let mut sensor2 = Capability::new(
"sensor2".to_string(),
"Platform 2".to_string(),
CapabilityType::Sensor,
0.8,
);
sensor2.metadata_json =
serde_json::to_string(&json!({"coverage_area": 200.0})).unwrap_or_default();
let caps = vec![sensor1, sensor2];
let context = CompositionContext::new(vec!["node1".to_string(), "node2".to_string()])
.with_cell_id("cell_alpha".to_string());
assert!(rule.applies_to(&caps));
let result = rule.compose(&caps, &context).await.unwrap();
assert!(result.has_compositions());
let composed = &result.composed_capabilities[0];
assert_eq!(composed.name, "Continuous Coverage");
let metadata: Value = serde_json::from_str(&composed.metadata_json).unwrap();
assert_eq!(metadata["total_coverage_area"].as_f64().unwrap(), 400.0);
assert!(composed.confidence > 0.8); assert_eq!(result.contributing_capabilities.len(), 2);
}
#[tokio::test]
async fn test_fault_tolerance_communication() {
let rule = FaultToleranceRule::default();
let comms: Vec<Capability> = (0..3)
.map(|i| {
let mut cap = Capability::new(
format!("comm{}", i),
format!("Radio {}", i),
CapabilityType::Communication,
0.8,
);
cap.metadata_json =
serde_json::to_string(&json!({"bandwidth": 10.0})).unwrap_or_default();
cap
})
.collect();
let context = CompositionContext::new(vec!["node1".to_string()]);
assert!(rule.applies_to(&comms));
let result = rule.compose(&comms, &context).await.unwrap();
assert!(result.has_compositions());
let composed = &result.composed_capabilities[0];
assert!((composed.confidence - 0.992).abs() < 0.01);
let metadata: Value = serde_json::from_str(&composed.metadata_json).unwrap();
assert_eq!(metadata["redundancy_level"].as_u64().unwrap(), 3);
}
#[tokio::test]
async fn test_fault_tolerance_insufficient_redundancy() {
let rule = FaultToleranceRule::new(4, CapabilityType::Compute);
let compute_caps: Vec<Capability> = (0..3)
.map(|i| {
Capability::new(
format!("compute{}", i),
format!("Compute {}", i),
CapabilityType::Compute,
0.9,
)
})
.collect();
let context = CompositionContext::new(vec!["node1".to_string()]);
assert!(!rule.applies_to(&compute_caps));
let result = rule.compose(&compute_caps, &context).await.unwrap();
assert!(!result.has_compositions());
}
#[tokio::test]
async fn test_redundancy_improves_low_confidence_sensors() {
let rule = DetectionReliabilityRule::new(2, 0.5);
let sensor1 = Capability::new(
"sensor1".to_string(),
"Weak Sensor 1".to_string(),
CapabilityType::Sensor,
0.6,
);
let sensor2 = Capability::new(
"sensor2".to_string(),
"Weak Sensor 2".to_string(),
CapabilityType::Sensor,
0.6,
);
let caps = vec![sensor1, sensor2];
let context = CompositionContext::new(vec!["node1".to_string()]);
let result = rule.compose(&caps, &context).await.unwrap();
assert!(result.has_compositions());
let composed = &result.composed_capabilities[0];
assert!((composed.confidence - 0.84).abs() < 0.01);
assert!(composed.confidence > 0.6); }
#[tokio::test]
async fn test_combined_confidence_calculation() {
let rule = DetectionReliabilityRule::default();
let confidences = vec![0.7, 0.8, 0.9];
let combined = rule.combined_confidence(&confidences);
assert!((combined - 0.994).abs() < 0.01);
}
}