1use crate::chunk::{ChunkKind, NewMemoryChunk};
38
39#[allow(clippy::collapsible_match)]
52pub fn score(chunk: &NewMemoryChunk) -> f32 {
53 if let Some(explicit) = chunk.importance {
56 return clamp(explicit);
57 }
58
59 let mut score: f32 = 0.5;
60
61 match chunk.kind {
62 ChunkKind::Correction => score += 0.4,
63 ChunkKind::JobSummary => score += 0.2,
64 ChunkKind::Fire => {
65 if metadata_str(chunk, "action_type")
66 .map(|s| s == "RequireConfirmation" || s == "Veto")
67 .unwrap_or(false)
68 {
69 score += 0.2;
70 }
71 }
72 ChunkKind::Action => {
73 if metadata_str(chunk, "decision")
74 .map(|s| s == "denied" || s == "vetoed")
75 .unwrap_or(false)
76 {
77 score += 0.2;
78 }
79 }
80 ChunkKind::Observation => {
81 if metadata_str(chunk, "stability")
82 .map(|s| s == "transient")
83 .unwrap_or(false)
84 {
85 score -= 0.2;
86 }
87 }
88 ChunkKind::Context => {
89 if metadata_i64(chunk, "duration_ms")
90 .map(|ms| ms < 2000)
91 .unwrap_or(false)
92 {
93 score -= 0.1;
94 }
95 }
96 _ => {}
97 }
98
99 if metadata_bool(chunk, "user_ack") {
100 score += 0.3;
101 }
102 if metadata_bool(chunk, "pinned_entity") {
103 score += 0.1;
104 }
105
106 let clamped = clamp(score);
107
108 match chunk.kind {
112 ChunkKind::Chat | ChunkKind::Action => clamped.max(0.1),
113 _ => clamped,
114 }
115}
116
117fn clamp(v: f32) -> f32 {
118 v.clamp(0.0, 1.0)
119}
120
121fn metadata_str<'a>(chunk: &'a NewMemoryChunk, key: &str) -> Option<&'a str> {
122 chunk.metadata.get(key).and_then(|v| v.as_str())
123}
124
125fn metadata_bool(chunk: &NewMemoryChunk, key: &str) -> bool {
126 chunk
127 .metadata
128 .get(key)
129 .and_then(|v| v.as_bool())
130 .unwrap_or(false)
131}
132
133fn metadata_i64(chunk: &NewMemoryChunk, key: &str) -> Option<i64> {
134 chunk.metadata.get(key).and_then(|v| v.as_i64())
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::chunk::{ChunkKind, ChunkSource};
141 use serde_json::json;
142
143 fn nc(kind: ChunkKind) -> NewMemoryChunk {
144 NewMemoryChunk {
145 kind,
146 source: ChunkSource::Embedded,
147 session_id: None,
148 project_root: None,
149 caller_id: "test".into(),
150 content: "x".into(),
151 metadata: json!({}),
152 importance: None,
153 shareable: false,
154 pinned: false,
155 }
156 }
157
158 #[test]
159 fn baseline_is_half() {
160 let s = score(&nc(ChunkKind::Observation));
161 assert_eq!(s, 0.5);
162 }
163
164 #[test]
165 fn correction_bumps_to_high() {
166 let s = score(&nc(ChunkKind::Correction));
167 assert!((s - 0.9).abs() < f32::EPSILON);
168 }
169
170 #[test]
171 fn job_summary_bumps_above_baseline() {
172 let s = score(&nc(ChunkKind::JobSummary));
173 assert!((s - 0.7).abs() < f32::EPSILON);
174 }
175
176 #[test]
177 fn fire_with_require_confirmation_bumps() {
178 let mut c = nc(ChunkKind::Fire);
179 c.metadata = json!({"action_type": "RequireConfirmation"});
180 assert!((score(&c) - 0.7).abs() < f32::EPSILON);
181 }
182
183 #[test]
184 fn action_denied_bumps() {
185 let mut c = nc(ChunkKind::Action);
186 c.metadata = json!({"decision": "denied"});
187 assert!((score(&c) - 0.7).abs() < f32::EPSILON);
188 }
189
190 #[test]
191 fn transient_observation_reduced() {
192 let mut c = nc(ChunkKind::Observation);
193 c.metadata = json!({"stability": "transient"});
194 assert!((score(&c) - 0.3).abs() < f32::EPSILON);
195 }
196
197 #[test]
198 fn user_ack_and_pinned_entity_stack() {
199 let mut c = nc(ChunkKind::Chat);
200 c.metadata = json!({"user_ack": true, "pinned_entity": true});
201 assert!((score(&c) - 0.9).abs() < f32::EPSILON);
203 }
204
205 #[test]
206 fn chat_floor_protects_against_zero() {
207 let mut c = nc(ChunkKind::Chat);
208 c.metadata = json!({"stability": "transient"});
210 c.importance = Some(0.0);
213 assert_eq!(score(&c), 0.0);
215 }
216
217 #[test]
218 fn explicit_importance_is_honored_with_clamp() {
219 let mut c = nc(ChunkKind::Chat);
220 c.importance = Some(1.7);
221 assert_eq!(score(&c), 1.0);
222 c.importance = Some(-0.3);
223 assert_eq!(score(&c), 0.0);
224 }
225
226 #[test]
227 fn chat_baseline_with_floor() {
228 let s = score(&nc(ChunkKind::Chat));
229 assert_eq!(s, 0.5);
231 }
232}