use crate::{CasialError, PerceptionId};
use ahash::AHashMap;
use anyhow::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct PerceptionConfidence(pub f64);
impl PerceptionConfidence {
pub fn new(confidence: f64) -> Result<Self> {
if !(0.0..=1.0).contains(&confidence) {
return Err(CasialError::PerceptionLock(
"Confidence must be between 0.0 and 1.0".to_string(),
)
.into());
}
Ok(Self(confidence))
}
pub fn value(&self) -> f64 {
self.0
}
pub fn is_high(&self) -> bool {
self.0 >= 0.8
}
pub fn is_low(&self) -> bool {
self.0 <= 0.3
}
pub fn is_uncertain(&self) -> bool {
(0.4..0.6).contains(&self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PerceptionType {
Human,
Artificial,
Hybrid,
Systemic,
External,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerceptionView {
pub id: PerceptionId,
pub name: String,
pub description: String,
pub perception_type: PerceptionType,
pub confidence: PerceptionConfidence,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub tags: Vec<String>,
pub metadata: AHashMap<String, serde_json::Value>,
pub supporting_perceptions: Vec<PerceptionId>,
pub conflicting_perceptions: Vec<PerceptionId>,
pub evidence: Vec<PerceptionEvidence>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerceptionEvidence {
pub id: uuid::Uuid,
pub description: String,
pub source: EvidenceSource,
pub weight: f64, pub timestamp: DateTime<Utc>,
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EvidenceSource {
Observation,
Reasoning,
Historical,
External,
Consensus,
Expert,
}
pub struct PerceptionManager {
perceptions: AHashMap<PerceptionId, PerceptionView>,
perception_relationships: AHashMap<PerceptionId, HashSet<PerceptionId>>,
active_locks: AHashMap<PerceptionId, PerceptionLock>,
}
#[derive(Debug, Clone)]
pub struct PerceptionLock {
pub perception_id: PerceptionId,
pub locked_by: uuid::Uuid, pub locked_at: DateTime<Utc>,
pub expires_at: DateTime<Utc>,
pub lock_type: PerceptionLockType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PerceptionLockType {
Exclusive,
Shared,
Advisory,
}
impl PerceptionManager {
pub fn new() -> Self {
Self {
perceptions: AHashMap::new(),
perception_relationships: AHashMap::new(),
active_locks: AHashMap::new(),
}
}
pub fn register_perception(&mut self, perception: PerceptionView) -> Result<()> {
let perception_id = perception.id;
self.perception_relationships
.insert(perception_id, HashSet::new());
for supporting_id in &perception.supporting_perceptions {
self.add_relationship(
perception_id,
*supporting_id,
PerceptionRelationType::Supports,
)?;
}
for conflicting_id in &perception.conflicting_perceptions {
self.add_relationship(
perception_id,
*conflicting_id,
PerceptionRelationType::Conflicts,
)?;
}
self.perceptions.insert(perception_id, perception);
Ok(())
}
pub fn get_perception(&self, perception_id: PerceptionId) -> Option<&PerceptionView> {
self.perceptions.get(&perception_id)
}
pub fn update_confidence(
&mut self,
perception_id: PerceptionId,
evidence: PerceptionEvidence,
) -> Result<()> {
if let Some(perception) = self.perceptions.get_mut(&perception_id) {
let old_confidence = perception.confidence.value();
let evidence_impact = evidence.weight * 0.1; let new_confidence = match evidence.source {
EvidenceSource::Observation | EvidenceSource::External => {
(old_confidence + evidence_impact).min(1.0)
}
EvidenceSource::Reasoning => {
old_confidence + (evidence_impact * 0.7) }
EvidenceSource::Historical => {
old_confidence + (evidence_impact * 0.5) }
EvidenceSource::Consensus => {
old_confidence + (evidence_impact * 0.8) }
EvidenceSource::Expert => {
old_confidence + (evidence_impact * 0.9) }
};
perception.confidence = PerceptionConfidence::new(new_confidence)?;
perception.evidence.push(evidence);
perception.updated_at = Utc::now();
Ok(())
} else {
Err(
CasialError::PerceptionLock(format!("Perception {} not found", perception_id.0))
.into(),
)
}
}
pub fn acquire_lock(
&mut self,
perception_id: PerceptionId,
session_id: uuid::Uuid,
lock_type: PerceptionLockType,
duration_seconds: u64,
) -> Result<bool> {
if let Some(existing_lock) = self.active_locks.get(&perception_id) {
if existing_lock.expires_at > Utc::now() {
match (&existing_lock.lock_type, &lock_type) {
(PerceptionLockType::Exclusive, _) => return Ok(false),
(PerceptionLockType::Shared, PerceptionLockType::Exclusive) => {
return Ok(false)
}
(PerceptionLockType::Shared, PerceptionLockType::Shared) => {
}
(PerceptionLockType::Advisory, _) | (_, PerceptionLockType::Advisory) => {
}
}
}
}
let lock = PerceptionLock {
perception_id,
locked_by: session_id,
locked_at: Utc::now(),
expires_at: Utc::now() + chrono::Duration::seconds(duration_seconds as i64),
lock_type,
};
self.active_locks.insert(perception_id, lock);
Ok(true)
}
pub fn release_lock(
&mut self,
perception_id: PerceptionId,
session_id: uuid::Uuid,
) -> Result<()> {
if let Some(lock) = self.active_locks.get(&perception_id) {
if lock.locked_by == session_id {
self.active_locks.remove(&perception_id);
Ok(())
} else {
Err(CasialError::PerceptionLock(format!(
"Lock not owned by session {}",
session_id
))
.into())
}
} else {
Err(CasialError::PerceptionLock(format!(
"No lock found for perception {}",
perception_id.0
))
.into())
}
}
pub fn cleanup_expired_locks(&mut self) -> usize {
let now = Utc::now();
let expired_locks: Vec<PerceptionId> = self
.active_locks
.iter()
.filter(|(_, lock)| lock.expires_at <= now)
.map(|(id, _)| *id)
.collect();
let count = expired_locks.len();
for perception_id in expired_locks {
self.active_locks.remove(&perception_id);
}
count
}
pub fn add_relationship(
&mut self,
from_perception: PerceptionId,
to_perception: PerceptionId,
relationship_type: PerceptionRelationType,
) -> Result<()> {
self.perception_relationships
.entry(from_perception)
.or_default()
.insert(to_perception);
match relationship_type {
PerceptionRelationType::Supports => {
self.perception_relationships
.entry(to_perception)
.or_default()
.insert(from_perception);
}
PerceptionRelationType::Conflicts => {
self.perception_relationships
.entry(to_perception)
.or_default()
.insert(from_perception);
}
PerceptionRelationType::DependsOn => {
}
PerceptionRelationType::Enhances => {
}
}
Ok(())
}
pub fn find_conflicts(&self, perception_id: PerceptionId) -> Vec<PerceptionId> {
if let Some(perception) = self.perceptions.get(&perception_id) {
perception.conflicting_perceptions.clone()
} else {
Vec::new()
}
}
pub fn find_supporters(&self, perception_id: PerceptionId) -> Vec<PerceptionId> {
if let Some(perception) = self.perceptions.get(&perception_id) {
perception.supporting_perceptions.clone()
} else {
Vec::new()
}
}
pub fn get_high_confidence_perceptions(&self, threshold: f64) -> Vec<PerceptionId> {
self.perceptions
.iter()
.filter(|(_, perception)| perception.confidence.value() >= threshold)
.map(|(id, _)| *id)
.collect()
}
pub fn get_statistics(&self) -> PerceptionManagerStats {
let total_perceptions = self.perceptions.len();
let active_locks = self.active_locks.len();
let avg_confidence = if total_perceptions > 0 {
self.perceptions
.values()
.map(|p| p.confidence.value())
.sum::<f64>()
/ total_perceptions as f64
} else {
0.0
};
let perception_types: AHashMap<String, usize> =
self.perceptions
.values()
.fold(AHashMap::new(), |mut acc, perception| {
let type_name = format!("{:?}", perception.perception_type);
*acc.entry(type_name).or_insert(0) += 1;
acc
});
PerceptionManagerStats {
total_perceptions,
active_locks,
average_confidence: avg_confidence,
perception_types,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PerceptionRelationType {
Supports,
Conflicts,
DependsOn,
Enhances,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerceptionManagerStats {
pub total_perceptions: usize,
pub active_locks: usize,
pub average_confidence: f64,
pub perception_types: AHashMap<String, usize>,
}
impl Default for PerceptionManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_perception_confidence() {
let confidence = PerceptionConfidence::new(0.8).unwrap();
assert!(confidence.is_high());
assert!(!confidence.is_low());
assert!(!confidence.is_uncertain());
}
#[test]
fn test_perception_manager() {
let mut manager = PerceptionManager::new();
let perception = PerceptionView {
id: PerceptionId::new(),
name: "Test Perception".to_string(),
description: "A test perception".to_string(),
perception_type: PerceptionType::Human,
confidence: PerceptionConfidence::new(0.7).unwrap(),
created_at: Utc::now(),
updated_at: Utc::now(),
tags: vec!["test".to_string()],
metadata: AHashMap::new(),
supporting_perceptions: vec![],
conflicting_perceptions: vec![],
evidence: vec![],
};
let perception_id = perception.id;
manager.register_perception(perception).unwrap();
assert!(manager.get_perception(perception_id).is_some());
}
#[test]
fn test_perception_locking() {
let mut manager = PerceptionManager::new();
let perception_id = PerceptionId::new();
let session_id = uuid::Uuid::new_v4();
let lock_acquired = manager
.acquire_lock(perception_id, session_id, PerceptionLockType::Exclusive, 60)
.unwrap();
assert!(lock_acquired);
manager.release_lock(perception_id, session_id).unwrap();
}
}