use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::Scope;
use crate::pattern::{
Applies, Content, Evidence, Lifecycle, Links, Tags, Tier, default_confidence,
default_importance, default_schema,
};
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Maturity {
#[default]
Draft,
Emerging,
Stable,
Canonical,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DecayMeta {
pub last_active: Option<DateTime<Utc>>,
pub half_life_override: Option<u32>,
}
fn is_zero_u32(v: &u32) -> bool {
*v == 0
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeBase {
#[serde(default = "default_schema")]
pub schema: u32,
pub name: String,
pub description: String,
pub content: Content,
#[serde(default)]
pub tier: Tier,
#[serde(default = "default_importance")]
pub importance: f64,
#[serde(default = "default_confidence")]
pub confidence: f64,
#[serde(default)]
pub tags: Tags,
#[serde(default)]
pub applies: Applies,
#[serde(default)]
pub evidence: Evidence,
#[serde(default)]
pub links: Links,
#[serde(default)]
pub lifecycle: Lifecycle,
#[serde(default = "Utc::now")]
pub created_at: DateTime<Utc>,
#[serde(default = "Utc::now")]
pub updated_at: DateTime<Utc>,
#[serde(default)]
pub maturity: Maturity,
#[serde(default)]
pub decay: DecayMeta,
#[serde(default)]
pub scope: Scope,
#[serde(default, skip_serializing_if = "is_zero_u32")]
pub version: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub revision: Option<String>,
}
impl Default for KnowledgeBase {
fn default() -> Self {
Self {
schema: default_schema(),
name: String::new(),
description: String::new(),
content: Content::default(),
tier: Tier::default(),
importance: default_importance(),
confidence: default_confidence(),
tags: Tags::default(),
applies: Applies::default(),
evidence: Evidence::default(),
links: Links::default(),
lifecycle: Lifecycle::default(),
created_at: Utc::now(),
updated_at: Utc::now(),
maturity: Maturity::default(),
decay: DecayMeta::default(),
scope: Scope::default(),
version: 0,
revision: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pattern::Content;
#[test]
fn test_knowledgebase_serde_roundtrip() {
let kb = KnowledgeBase {
name: "test-kb".into(),
description: "A test knowledge base".into(),
content: Content::DualLayer {
technical: "Use X for Y".into(),
principle: Some("Because Z".into()),
},
tier: Tier::Project,
importance: 0.8,
confidence: 0.9,
maturity: Maturity::Stable,
..Default::default()
};
let yaml = serde_yaml::to_string(&kb).expect("serialize");
let kb2: KnowledgeBase = serde_yaml::from_str(&yaml).expect("deserialize");
assert_eq!(kb2.name, "test-kb");
assert_eq!(kb2.description, "A test knowledge base");
assert_eq!(kb2.tier, Tier::Project);
assert!((kb2.importance - 0.8).abs() < 0.001);
assert!((kb2.confidence - 0.9).abs() < 0.001);
assert_eq!(kb2.maturity, Maturity::Stable);
assert_eq!(kb2.schema, 3);
}
#[test]
fn test_knowledgebase_default() {
let kb = KnowledgeBase::default();
assert_eq!(kb.schema, 3);
assert!((kb.confidence - 0.5).abs() < 0.001);
assert_eq!(kb.maturity, Maturity::Draft);
}
#[test]
fn test_knowledgebase_minimal_yaml() {
let yaml = "name: minimal\ndescription: Minimal test\ncontent: Just text\n";
let kb: KnowledgeBase = serde_yaml::from_str(yaml).expect("deserialize minimal");
assert_eq!(kb.name, "minimal");
assert_eq!(kb.schema, 3); assert_eq!(kb.tier, Tier::Session);
}
}