use std::collections::HashSet;
use uuid::Uuid;
use super::handle::PalaceHandle;
use super::id_tokens::{id_token_boost, query_id_tokens, rare_id_tokens};
use super::layers::{RANK_TRACE_TARGET, rank_score, uuid_prefix_eq};
use super::scope::scope_admits;
use super::types::RecallResult;
use crate::memory_core::decay::DecayConfig;
use crate::memory_core::dream::MIN_KEYWORD_CHARS;
use crate::memory_core::palace::Drawer;
use crate::memory_core::store::vector::VectorHit;
const CLOSET_BOOST: f32 = 0.15;
pub(super) fn score_candidates(
handle: &PalaceHandle,
hits: &[VectorHit],
allowed: &Option<HashSet<Uuid>>,
query_tokens: &[String],
layer: u8,
) -> Vec<RecallResult> {
let drawers = handle.drawers.read();
let closets = handle.closets.read();
let now = chrono::Utc::now();
let candidates: Vec<(f32, &Drawer)> = hits
.iter()
.filter_map(|hit| {
let drawer = drawers
.iter()
.find(|d| uuid_prefix_eq(d.id, hit.drawer_id))?;
(scope_admits(allowed, drawer.room_id) && !drawer.is_expired_at(now))
.then_some((hit.score, drawer))
})
.collect();
let id_tokens = query_id_tokens(query_tokens);
let contents: Vec<&str> = candidates.iter().map(|(_, d)| d.content()).collect();
let rare = rare_id_tokens(&id_tokens, &contents);
let closet_tokens: Vec<&str> = query_tokens
.iter()
.map(String::as_str)
.filter(|t| t.chars().count() > MIN_KEYWORD_CHARS || rare.contains(t))
.collect();
candidates
.into_iter()
.map(|(similarity, drawer)| {
let age_days = DecayConfig::age_days(drawer.created_at);
let boost = drawer.accumulated_boost(&handle.decay_config);
let eff_importance =
handle
.decay_config
.effective_importance(drawer.importance, age_days, boost);
let in_closet = closet_tokens.iter().any(|tok| {
closets
.get(*tok)
.is_some_and(|ids| ids.contains(&drawer.id))
});
let tag_boost = if in_closet { CLOSET_BOOST } else { 0.0 };
let id_boost = id_token_boost(&rare, drawer.content());
let score = rank_score(similarity, eff_importance, tag_boost + id_boost);
if layer == 2 {
tracing::debug!(
target: RANK_TRACE_TARGET,
drawer_id = %drawer.id,
similarity,
importance = drawer.importance,
eff_importance,
tag_boost,
id_boost,
score,
"l2 candidate"
);
}
RecallResult {
drawer: drawer.clone(),
score,
layer,
}
})
.collect()
}