1use hippmem_core::model::understanding::{
5 DecisionFrame, EmotionFrame, GoalFrame, GoalStatus, Polarity, PreferenceFrame,
6};
7use hippmem_core::model::unit::MemoryUnit;
8use hippmem_core::score::UnitScore;
9use hippmem_model::lang::active_locales;
10
11pub struct EnrichInput {
13 pub unit: MemoryUnit,
14}
15
16pub struct EnrichOutput {
18 pub unit: MemoryUnit,
19}
20
21pub fn enrich_unit(input: EnrichInput) -> EnrichOutput {
24 let mut unit = input.unit;
25 let text = &unit.content.raw.clone();
26 let mut u = unit.understanding.clone();
27
28 if u.goals.is_empty() {
30 u.goals = extract_goals_enrich(text);
31 }
32 if u.preferences.is_empty() {
34 u.preferences = extract_preferences(text);
35 }
36 if u.emotions.is_empty() {
38 u.emotions = extract_emotions_enrich(text);
39 }
40 if u.decisions.is_empty() {
42 u.decisions = extract_decisions_enrich(text);
43 }
44
45 unit.understanding = u;
46 unit.stage = hippmem_core::model::unit::MemoryStage::Enriched;
47 unit.updated_at = unit.context.local_time;
48
49 EnrichOutput { unit }
50}
51
52fn extract_goals_enrich(text: &str) -> Vec<GoalFrame> {
53 for lang in active_locales() {
54 for m in lang.goal_markers {
55 if text.contains(m) {
56 return vec![GoalFrame {
57 description: format!("marker:{m}"),
58 status: GoalStatus::Active,
59 constraints: vec![],
60 confidence: UnitScore::new(0.45),
61 }];
62 }
63 }
64 }
65 vec![]
66}
67
68fn extract_preferences(text: &str) -> Vec<PreferenceFrame> {
69 for lang in active_locales() {
70 for w in lang.preference_pos {
71 if text.contains(w) {
72 return vec![PreferenceFrame {
73 object: format!("preference:{w}"),
74 polarity: Polarity::Like,
75 strength: UnitScore::new(0.5),
76 still_valid: true,
77 confidence: UnitScore::new(0.5),
78 }];
79 }
80 }
81 }
82 for lang in active_locales() {
83 for w in lang.preference_neg {
84 if text.contains(w) {
85 return vec![PreferenceFrame {
86 object: format!("preference:{w}"),
87 polarity: Polarity::Dislike,
88 strength: UnitScore::new(0.5),
89 still_valid: true,
90 confidence: UnitScore::new(0.5),
91 }];
92 }
93 }
94 }
95 vec![]
96}
97
98fn extract_emotions_enrich(text: &str) -> Vec<EmotionFrame> {
99 for lang in active_locales() {
100 for (w, ek) in lang.emotion_keywords {
101 if text.contains(w) {
102 return vec![EmotionFrame {
103 emotion: *ek,
104 intensity: UnitScore::new(0.6),
105 trigger: Some(format!("emotion:{w}")),
106 confidence: UnitScore::new(0.5),
107 }];
108 }
109 }
110 }
111 vec![]
112}
113
114fn extract_decisions_enrich(text: &str) -> Vec<DecisionFrame> {
115 for lang in active_locales() {
116 for m in lang.decision_markers {
117 if text.contains(m) {
118 return vec![DecisionFrame {
119 decision: format!("decision:{m}"),
120 rationale: Some(text.chars().take(100).collect()),
121 decided_at: None,
122 reverted: false,
123 confidence: UnitScore::new(0.45),
124 }];
125 }
126 }
127 }
128 vec![]
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use hippmem_core::ids::MemoryId;
135 use hippmem_core::model::understanding::MemoryUnderstanding;
136 use hippmem_core::model::unit::{
137 ContentType, Language, MemoryContent, MemoryStage, WriteContext,
138 };
139 use hippmem_core::time::Timestamp;
140
141 fn make_unit(text: &str) -> MemoryUnit {
142 MemoryUnit {
143 schema_version: 1,
144 id: MemoryId(1),
145 created_at: Timestamp(0),
146 updated_at: Timestamp(0),
147 content: MemoryContent {
148 raw: text.into(),
149 summary: None,
150 normalized: None,
151 language: Language::Zh,
152 content_type: ContentType::UserStatement,
153 },
154 context: WriteContext {
155 conversation_id: None,
156 session_id: None,
157 project_id: None,
158 task_id: None,
159 user_id: None,
160 local_time: Timestamp(1_700_000_000_000),
161 preceding_memory_ids: vec![],
162 source_refs: vec![],
163 },
164 understanding: MemoryUnderstanding {
165 entities: vec![],
166 events: vec![],
167 goals: vec![],
168 decisions: vec![],
169 preferences: vec![],
170 emotions: vec![],
171 causal_claims: vec![],
172 contradictions: vec![],
173 topics: vec![],
174 importance: UnitScore::new(0.5),
175 confidence: UnitScore::new(0.5),
176 },
177 association_keys: hippmem_core::model::links::AssociationKeys {
178 entity_keys: vec![],
179 temporal_keys: vec![],
180 lexical_signature: hippmem_core::model::links::LexicalSignature { simhash: [0; 4] },
181 semantic_signature: hippmem_core::model::links::SemanticSignature {
182 lexical_simhash: [0; 4],
183 dense_embedding_ref: None,
184 binary_code: [0, 0],
185 topic_minhash: [0u32; 16],
186 },
187 topic_keys: vec![],
188 emotion_keys: vec![],
189 goal_keys: vec![],
190 event_keys: vec![],
191 causal_keys: vec![],
192 },
193 links: vec![],
194 activation: hippmem_core::model::links::ActivationState {
195 last_retrieved_at: None,
196 retrieval_count: 0,
197 co_activations: vec![],
198 usage_score: UnitScore::new(0.5),
199 },
200 lifecycle: hippmem_core::model::unit::MemoryLifecycle::Active,
201 provenance: hippmem_core::model::unit::Provenance {
202 origin: hippmem_core::model::unit::SourceKind::Conversation,
203 generated_by: hippmem_core::model::unit::GeneratedBy::UserDirect,
204 reliability: UnitScore::new(0.5),
205 evidence_refs: vec![],
206 revision_history: vec![],
207 },
208 stage: MemoryStage::Indexed,
209 }
210 }
211
212 #[test]
213 fn enrich_adds_preferences() {
214 let unit = make_unit("I like programming in Rust");
215 let output = enrich_unit(EnrichInput { unit });
216 assert!(!output.unit.understanding.preferences.is_empty());
217 assert_eq!(output.unit.stage, MemoryStage::Enriched);
218 }
219
220 #[test]
221 fn enrich_adds_goals() {
222 let unit = make_unit("I plan to learn Rust programming");
223 let output = enrich_unit(EnrichInput { unit });
224 assert!(!output.unit.understanding.goals.is_empty());
225 }
226
227 #[test]
228 fn enrich_noop_on_empty_text() {
229 let unit = make_unit("The weather is nice today");
230 let output = enrich_unit(EnrichInput { unit });
231 assert_eq!(output.unit.stage, MemoryStage::Enriched);
232 }
233}