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