1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9use crate::Scope;
10use crate::pattern::{
11 Applies, Content, Evidence, Lifecycle, Links, Tags, Tier, default_confidence,
12 default_importance, default_schema,
13};
14
15#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
17#[serde(rename_all = "lowercase")]
18pub enum Maturity {
19 #[default]
20 Draft,
21 Emerging,
22 Stable,
23 Canonical,
24}
25
26#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28pub struct DecayMeta {
29 pub last_active: Option<DateTime<Utc>>,
31 pub half_life_override: Option<u32>,
33}
34
35fn is_zero_u32(v: &u32) -> bool {
36 *v == 0
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct KnowledgeBase {
44 #[serde(default = "default_schema")]
45 pub schema: u32,
46
47 pub name: String,
49
50 pub description: String,
52
53 pub content: Content,
55
56 #[serde(default)]
58 pub tier: Tier,
59
60 #[serde(default = "default_importance")]
62 pub importance: f64,
63
64 #[serde(default = "default_confidence")]
66 pub confidence: f64,
67
68 #[serde(default)]
70 pub tags: Tags,
71
72 #[serde(default)]
74 pub applies: Applies,
75
76 #[serde(default)]
78 pub evidence: Evidence,
79
80 #[serde(default)]
82 pub links: Links,
83
84 #[serde(default)]
86 pub lifecycle: Lifecycle,
87
88 #[serde(default = "Utc::now")]
89 pub created_at: DateTime<Utc>,
90
91 #[serde(default = "Utc::now")]
92 pub updated_at: DateTime<Utc>,
93
94 #[serde(default)]
96 pub maturity: Maturity,
97
98 #[serde(default)]
100 pub decay: DecayMeta,
101
102 #[serde(default)]
105 pub scope: Scope,
106
107 #[serde(default, skip_serializing_if = "is_zero_u32")]
111 pub version: u32,
112
113 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub revision: Option<String>,
117}
118
119impl Default for KnowledgeBase {
120 fn default() -> Self {
121 Self {
122 schema: default_schema(),
123 name: String::new(),
124 description: String::new(),
125 content: Content::default(),
126 tier: Tier::default(),
127 importance: default_importance(),
128 confidence: default_confidence(),
129 tags: Tags::default(),
130 applies: Applies::default(),
131 evidence: Evidence::default(),
132 links: Links::default(),
133 lifecycle: Lifecycle::default(),
134 created_at: Utc::now(),
135 updated_at: Utc::now(),
136 maturity: Maturity::default(),
137 decay: DecayMeta::default(),
138 scope: Scope::default(),
139 version: 0,
140 revision: None,
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::pattern::Content;
149
150 #[test]
151 fn test_knowledgebase_serde_roundtrip() {
152 let kb = KnowledgeBase {
153 name: "test-kb".into(),
154 description: "A test knowledge base".into(),
155 content: Content::DualLayer {
156 technical: "Use X for Y".into(),
157 principle: Some("Because Z".into()),
158 },
159 tier: Tier::Project,
160 importance: 0.8,
161 confidence: 0.9,
162 maturity: Maturity::Stable,
163 ..Default::default()
164 };
165
166 let yaml = serde_yaml::to_string(&kb).expect("serialize");
167 let kb2: KnowledgeBase = serde_yaml::from_str(&yaml).expect("deserialize");
168
169 assert_eq!(kb2.name, "test-kb");
170 assert_eq!(kb2.description, "A test knowledge base");
171 assert_eq!(kb2.tier, Tier::Project);
172 assert!((kb2.importance - 0.8).abs() < 0.001);
173 assert!((kb2.confidence - 0.9).abs() < 0.001);
174 assert_eq!(kb2.maturity, Maturity::Stable);
175 assert_eq!(kb2.schema, 3);
176 }
177
178 #[test]
179 fn test_knowledgebase_default() {
180 let kb = KnowledgeBase::default();
181 assert_eq!(kb.schema, 3);
182 assert!((kb.confidence - 0.5).abs() < 0.001);
183 assert_eq!(kb.maturity, Maturity::Draft);
184 }
185
186 #[test]
187 fn test_knowledgebase_minimal_yaml() {
188 let yaml = "name: minimal\ndescription: Minimal test\ncontent: Just text\n";
190 let kb: KnowledgeBase = serde_yaml::from_str(yaml).expect("deserialize minimal");
191 assert_eq!(kb.name, "minimal");
192 assert_eq!(kb.schema, 3); assert_eq!(kb.tier, Tier::Session);
194 }
195}