test scheduler
This commit is contained in:
parent
f09f3c9144
commit
7aa633d3a6
@ -11,3 +11,4 @@ seahorse = "*"
|
|||||||
rusqlite = { version = "0.29", features = ["serde_json"] }
|
rusqlite = { version = "0.29", features = ["serde_json"] }
|
||||||
shellexpand = "*"
|
shellexpand = "*"
|
||||||
fs_extra = "1.3"
|
fs_extra = "1.3"
|
||||||
|
rand = "0.9.1"
|
||||||
|
12
example.json
12
example.json
@ -26,5 +26,17 @@
|
|||||||
"おはよう!今日もがんばろう!",
|
"おはよう!今日もがんばろう!",
|
||||||
"ねえ、話したいことがあるの。"
|
"ねえ、話したいことがあるの。"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"last_interaction": "2025-05-21T23:15:00Z",
|
||||||
|
"memory": {
|
||||||
|
"recent_messages": [],
|
||||||
|
"long_term_notes": []
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"trust": 0.5,
|
||||||
|
"intimacy": 0.5,
|
||||||
|
"energy": 0.5,
|
||||||
|
"can_send": true,
|
||||||
|
"last_updated": "2025-05-21T15:52:06.590981Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
29
src/chat.rs
29
src/chat.rs
@ -4,7 +4,7 @@ use std::process::Command;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use seahorse::Context;
|
use seahorse::Context;
|
||||||
use crate::config::ConfigPaths;
|
use crate::config::ConfigPaths;
|
||||||
use crate::metrics::{load_metrics, save_metrics, update_metrics_decay};
|
use crate::metrics::{load_user_data, save_user_data, update_metrics_decay};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Provider {
|
pub enum Provider {
|
||||||
@ -46,33 +46,28 @@ pub fn ask_chat(c: &Context, question: &str) -> Option<String> {
|
|||||||
let config = ConfigPaths::new();
|
let config = ConfigPaths::new();
|
||||||
let base_dir = config.base_dir.join("mcp");
|
let base_dir = config.base_dir.join("mcp");
|
||||||
let script_path = base_dir.join("scripts/ask.py");
|
let script_path = base_dir.join("scripts/ask.py");
|
||||||
let metrics_path = config.base_dir.join("metrics.json");
|
let user_path = config.base_dir.join("user.json");
|
||||||
let mut metrics = load_metrics(&metrics_path);
|
|
||||||
|
|
||||||
update_metrics_decay(&mut metrics);
|
let mut user = load_user_data(&user_path);
|
||||||
|
user.metrics = update_metrics_decay();
|
||||||
if !metrics.can_send {
|
|
||||||
println!("❌ 送信条件を満たしていないため、AIメッセージは送信されません。");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Python 実行パス
|
||||||
let python_path = if cfg!(target_os = "windows") {
|
let python_path = if cfg!(target_os = "windows") {
|
||||||
base_dir.join(".venv/Scripts/python.exe")
|
base_dir.join(".venv/Scripts/python.exe")
|
||||||
} else {
|
} else {
|
||||||
base_dir.join(".venv/bin/python")
|
base_dir.join(".venv/bin/python")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 各種オプション
|
||||||
let ollama_host = c.string_flag("host").ok();
|
let ollama_host = c.string_flag("host").ok();
|
||||||
let ollama_model = c.string_flag("model").ok();
|
let ollama_model = c.string_flag("model").ok();
|
||||||
let provider_str = c.string_flag("provider").unwrap_or_else(|_| "ollama".to_string());
|
let provider_str = c.string_flag("provider").unwrap_or_else(|_| "ollama".to_string());
|
||||||
let provider = Provider::from_str(&provider_str).unwrap_or(Provider::Ollama);
|
let provider = Provider::from_str(&provider_str).unwrap_or(Provider::Ollama);
|
||||||
//let api_key = c.string_flag("api-key").ok().or_else(|| crate::metrics::load_openai_api_key());
|
let api_key = c.string_flag("api-key").ok().or_else(load_openai_api_key);
|
||||||
let api_key = c.string_flag("api-key")
|
|
||||||
.ok()
|
|
||||||
.or_else(|| load_openai_api_key());
|
|
||||||
|
|
||||||
println!("🔍 使用プロバイダー: {}", provider.as_str());
|
println!("🔍 使用プロバイダー: {}", provider.as_str());
|
||||||
|
|
||||||
|
// Python コマンド準備
|
||||||
let mut command = Command::new(python_path);
|
let mut command = Command::new(python_path);
|
||||||
command.arg(script_path).arg(question);
|
command.arg(script_path).arg(question);
|
||||||
|
|
||||||
@ -93,12 +88,10 @@ pub fn ask_chat(c: &Context, question: &str) -> Option<String> {
|
|||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
let response = String::from_utf8_lossy(&output.stdout).to_string();
|
let response = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
println!("💬 {}", response);
|
user.metrics.intimacy += 0.01;
|
||||||
|
user.metrics.last_updated = chrono::Utc::now();
|
||||||
|
save_user_data(&user_path, &user);
|
||||||
|
|
||||||
// 応答後のメトリクス更新
|
|
||||||
metrics.intimacy += 0.02;
|
|
||||||
metrics.last_updated = chrono::Utc::now();
|
|
||||||
save_metrics(&metrics, &metrics_path);
|
|
||||||
Some(response)
|
Some(response)
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
@ -9,6 +9,7 @@ use crate::chat::ask_chat;
|
|||||||
use crate::git::{git_init, git_status};
|
use crate::git::{git_init, git_status};
|
||||||
use crate::config::ConfigPaths;
|
use crate::config::ConfigPaths;
|
||||||
use crate::commands::git_repo::read_all_git_files;
|
use crate::commands::git_repo::read_all_git_files;
|
||||||
|
use crate::metrics::{load_user_data, save_user_data};
|
||||||
|
|
||||||
pub fn mcp_setup() {
|
pub fn mcp_setup() {
|
||||||
let config = ConfigPaths::new();
|
let config = ConfigPaths::new();
|
||||||
@ -160,7 +161,8 @@ fn chat_cmd() -> Command {
|
|||||||
)
|
)
|
||||||
.action(|c: &Context| {
|
.action(|c: &Context| {
|
||||||
let config = ConfigPaths::new();
|
let config = ConfigPaths::new();
|
||||||
|
let user_path = config.data_file("json");
|
||||||
|
let mut user = load_user_data(&user_path);
|
||||||
// repoがある場合は、コードベース読み込みモード
|
// repoがある場合は、コードベース読み込みモード
|
||||||
if let Ok(repo_url) = c.string_flag("repo") {
|
if let Ok(repo_url) = c.string_flag("repo") {
|
||||||
let repo_base = config.base_dir.join("repos");
|
let repo_base = config.base_dir.join("repos");
|
||||||
@ -188,14 +190,24 @@ fn chat_cmd() -> Command {
|
|||||||
} else {
|
} else {
|
||||||
eprintln!("❗ 提案が取得できませんでした");
|
eprintln!("❗ 提案が取得できませんでした");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通常のチャット処理(repoが指定されていない場合)
|
// 通常のチャット処理(repoが指定されていない場合)
|
||||||
match c.args.get(0) {
|
match c.args.get(0) {
|
||||||
Some(question) => {
|
Some(question) => {
|
||||||
if let Some(response) = ask_chat(c, question) {
|
let response = ask_chat(c, question);
|
||||||
println!("💬 応答:\n{}", response);
|
|
||||||
|
if let Some(ref text) = response {
|
||||||
|
println!("💬 応答:\n{}", text);
|
||||||
|
// 返答内容に基づいて増減(返答の感情解析)
|
||||||
|
if text.contains("thank") || text.contains("great") {
|
||||||
|
user.metrics.trust += 0.05;
|
||||||
|
} else if text.contains("hate") || text.contains("bad") {
|
||||||
|
user.metrics.trust -= 0.05;
|
||||||
|
}
|
||||||
|
save_user_data(&user_path, &user);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("❗ 応答が取得できませんでした");
|
eprintln!("❗ 応答が取得できませんでした");
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,105 @@
|
|||||||
// src/commands/scheduler.rs
|
// src/commands/scheduler.rs
|
||||||
|
|
||||||
use seahorse::{Command, Context};
|
use seahorse::{Command, Context};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use chrono::Local;
|
use chrono::{Local, Utc, Timelike};
|
||||||
|
use crate::metrics::{load_user_data, save_user_data};
|
||||||
|
use crate::config::ConfigPaths;
|
||||||
|
use crate::chat::ask_chat;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use rand::rng;
|
||||||
|
|
||||||
|
fn send_scheduled_message() {
|
||||||
|
let config = ConfigPaths::new();
|
||||||
|
let user_path = config.data_file("json");
|
||||||
|
let mut user = load_user_data(&user_path);
|
||||||
|
|
||||||
|
if !user.metrics.can_send {
|
||||||
|
println!("🚫 送信条件を満たしていないため、スケジュール送信スキップ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(schedule_str) = &user.messaging.schedule_time {
|
||||||
|
let now = Local::now();
|
||||||
|
let target: Vec<&str> = schedule_str.split(':').collect();
|
||||||
|
|
||||||
|
if target.len() != 2 {
|
||||||
|
println!("⚠️ schedule_time形式が無効です: {}", schedule_str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (sh, sm) = (target[0].parse::<u32>(), target[1].parse::<u32>());
|
||||||
|
if let (Ok(sh), Ok(sm)) = (sh, sm) {
|
||||||
|
if now.hour() == sh && now.minute() == sm {
|
||||||
|
if let Some(msg) = user.messaging.templates.choose(&mut rng()) {
|
||||||
|
println!("💬 自動送信メッセージ: {}", msg);
|
||||||
|
let dummy_context = Context::new(vec![], None, "".to_string());
|
||||||
|
ask_chat(&dummy_context, msg);
|
||||||
|
user.metrics.intimacy += 0.03;
|
||||||
|
save_user_data(&user_path, &user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn scheduler_cmd() -> Command {
|
pub fn scheduler_cmd() -> Command {
|
||||||
Command::new("scheduler")
|
Command::new("scheduler")
|
||||||
.usage("scheduler [interval_sec]")
|
.usage("scheduler [interval_sec]")
|
||||||
.alias("s")
|
.alias("s")
|
||||||
|
.description("定期的に送信条件をチェックし、自発的なメッセージ送信を試みる")
|
||||||
.action(|c: &Context| {
|
.action(|c: &Context| {
|
||||||
let interval = c.args.get(0)
|
let interval = c.args.get(0)
|
||||||
.and_then(|s| s.parse::<u64>().ok())
|
.and_then(|s| s.parse::<u64>().ok())
|
||||||
.unwrap_or(60); // デフォルト: 60秒ごと
|
.unwrap_or(3600); // デフォルト: 1時間(テストしやすく)
|
||||||
|
|
||||||
println!("⏳ スケジューラー開始({interval}秒ごと)...");
|
println!("⏳ スケジューラー開始({}秒ごと)...", interval);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let now = Local::now();
|
let config = ConfigPaths::new();
|
||||||
println!("🔁 タスク実行中: {}", now.format("%Y-%m-%d %H:%M:%S"));
|
let user_path = config.data_file("json");
|
||||||
|
let mut user = load_user_data(&user_path);
|
||||||
|
|
||||||
// ここで talk_cmd や save_cmd の内部処理を呼ぶ感じ
|
let now = Utc::now();
|
||||||
// たとえば load_config → AI更新 → print とか
|
let elapsed = now.signed_duration_since(user.metrics.last_updated);
|
||||||
|
let hours = elapsed.num_minutes() as f32 / 60.0;
|
||||||
|
|
||||||
|
let speed_factor = if hours > 48.0 {
|
||||||
|
2.0
|
||||||
|
} else if hours > 24.0 {
|
||||||
|
1.5
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
user.metrics.trust = (user.metrics.trust - 0.01 * speed_factor).clamp(0.0, 1.0);
|
||||||
|
user.metrics.intimacy = (user.metrics.intimacy - 0.01 * speed_factor).clamp(0.0, 1.0);
|
||||||
|
user.metrics.energy = (user.metrics.energy - 0.01 * speed_factor).clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
user.metrics.can_send =
|
||||||
|
user.metrics.trust >= 0.5 &&
|
||||||
|
user.metrics.intimacy >= 0.5 &&
|
||||||
|
user.metrics.energy >= 0.5;
|
||||||
|
|
||||||
|
user.metrics.last_updated = now;
|
||||||
|
|
||||||
|
if user.metrics.can_send {
|
||||||
|
println!("💡 AIメッセージ送信条件を満たしています(信頼:{:.2}, 親密:{:.2}, エネルギー:{:.2})",
|
||||||
|
user.metrics.trust,
|
||||||
|
user.metrics.intimacy,
|
||||||
|
user.metrics.energy
|
||||||
|
);
|
||||||
|
send_scheduled_message();
|
||||||
|
} else {
|
||||||
|
println!("🤫 条件未達成のため送信スキップ: trust={:.2}, intimacy={:.2}, energy={:.2}",
|
||||||
|
user.metrics.trust,
|
||||||
|
user.metrics.intimacy,
|
||||||
|
user.metrics.energy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
save_user_data(&user_path, &user);
|
||||||
thread::sleep(Duration::from_secs(interval));
|
thread::sleep(Duration::from_secs(interval));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
181
src/metrics.rs
181
src/metrics.rs
@ -1,10 +1,12 @@
|
|||||||
// src/metrics.rs
|
// src/metrics.rs
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
use crate::config::ConfigPaths;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Metrics {
|
pub struct Metrics {
|
||||||
pub trust: f32,
|
pub trust: f32,
|
||||||
pub intimacy: f32,
|
pub intimacy: f32,
|
||||||
@ -13,86 +15,129 @@ pub struct Metrics {
|
|||||||
pub last_updated: DateTime<Utc>,
|
pub last_updated: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Personality {
|
||||||
|
pub kind: String,
|
||||||
|
pub strength: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Relationship {
|
||||||
|
pub trust: f32,
|
||||||
|
pub intimacy: f32,
|
||||||
|
pub curiosity: f32,
|
||||||
|
pub threshold: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Environment {
|
||||||
|
pub luck_today: f32,
|
||||||
|
pub luck_history: Vec<f32>,
|
||||||
|
pub level: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Messaging {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub schedule_time: Option<String>,
|
||||||
|
pub decay_rate: f32,
|
||||||
|
pub templates: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Memory {
|
||||||
|
pub recent_messages: Vec<String>,
|
||||||
|
pub long_term_notes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct UserData {
|
||||||
|
pub personality: Personality,
|
||||||
|
pub relationship: Relationship,
|
||||||
|
pub environment: Environment,
|
||||||
|
pub messaging: Messaging,
|
||||||
|
pub last_interaction: DateTime<Utc>,
|
||||||
|
pub memory: Memory,
|
||||||
|
pub metrics: Metrics,
|
||||||
|
}
|
||||||
|
|
||||||
impl Metrics {
|
impl Metrics {
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
trust: 0.5,
|
|
||||||
intimacy: 0.5,
|
|
||||||
energy: 0.5,
|
|
||||||
last_updated: chrono::Utc::now(),
|
|
||||||
can_send: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// パラメータの減衰処理を行い、can_sendを更新する
|
|
||||||
pub fn decay(&mut self) {
|
pub fn decay(&mut self) {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let elapsed = now.signed_duration_since(self.last_updated);
|
let hours = (now - self.last_updated).num_minutes() as f32 / 60.0;
|
||||||
let hours = elapsed.num_minutes() as f32 / 60.0;
|
|
||||||
|
|
||||||
self.trust = decay_param(self.trust, hours);
|
self.trust = decay_param(self.trust, hours);
|
||||||
self.intimacy = decay_param(self.intimacy, hours);
|
self.intimacy = decay_param(self.intimacy, hours);
|
||||||
self.energy = decay_param(self.energy, hours);
|
self.energy = decay_param(self.energy, hours);
|
||||||
|
|
||||||
self.last_updated = now;
|
|
||||||
self.can_send = self.trust >= 0.5 && self.intimacy >= 0.5 && self.energy >= 0.5;
|
self.can_send = self.trust >= 0.5 && self.intimacy >= 0.5 && self.energy >= 0.5;
|
||||||
|
self.last_updated = now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JSONからMetricsを読み込み、減衰し、保存して返す
|
pub fn load_user_data(path: &Path) -> UserData {
|
||||||
pub fn load_and_decay(path: &Path) -> Self {
|
let config = ConfigPaths::new();
|
||||||
let mut metrics = if path.exists() {
|
let example_path = Path::new("example.json");
|
||||||
let content = fs::read_to_string(path).expect("metrics.jsonの読み込みに失敗しました");
|
config.ensure_file_exists("json", example_path);
|
||||||
serde_json::from_str(&content).expect("JSONパース失敗")
|
|
||||||
} else {
|
if !path.exists() {
|
||||||
println!("⚠️ metrics.json が存在しないため、新しく作成します。");
|
return UserData {
|
||||||
Metrics::default()
|
personality: Personality {
|
||||||
|
kind: "positive".into(),
|
||||||
|
strength: 0.8,
|
||||||
|
},
|
||||||
|
relationship: Relationship {
|
||||||
|
trust: 0.2,
|
||||||
|
intimacy: 0.6,
|
||||||
|
curiosity: 0.5,
|
||||||
|
threshold: 1.5,
|
||||||
|
},
|
||||||
|
environment: Environment {
|
||||||
|
luck_today: 0.9,
|
||||||
|
luck_history: vec![0.9, 0.9, 0.9],
|
||||||
|
level: 1,
|
||||||
|
},
|
||||||
|
messaging: Messaging {
|
||||||
|
enabled: true,
|
||||||
|
schedule_time: Some("08:00".to_string()),
|
||||||
|
decay_rate: 0.1,
|
||||||
|
templates: vec![
|
||||||
|
"おはよう!今日もがんばろう!".to_string(),
|
||||||
|
"ねえ、話したいことがあるの。".to_string(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
last_interaction: Utc::now(),
|
||||||
|
memory: Memory {
|
||||||
|
recent_messages: vec![],
|
||||||
|
long_term_notes: vec![],
|
||||||
|
},
|
||||||
|
metrics: Metrics {
|
||||||
|
trust: 0.5,
|
||||||
|
intimacy: 0.5,
|
||||||
|
energy: 0.5,
|
||||||
|
can_send: true,
|
||||||
|
last_updated: Utc::now(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
metrics.decay();
|
|
||||||
metrics.save(path);
|
|
||||||
metrics
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Metricsを保存する
|
let content = fs::read_to_string(path).expect("user.json の読み込みに失敗しました");
|
||||||
pub fn save(&self, path: &Path) {
|
serde_json::from_str(&content).expect("user.json のパースに失敗しました")
|
||||||
let data = serde_json::to_string_pretty(self).expect("JSON変換失敗");
|
}
|
||||||
fs::write(path, data).expect("metrics.jsonの書き込みに失敗しました");
|
|
||||||
}
|
pub fn save_user_data(path: &Path, data: &UserData) {
|
||||||
|
let content = serde_json::to_string_pretty(data).expect("user.json のシリアライズ失敗");
|
||||||
|
fs::write(path, content).expect("user.json の書き込みに失敗しました");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_metrics_decay() -> Metrics {
|
||||||
|
let config = ConfigPaths::new();
|
||||||
|
let path = config.base_dir.join("user.json");
|
||||||
|
let mut data = load_user_data(&path);
|
||||||
|
data.metrics.decay();
|
||||||
|
save_user_data(&path, &data);
|
||||||
|
data.metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 単一のパラメータを減衰させる
|
|
||||||
fn decay_param(value: f32, hours: f32) -> f32 {
|
fn decay_param(value: f32, hours: f32) -> f32 {
|
||||||
let decay_rate = 0.01; // 時間ごとの減衰率
|
let decay_rate = 0.05;
|
||||||
(value * (1.0f32 - decay_rate).powf(hours)).clamp(0.0, 1.0)
|
(value * (1.0f32 - decay_rate).powf(hours)).clamp(0.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_decay_behavior() {
|
|
||||||
let mut metrics = Metrics {
|
|
||||||
trust: 1.0,
|
|
||||||
intimacy: 1.0,
|
|
||||||
energy: 1.0,
|
|
||||||
can_send: true,
|
|
||||||
last_updated: Utc::now() - Duration::hours(12),
|
|
||||||
};
|
|
||||||
metrics.decay();
|
|
||||||
assert!(metrics.trust < 1.0);
|
|
||||||
assert!(metrics.can_send); // 減衰後でも0.5以上あるならtrue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_metrics(path: &Path) -> Metrics {
|
|
||||||
Metrics::load_and_decay(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_metrics(metrics: &Metrics, path: &Path) {
|
|
||||||
metrics.save(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_metrics_decay(metrics: &mut Metrics) {
|
|
||||||
metrics.decay()
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user