gpt/src/relationship.rs
2025-06-08 06:41:41 +09:00

307 lines
11 KiB
Rust

use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use anyhow::{Result, Context};
use chrono::{DateTime, Utc};
use crate::config::Config;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Relationship {
pub user_id: String,
pub score: f64,
pub threshold: f64,
pub status: RelationshipStatus,
pub total_interactions: u32,
pub positive_interactions: u32,
pub negative_interactions: u32,
pub transmission_enabled: bool,
pub is_broken: bool,
pub last_interaction: Option<DateTime<Utc>>,
pub last_transmission: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub daily_interaction_count: u32,
pub last_daily_reset: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RelationshipStatus {
New,
Acquaintance,
Friend,
CloseFriend,
Broken,
}
impl std::fmt::Display for RelationshipStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RelationshipStatus::New => write!(f, "new"),
RelationshipStatus::Acquaintance => write!(f, "acquaintance"),
RelationshipStatus::Friend => write!(f, "friend"),
RelationshipStatus::CloseFriend => write!(f, "close_friend"),
RelationshipStatus::Broken => write!(f, "broken"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelationshipTracker {
relationships: HashMap<String, Relationship>,
config: Config,
}
impl RelationshipTracker {
pub fn new(config: &Config) -> Result<Self> {
let relationships = Self::load_relationships(config)?;
Ok(RelationshipTracker {
relationships,
config: config.clone(),
})
}
pub fn get_or_create_relationship(&mut self, user_id: &str) -> &mut Relationship {
let now = Utc::now();
self.relationships.entry(user_id.to_string()).or_insert_with(|| {
Relationship {
user_id: user_id.to_string(),
score: 0.0,
threshold: 10.0, // Default threshold for transmission
status: RelationshipStatus::New,
total_interactions: 0,
positive_interactions: 0,
negative_interactions: 0,
transmission_enabled: false,
is_broken: false,
last_interaction: None,
last_transmission: None,
created_at: now,
daily_interaction_count: 0,
last_daily_reset: now,
}
})
}
pub fn process_interaction(&mut self, user_id: &str, sentiment: f64) -> Result<f64> {
let now = Utc::now();
let previous_score;
let score_change;
// Create relationship if it doesn't exist
{
let relationship = self.get_or_create_relationship(user_id);
// Reset daily count if needed
if (now - relationship.last_daily_reset).num_days() >= 1 {
relationship.daily_interaction_count = 0;
relationship.last_daily_reset = now;
}
// Apply daily interaction limit
if relationship.daily_interaction_count >= 10 {
return Ok(0.0); // No score change due to daily limit
}
previous_score = relationship.score;
// Calculate score change based on sentiment
let mut base_score_change = sentiment * 0.5; // Base change
// Apply diminishing returns for high interaction counts
let interaction_factor = 1.0 / (1.0 + relationship.total_interactions as f64 * 0.01);
base_score_change *= interaction_factor;
score_change = base_score_change;
// Update relationship data
relationship.score += score_change;
relationship.score = relationship.score.max(-50.0).min(100.0); // Clamp score
relationship.total_interactions += 1;
relationship.daily_interaction_count += 1;
relationship.last_interaction = Some(now);
if sentiment > 0.0 {
relationship.positive_interactions += 1;
} else if sentiment < 0.0 {
relationship.negative_interactions += 1;
}
// Check for relationship breaking
if relationship.score <= -20.0 && !relationship.is_broken {
relationship.is_broken = true;
relationship.transmission_enabled = false;
relationship.status = RelationshipStatus::Broken;
}
// Enable transmission if threshold is reached
if relationship.score >= relationship.threshold && !relationship.is_broken {
relationship.transmission_enabled = true;
}
}
// Update status based on score (separate borrow)
self.update_relationship_status(user_id);
self.save_relationships()?;
Ok(score_change)
}
fn update_relationship_status(&mut self, user_id: &str) {
if let Some(relationship) = self.relationships.get_mut(user_id) {
if relationship.is_broken {
return; // Broken relationships cannot change status
}
relationship.status = match relationship.score {
score if score >= 50.0 => RelationshipStatus::CloseFriend,
score if score >= 20.0 => RelationshipStatus::Friend,
score if score >= 5.0 => RelationshipStatus::Acquaintance,
_ => RelationshipStatus::New,
};
}
}
pub fn apply_time_decay(&mut self) -> Result<()> {
let now = Utc::now();
let decay_rate = 0.1; // 10% decay per day
for relationship in self.relationships.values_mut() {
if let Some(last_interaction) = relationship.last_interaction {
let days_since_interaction = (now - last_interaction).num_days() as f64;
if days_since_interaction > 0.0 {
let decay_factor = (1.0_f64 - decay_rate).powf(days_since_interaction);
relationship.score *= decay_factor;
// Update status after decay
if relationship.score < relationship.threshold {
relationship.transmission_enabled = false;
}
}
}
}
// Update statuses for all relationships
let user_ids: Vec<String> = self.relationships.keys().cloned().collect();
for user_id in user_ids {
self.update_relationship_status(&user_id);
}
self.save_relationships()?;
Ok(())
}
pub fn get_relationship(&self, user_id: &str) -> Option<&Relationship> {
self.relationships.get(user_id)
}
pub fn list_all_relationships(&self) -> &HashMap<String, Relationship> {
&self.relationships
}
pub fn get_transmission_eligible(&self) -> HashMap<String, &Relationship> {
self.relationships
.iter()
.filter(|(_, rel)| rel.transmission_enabled && !rel.is_broken)
.map(|(id, rel)| (id.clone(), rel))
.collect()
}
pub fn record_transmission(&mut self, user_id: &str) -> Result<()> {
if let Some(relationship) = self.relationships.get_mut(user_id) {
relationship.last_transmission = Some(Utc::now());
self.save_relationships()?;
}
Ok(())
}
pub fn get_relationship_stats(&self) -> RelationshipStats {
let total_relationships = self.relationships.len();
let active_relationships = self.relationships
.values()
.filter(|r| r.total_interactions > 0)
.count();
let transmission_enabled = self.relationships
.values()
.filter(|r| r.transmission_enabled)
.count();
let broken_relationships = self.relationships
.values()
.filter(|r| r.is_broken)
.count();
let avg_score = if total_relationships > 0 {
self.relationships.values().map(|r| r.score).sum::<f64>() / total_relationships as f64
} else {
0.0
};
RelationshipStats {
total_relationships,
active_relationships,
transmission_enabled,
broken_relationships,
avg_score,
}
}
fn load_relationships(config: &Config) -> Result<HashMap<String, Relationship>> {
let file_path = config.relationships_file();
if !file_path.exists() {
return Ok(HashMap::new());
}
let content = std::fs::read_to_string(file_path)
.context("Failed to read relationships file")?;
let relationships: HashMap<String, Relationship> = serde_json::from_str(&content)
.context("Failed to parse relationships file")?;
Ok(relationships)
}
fn save_relationships(&self) -> Result<()> {
let content = serde_json::to_string_pretty(&self.relationships)
.context("Failed to serialize relationships")?;
std::fs::write(&self.config.relationships_file(), content)
.context("Failed to write relationships file")?;
Ok(())
}
pub fn get_all_relationships(&self) -> Result<HashMap<String, RelationshipCompact>> {
let mut result = HashMap::new();
for (user_id, relationship) in &self.relationships {
result.insert(user_id.clone(), RelationshipCompact {
score: relationship.score,
trust_level: relationship.score / 10.0, // Simplified trust calculation
interaction_count: relationship.total_interactions,
last_interaction: relationship.last_interaction.unwrap_or(relationship.created_at),
status: relationship.status.clone(),
});
}
Ok(result)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct RelationshipStats {
pub total_relationships: usize,
pub active_relationships: usize,
pub transmission_enabled: usize,
pub broken_relationships: usize,
pub avg_score: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct RelationshipCompact {
pub score: f64,
pub trust_level: f64,
pub interaction_count: u32,
pub last_interaction: DateTime<Utc>,
pub status: RelationshipStatus,
}