use crate::chunk::{ChunkKind, NewMemoryChunk};
#[allow(clippy::collapsible_match)]
pub fn score(chunk: &NewMemoryChunk) -> f32 {
if let Some(explicit) = chunk.importance {
return clamp(explicit);
}
let mut score: f32 = 0.5;
match chunk.kind {
ChunkKind::Correction => score += 0.4,
ChunkKind::JobSummary => score += 0.2,
ChunkKind::Fire => {
if metadata_str(chunk, "action_type")
.map(|s| s == "RequireConfirmation" || s == "Veto")
.unwrap_or(false)
{
score += 0.2;
}
}
ChunkKind::Action => {
if metadata_str(chunk, "decision")
.map(|s| s == "denied" || s == "vetoed")
.unwrap_or(false)
{
score += 0.2;
}
}
ChunkKind::Observation => {
if metadata_str(chunk, "stability")
.map(|s| s == "transient")
.unwrap_or(false)
{
score -= 0.2;
}
}
ChunkKind::Context => {
if metadata_i64(chunk, "duration_ms")
.map(|ms| ms < 2000)
.unwrap_or(false)
{
score -= 0.1;
}
}
_ => {}
}
if metadata_bool(chunk, "user_ack") {
score += 0.3;
}
if metadata_bool(chunk, "pinned_entity") {
score += 0.1;
}
let clamped = clamp(score);
match chunk.kind {
ChunkKind::Chat | ChunkKind::Action => clamped.max(0.1),
_ => clamped,
}
}
fn clamp(v: f32) -> f32 {
v.clamp(0.0, 1.0)
}
fn metadata_str<'a>(chunk: &'a NewMemoryChunk, key: &str) -> Option<&'a str> {
chunk.metadata.get(key).and_then(|v| v.as_str())
}
fn metadata_bool(chunk: &NewMemoryChunk, key: &str) -> bool {
chunk
.metadata
.get(key)
.and_then(|v| v.as_bool())
.unwrap_or(false)
}
fn metadata_i64(chunk: &NewMemoryChunk, key: &str) -> Option<i64> {
chunk.metadata.get(key).and_then(|v| v.as_i64())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunk::{ChunkKind, ChunkSource};
use serde_json::json;
fn nc(kind: ChunkKind) -> NewMemoryChunk {
NewMemoryChunk {
kind,
source: ChunkSource::Embedded,
session_id: None,
project_root: None,
caller_id: "test".into(),
content: "x".into(),
metadata: json!({}),
importance: None,
shareable: false,
pinned: false,
}
}
#[test]
fn baseline_is_half() {
let s = score(&nc(ChunkKind::Observation));
assert_eq!(s, 0.5);
}
#[test]
fn correction_bumps_to_high() {
let s = score(&nc(ChunkKind::Correction));
assert!((s - 0.9).abs() < f32::EPSILON);
}
#[test]
fn job_summary_bumps_above_baseline() {
let s = score(&nc(ChunkKind::JobSummary));
assert!((s - 0.7).abs() < f32::EPSILON);
}
#[test]
fn fire_with_require_confirmation_bumps() {
let mut c = nc(ChunkKind::Fire);
c.metadata = json!({"action_type": "RequireConfirmation"});
assert!((score(&c) - 0.7).abs() < f32::EPSILON);
}
#[test]
fn action_denied_bumps() {
let mut c = nc(ChunkKind::Action);
c.metadata = json!({"decision": "denied"});
assert!((score(&c) - 0.7).abs() < f32::EPSILON);
}
#[test]
fn transient_observation_reduced() {
let mut c = nc(ChunkKind::Observation);
c.metadata = json!({"stability": "transient"});
assert!((score(&c) - 0.3).abs() < f32::EPSILON);
}
#[test]
fn user_ack_and_pinned_entity_stack() {
let mut c = nc(ChunkKind::Chat);
c.metadata = json!({"user_ack": true, "pinned_entity": true});
assert!((score(&c) - 0.9).abs() < f32::EPSILON);
}
#[test]
fn chat_floor_protects_against_zero() {
let mut c = nc(ChunkKind::Chat);
c.metadata = json!({"stability": "transient"});
c.importance = Some(0.0);
assert_eq!(score(&c), 0.0);
}
#[test]
fn explicit_importance_is_honored_with_clamp() {
let mut c = nc(ChunkKind::Chat);
c.importance = Some(1.7);
assert_eq!(score(&c), 1.0);
c.importance = Some(-0.3);
assert_eq!(score(&c), 0.0);
}
#[test]
fn chat_baseline_with_floor() {
let s = score(&nc(ChunkKind::Chat));
assert_eq!(s, 0.5);
}
}