pub fn decay_score(importance: f64, half_life: f64, elapsed: f64) -> f64 {
if half_life > 0.0 {
importance * f64::powf(2.0, -elapsed.max(0.0) / half_life)
} else {
0.0
}
}
pub fn recency_score(age: f64) -> f64 {
f64::exp(-age.max(0.0) / (7.0 * 86400.0))
}
pub fn valence_boost(valence: f64) -> f64 {
1.0 + 0.3 * valence.abs()
}
const NEGATIVE_QUERY_WORDS: &[&str] = &[
"sad",
"frustrated",
"angry",
"bad",
"worst",
"low",
"lows",
"difficult",
"hard",
"struggle",
"pain",
"stress",
"anxious",
"upset",
"failed",
"failure",
"problem",
"negative",
"stressing",
"worried",
"tough",
];
const POSITIVE_QUERY_WORDS: &[&str] = &[
"happy",
"joy",
"great",
"best",
"high",
"good",
"wonderful",
"excited",
"proud",
"success",
"achievement",
"positive",
"celebration",
"love",
];
fn sentiment_stem(word: &str) -> &str {
for suffix in &[
"iest", "ness", "ment", "ful", "est", "ing", "ous", "ive", "ity", "ed", "er", "ly", "al",
"es", "s",
] {
if word.len() > suffix.len() + 2 && word.ends_with(suffix) {
return &word[..word.len() - suffix.len()];
}
}
word
}
pub fn detect_query_sentiment(query_text: &str) -> f64 {
let lower = query_text.to_lowercase();
let tokens: Vec<&str> = lower
.split(|c: char| !c.is_alphanumeric())
.filter(|s| !s.is_empty())
.collect();
let matches_list = |token: &str, words: &[&str]| -> bool {
let stemmed = sentiment_stem(token);
words.iter().any(|w| {
token == *w
|| stemmed == *w
|| (token.len() >= 4 && token.starts_with(w))
|| (stemmed.len() >= 4 && w.starts_with(stemmed))
})
};
let neg_count = tokens
.iter()
.filter(|t| matches_list(t, NEGATIVE_QUERY_WORDS))
.count();
let pos_count = tokens
.iter()
.filter(|t| matches_list(t, POSITIVE_QUERY_WORDS))
.count();
if neg_count > pos_count {
-1.0
} else if pos_count > neg_count {
1.0
} else {
0.0
}
}
pub fn query_valence_boost(memory_valence: f64, query_sentiment: f64) -> f64 {
let base = 1.0 + 0.3 * memory_valence.abs();
if query_sentiment == 0.0 {
return base;
}
let alignment = if memory_valence.abs() < 1e-10 {
0.0
} else {
query_sentiment * memory_valence.signum()
};
base * (1.0 + 0.2 * alignment)
}
pub fn composite_score_with_sentiment(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
valence: f64,
query_sentiment: f64,
) -> f64 {
let freshness_z = freshness_z(decay, recency);
let importance_z = importance_gate(similarity) * importance.clamp(0.0, 1.0);
W_SIM
* similarity
* policy_mult(freshness_z, importance_z, 0.0, 0.0, 0.0)
* query_valence_boost(valence, query_sentiment)
}
pub const POLICY_BUDGET_LN: f64 = 0.262_364_264_467_491_9;
pub const PW_FRESHNESS: f64 = 0.22;
pub const PW_IMPORTANCE: f64 = 0.40;
pub const PW_GRAPH: f64 = 0.13;
pub const PW_AGREEMENT: f64 = 0.13;
pub const PW_USAGE: f64 = 0.12;
#[inline]
pub fn freshness_z(decay: f64, recency: f64) -> f64 {
((W_DECAY * decay + W_RECENCY * recency) / (W_DECAY + W_RECENCY)).clamp(0.0, 1.0)
}
#[inline]
pub fn usage_z(access_count: u32) -> f64 {
((access_count as f64).ln_1p() / (ACCESS_SATURATION as f64).ln_1p()).min(1.0)
}
pub const LANE_LIFT_MAX: f64 = 0.10;
#[inline]
pub fn lane_lift_mult(z: f64) -> f64 {
1.0 + crate::base::tuning::tuning().lane_lift_max * z.clamp(0.0, 1.0)
}
#[inline]
pub fn policy_mult(
freshness_z: f64,
importance_z: f64,
graph_z: f64,
agreement_z: f64,
usage_z: f64,
) -> f64 {
let t = crate::base::tuning::tuning();
let (wf, wi, wg, wa, wu) = t.normalized_weights();
let z = wf * freshness_z.clamp(0.0, 1.0)
+ wi * importance_z.clamp(0.0, 1.0)
+ wg * graph_z.clamp(0.0, 1.0)
+ wa * agreement_z.clamp(0.0, 1.0)
+ wu * usage_z.clamp(0.0, 1.0);
(t.policy_budget_ln() * z).exp()
}
pub const AGREEMENT_SCALE: f64 = 0.125;
#[inline]
pub fn agreement_mult(extra_lanes: usize) -> f64 {
let t = crate::base::tuning::tuning();
let (_, _, _, wa, _) = t.normalized_weights();
(t.policy_budget_ln() * wa * (extra_lanes.min(2) as f64 / 2.0)).exp()
}
pub const GRAPH_SCALE: f64 = 0.125;
#[inline]
pub fn graph_mult(graph_proximity: f64) -> f64 {
let t = crate::base::tuning::tuning();
let (_, _, wg, _, _) = t.normalized_weights();
(t.policy_budget_ln() * wg * graph_proximity.clamp(0.0, 1.0)).exp()
}
pub fn graph_composite_score_with_sentiment(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
valence: f64,
graph_proximity: f64,
query_sentiment: f64,
) -> f64 {
composite_score_with_sentiment(
similarity,
decay,
recency,
importance,
valence,
query_sentiment,
) * graph_mult(graph_proximity)
}
pub const W_SIM: f64 = 0.50;
pub const W_DECAY: f64 = 0.20;
pub const W_RECENCY: f64 = 0.30;
pub const FRESHNESS_SCALE: f64 = 0.25;
#[inline]
pub fn freshness_mult(decay: f64, recency: f64, w_decay: f64, w_recency: f64) -> f64 {
1.0 + FRESHNESS_SCALE * (w_decay * decay + w_recency * recency)
}
pub const GATE_K: f64 = 12.0;
pub const GATE_TAU: f64 = 0.25;
pub const ALPHA_IMP: f64 = 0.80;
pub const GW_SIM: f64 = 0.35;
pub const GW_DECAY: f64 = 0.15;
pub const GW_RECENCY: f64 = 0.20;
pub const GW_GRAPH: f64 = 0.30;
pub const GW_ALPHA_IMP: f64 = 0.60;
use crate::types::ScoreContributions;
#[inline]
fn sigmoid(x: f64) -> f64 {
1.0 / (1.0 + (-x).exp())
}
#[inline]
pub fn importance_gate(similarity: f64) -> f64 {
let t = crate::base::tuning::tuning();
sigmoid(t.gate_k * (similarity - t.gate_tau))
}
pub fn composite_score(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
valence: f64,
) -> f64 {
let freshness_z = freshness_z(decay, recency);
let importance_z = importance_gate(similarity) * importance.clamp(0.0, 1.0);
W_SIM
* similarity
* policy_mult(freshness_z, importance_z, 0.0, 0.0, 0.0)
* valence_boost(valence)
}
pub fn standard_contributions(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
) -> ScoreContributions {
let gate = importance_gate(similarity);
ScoreContributions {
similarity: W_SIM * similarity,
decay: POLICY_BUDGET_LN * PW_FRESHNESS * freshness_z(decay, 0.0),
recency: POLICY_BUDGET_LN * PW_FRESHNESS * freshness_z(0.0, recency),
importance: POLICY_BUDGET_LN * PW_IMPORTANCE * gate * importance.min(1.0),
graph_proximity: 0.0,
}
}
pub fn graph_composite_score(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
valence: f64,
graph_proximity: f64,
) -> f64 {
let freshness_z = freshness_z(decay, recency);
let importance_z = importance_gate(similarity) * importance.clamp(0.0, 1.0);
W_SIM
* similarity
* policy_mult(freshness_z, importance_z, graph_proximity, 0.0, 0.0)
* valence_boost(valence)
}
pub fn graph_contributions(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
graph_proximity: f64,
) -> ScoreContributions {
if graph_proximity > 0.0 {
let gate = importance_gate(similarity);
ScoreContributions {
similarity: GW_SIM * similarity,
decay: FRESHNESS_SCALE * GW_DECAY * decay,
recency: FRESHNESS_SCALE * GW_RECENCY * recency,
importance: gate * GW_ALPHA_IMP * importance.min(1.0),
graph_proximity: GW_GRAPH * graph_proximity,
}
} else {
standard_contributions(similarity, decay, recency, importance)
}
}
pub fn eviction_score(decay: f64, recency: f64, access_count: u32) -> f64 {
let access_resist =
((access_count as f64).ln_1p() / (ACCESS_SATURATION as f64).ln_1p()).min(1.0);
0.6 * decay + 0.4 * recency + ACCESS_WEIGHT * access_resist
}
pub const ACCESS_SATURATION: u32 = 20;
pub const ACCESS_WEIGHT: f64 = 0.5;
pub fn build_why(similarity: f64, recency: f64, decay: f64, valence: f64) -> Vec<String> {
let mut why = Vec::new();
if similarity > 0.5 {
why.push(format!("semantically similar ({similarity:.2})"));
}
if recency > 0.5 {
why.push("recent".to_string());
}
if decay > 0.3 {
why.push(format!("important (decay={decay:.2})"));
}
if valence.abs() > 0.5 {
why.push(format!("emotionally weighted ({valence:.2})"));
}
if why.is_empty() {
why.push("matched query".to_string());
}
why
}
pub fn adaptive_composite_score(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
valence: f64,
query_sentiment: f64,
weights: &crate::types::LearnedWeights,
) -> f64 {
let freshness_z = ((weights.w_decay * decay + weights.w_recency * recency)
/ (weights.w_decay + weights.w_recency).max(1e-9))
.clamp(0.0, 1.0);
let gate = sigmoid(GATE_K * (similarity - weights.gate_tau));
let importance_z = gate * importance.clamp(0.0, 1.0);
let imp_w = (weights.alpha_imp / ALPHA_IMP * PW_IMPORTANCE).clamp(0.0, 1.0);
let late_w = PW_GRAPH + PW_AGREEMENT + PW_USAGE;
let total_w = PW_FRESHNESS + imp_w + late_w;
let scale = if total_w > 1.0 { 1.0 / total_w } else { 1.0 };
let z = (PW_FRESHNESS * freshness_z + imp_w * importance_z) * scale;
weights.w_sim
* similarity
* (POLICY_BUDGET_LN * z).exp()
* query_valence_boost(valence, query_sentiment)
}
pub fn adaptive_contributions(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
weights: &crate::types::LearnedWeights,
) -> ScoreContributions {
let gate = sigmoid(GATE_K * (similarity - weights.gate_tau));
ScoreContributions {
similarity: weights.w_sim * similarity,
decay: FRESHNESS_SCALE * weights.w_decay * decay,
recency: FRESHNESS_SCALE * weights.w_recency * recency,
importance: gate * weights.alpha_imp * importance.min(1.0),
graph_proximity: 0.0,
}
}
pub fn adaptive_graph_composite_score(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
valence: f64,
graph_proximity: f64,
query_sentiment: f64,
weights: &crate::types::LearnedWeights,
) -> f64 {
adaptive_composite_score(
similarity,
decay,
recency,
importance,
valence,
query_sentiment,
weights,
) * graph_mult(graph_proximity)
}
pub fn adaptive_graph_contributions(
similarity: f64,
decay: f64,
recency: f64,
importance: f64,
graph_proximity: f64,
weights: &crate::types::LearnedWeights,
) -> ScoreContributions {
let mut c = adaptive_contributions(similarity, decay, recency, importance, weights);
c.graph_proximity = POLICY_BUDGET_LN * PW_GRAPH * graph_proximity.clamp(0.0, 1.0);
c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decay_score_fresh() {
let score = decay_score(0.8, 604800.0, 0.0);
assert!((score - 0.8).abs() < 1e-10);
}
#[test]
fn test_decay_score_one_half_life() {
let score = decay_score(1.0, 100.0, 100.0);
assert!((score - 0.5).abs() < 1e-10);
}
#[test]
fn test_decay_score_zero_half_life() {
let score = decay_score(0.8, 0.0, 100.0);
assert_eq!(score, 0.0);
}
#[test]
fn test_recency_score_fresh() {
let score = recency_score(0.0);
assert!((score - 1.0).abs() < 1e-10);
}
#[test]
fn test_recency_score_seven_days() {
let score = recency_score(7.0 * 86400.0);
assert!((score - f64::exp(-1.0)).abs() < 1e-10);
}
#[test]
fn test_valence_boost_zero() {
assert!((valence_boost(0.0) - 1.0).abs() < 1e-10);
}
#[test]
fn test_valence_boost_positive() {
assert!((valence_boost(1.0) - 1.3).abs() < 1e-10);
}
#[test]
fn test_valence_boost_negative() {
assert!((valence_boost(-0.5) - 1.15).abs() < 1e-10);
}
#[test]
fn test_importance_gate_high_similarity() {
let gate = importance_gate(0.8);
assert!(gate > 0.99, "gate at sim=0.8 should be ~1.0, got {gate}");
}
#[test]
fn test_importance_gate_low_similarity() {
let gate = importance_gate(0.05);
assert!(gate < 0.12, "gate at sim=0.05 should be small, got {gate}");
}
#[test]
fn test_importance_gate_at_threshold() {
let gate = importance_gate(GATE_TAU);
assert!(
(gate - 0.5).abs() < 1e-10,
"gate at sim=τ should be 0.5, got {gate}"
);
}
#[test]
fn test_importance_gate_monotonic() {
let low = importance_gate(0.1);
let mid = importance_gate(0.3);
let high = importance_gate(0.7);
assert!(mid > low, "gate should increase with similarity");
assert!(high > mid, "gate should increase with similarity");
}
#[test]
fn test_high_imp_low_sim_loses_to_low_imp_high_sim() {
let irrelevant_important = composite_score(0.10, 0.5, 0.5, 1.0, 0.0);
let relevant_normal = composite_score(0.60, 0.5, 0.5, 0.3, 0.0);
assert!(relevant_normal > irrelevant_important,
"relevant_normal ({relevant_normal:.4}) should beat irrelevant_important ({irrelevant_important:.4})");
}
#[test]
fn test_high_imp_high_sim_beats_low_imp_high_sim() {
let important = composite_score(0.70, 0.5, 0.5, 0.9, 0.0);
let normal = composite_score(0.70, 0.5, 0.5, 0.3, 0.0);
assert!(
important > normal,
"when both relevant, higher importance should win: {important:.4} vs {normal:.4}"
);
}
#[test]
fn test_composite_score_basic() {
let score = composite_score(1.0, 1.0, 1.0, 1.0, 0.0);
let relevance = W_SIM * 1.0;
assert!(
score > relevance,
"maxed priors must lift a fully-relevant record above bare relevance"
);
assert!(
score <= relevance * POLICY_BUDGET_LN.exp() + 1e-12,
"priors must never exceed the shared budget: {score} > {}",
relevance * POLICY_BUDGET_LN.exp()
);
}
#[test]
fn test_composite_score_with_valence() {
let neutral = composite_score(1.0, 1.0, 1.0, 1.0, 0.0);
let valenced = composite_score(1.0, 1.0, 1.0, 1.0, 1.0);
assert!((valenced - neutral * 1.3).abs() < 1e-10);
}
#[test]
fn test_graph_composite_zero_proximity_matches_original() {
let original = composite_score(0.8, 0.6, 0.9, 0.7, 0.3);
let graph = graph_composite_score(0.8, 0.6, 0.9, 0.7, 0.3, 0.0);
assert!((original - graph).abs() < 1e-10);
}
#[test]
fn test_graph_composite_with_proximity() {
let with_edges = graph_composite_score(0.5, 0.5, 0.5, 0.5, 0.0, 1.0);
let without = graph_composite_score(0.5, 0.5, 0.5, 0.5, 0.0, 0.0);
assert!(
with_edges > without,
"proximity must still break ties between equally-relevant records"
);
assert!(
with_edges <= without * POLICY_BUDGET_LN.exp() + 1e-12,
"proximity shares the ONE prior budget; it cannot exceed it"
);
let irrelevant_connected = graph_composite_score(0.0026, 1.0, 1.0, 1.0, 0.0, 1.0);
let relevant_isolated = graph_composite_score(0.309, 0.0, 0.0, 0.0, 0.0, 0.0);
assert!(
relevant_isolated > irrelevant_connected,
"the graph wall is back: connected {irrelevant_connected} beat relevant {relevant_isolated}"
);
}
#[test]
fn the_recency_wall_is_torn_down() {
let old_relevant = composite_score(0.6, 0.0, 0.0, 0.5, 0.0);
let fresh_irrelevant = composite_score(0.3, 1.0, 1.0, 0.5, 0.0);
assert!(
old_relevant > fresh_irrelevant,
"an old relevant record must outrank fresh irrelevant noise: \
{old_relevant:.4} vs {fresh_irrelevant:.4}"
);
}
#[test]
fn test_composite_monotonic_in_similarity() {
let low = composite_score(0.3, 0.5, 0.5, 0.5, 0.0);
let high = composite_score(0.9, 0.5, 0.5, 0.5, 0.0);
assert!(high > low, "higher similarity should yield higher score");
}
#[test]
fn test_composite_monotonic_in_importance() {
let low = composite_score(0.5, 0.5, 0.5, 0.2, 0.0);
let high = composite_score(0.5, 0.5, 0.5, 0.9, 0.0);
assert!(
high > low,
"higher importance should yield higher score (when sim>τ)"
);
}
#[test]
fn test_composite_monotonic_in_recency() {
let low = composite_score(0.5, 0.5, 0.1, 0.5, 0.0);
let high = composite_score(0.5, 0.5, 0.9, 0.5, 0.0);
assert!(high > low, "higher recency should yield higher score");
}
#[test]
fn test_valence_symmetric() {
assert!((valence_boost(0.7) - valence_boost(-0.7)).abs() < 1e-10);
}
#[test]
fn test_valence_always_geq_1() {
for v in [-1.0, -0.5, 0.0, 0.5, 1.0] {
assert!(
valence_boost(v) >= 1.0,
"valence_boost({v}) = {} < 1.0",
valence_boost(v)
);
}
}
#[test]
fn test_composite_non_negative() {
for &sim in &[0.0, 0.5, 1.0] {
for &dec in &[0.0, 0.5, 1.0] {
for &rec in &[0.0, 0.5, 1.0] {
for &imp in &[0.0, 0.5, 1.0] {
for &val in &[-1.0, 0.0, 1.0] {
let s = composite_score(sim, dec, rec, imp, val);
assert!(
s >= 0.0,
"composite_score({sim},{dec},{rec},{imp},{val}) = {s} < 0"
);
}
}
}
}
}
}
#[test]
fn test_graph_composite_non_negative() {
for &prox in &[0.0, 0.25, 0.5, 1.0] {
let s = graph_composite_score(0.5, 0.5, 0.5, 0.5, 0.0, prox);
assert!(
s >= 0.0,
"graph_composite with prox={prox} should be non-negative"
);
}
}
#[test]
fn test_graph_proximity_increases_score() {
let without = graph_composite_score(0.3, 0.8, 0.7, 0.6, 0.0, 0.0);
let with = graph_composite_score(0.3, 0.8, 0.7, 0.6, 0.0, 0.8);
assert!(
with > without,
"graph proximity (0.8) should increase score: without={without}, with={with}"
);
}
#[test]
fn test_decay_monotonic_in_elapsed() {
let fresh = decay_score(0.8, 604800.0, 0.0);
let old = decay_score(0.8, 604800.0, 604800.0);
let ancient = decay_score(0.8, 604800.0, 604800.0 * 10.0);
assert!(fresh > old);
assert!(old > ancient);
}
#[test]
fn test_recency_monotonic_in_age() {
let fresh = recency_score(0.0);
let week = recency_score(7.0 * 86400.0);
let month = recency_score(30.0 * 86400.0);
assert!(fresh > week);
assert!(week > month);
}
#[test]
fn test_build_why_always_nonempty() {
let why = build_why(0.0, 0.0, 0.0, 0.0);
assert!(
!why.is_empty(),
"build_why should always produce at least one reason"
);
assert_eq!(why[0], "matched query");
}
#[test]
fn test_build_why_contains_similarity() {
let why = build_why(0.9, 0.1, 0.1, 0.0);
assert!(why.iter().any(|w| w.contains("semantically similar")));
}
#[test]
fn test_importance_capped_at_1() {
let capped = composite_score(0.5, 0.5, 0.5, 5.0, 0.0);
let at_one = composite_score(0.5, 0.5, 0.5, 1.0, 0.0);
assert!(
(capped - at_one).abs() < 1e-10,
"importance should be capped at 1.0"
);
}
#[test]
fn test_eviction_score() {
let score = eviction_score(1.0, 1.0, 0);
assert!((score - 1.0).abs() < 1e-10, "max inputs, no access => 1.0");
let score_zero = eviction_score(0.0, 0.0, 0);
assert!(
(score_zero - 0.0).abs() < 1e-10,
"zero inputs, no access => 0.0"
);
let low = eviction_score(0.1, 0.5, 0);
let high = eviction_score(0.9, 0.5, 0);
assert!(high > low, "higher decay => higher eviction score");
let cold = eviction_score(0.2, 0.2, 0);
let hot = eviction_score(0.2, 0.2, 25);
assert!(
hot > cold,
"frequent recall must resist eviction: {hot} > {cold}"
);
let at_sat = eviction_score(0.2, 0.2, ACCESS_SATURATION);
let beyond = eviction_score(0.2, 0.2, ACCESS_SATURATION * 100);
assert!(
(beyond - at_sat).abs() < 0.05,
"access resistance saturates"
);
}
#[test]
fn test_irrelevant_anchor_cannot_dominate() {
let anchor = composite_score(0.08, 0.8, 0.3, 0.9, 0.0);
let daily = composite_score(0.65, 0.6, 0.8, 0.2, 0.0);
assert!(
daily > anchor,
"relevant daily ({daily:.4}) should beat irrelevant anchor ({anchor:.4})"
);
}
#[test]
fn test_detect_query_sentiment_negative() {
assert_eq!(
detect_query_sentiment("What failures and problems have been stressing me out?"),
-1.0
);
assert_eq!(
detect_query_sentiment("Tell me about my emotional lows"),
-1.0
);
assert_eq!(
detect_query_sentiment("What was difficult this year?"),
-1.0
);
}
#[test]
fn test_detect_query_sentiment_positive() {
assert_eq!(detect_query_sentiment("What good things happened?"), 1.0);
assert_eq!(detect_query_sentiment("Tell me about happy moments"), 1.0);
assert_eq!(
detect_query_sentiment("What was my greatest achievement?"),
1.0
);
}
#[test]
fn test_detect_query_sentiment_neutral() {
assert_eq!(
detect_query_sentiment("What happened at work recently?"),
0.0
);
assert_eq!(detect_query_sentiment("Tell me about my family"), 0.0);
}
#[test]
fn test_query_valence_boost_neutral_query_matches_standard() {
for &v in &[-1.0, -0.5, 0.0, 0.5, 1.0] {
let standard = valence_boost(v);
let query_aware = query_valence_boost(v, 0.0);
assert!((standard - query_aware).abs() < 1e-10,
"neutral query should match standard: valence={v}, standard={standard}, query_aware={query_aware}");
}
}
#[test]
fn test_query_valence_boost_negative_alignment() {
let negative_aligned = query_valence_boost(-0.8, -1.0);
let standard = valence_boost(-0.8);
assert!(negative_aligned > standard,
"negative query + negative memory should boost more: aligned={negative_aligned:.4}, standard={standard:.4}");
}
#[test]
fn test_query_valence_boost_negative_misaligned() {
let misaligned = query_valence_boost(0.8, -1.0);
let standard = valence_boost(0.8);
assert!(misaligned < standard,
"negative query + positive memory should boost less: misaligned={misaligned:.4}, standard={standard:.4}");
}
#[test]
fn test_query_valence_boost_always_positive() {
for &v in &[-1.0, -0.5, 0.0, 0.5, 1.0] {
for &s in &[-1.0, 0.0, 1.0] {
let boost = query_valence_boost(v, s);
assert!(
boost >= 0.8,
"query_valence_boost({v}, {s}) = {boost} should be positive"
);
}
}
}
#[test]
fn test_composite_with_sentiment_matches_original_for_neutral() {
let original = composite_score(0.6, 0.5, 0.7, 0.8, 0.3);
let with_sent = composite_score_with_sentiment(0.6, 0.5, 0.7, 0.8, 0.3, 0.0);
assert!(
(original - with_sent).abs() < 1e-10,
"neutral sentiment should match original: {original} vs {with_sent}"
);
}
#[test]
fn test_gate_tau_regression_anchor_still_loses() {
let anchor = composite_score(0.08, 0.8, 0.3, 0.9, 0.0);
let daily = composite_score(0.65, 0.6, 0.8, 0.2, 0.0);
assert!(daily > anchor,
"with GATE_TAU=0.25, relevant daily ({daily:.4}) should still beat irrelevant anchor ({anchor:.4})");
let gate = importance_gate(0.08);
assert!(gate < 0.2, "gate at sim=0.08 should be small, got {gate}");
}
}
#[cfg(test)]
mod graph_wall_tests {
use super::*;
use crate::types::LearnedWeights;
#[test]
fn maximal_graph_edge_cannot_beat_a_relevant_record() {
let irrelevant = graph_composite_score_with_sentiment(0.0026, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0);
let relevant = graph_composite_score_with_sentiment(0.309, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0);
assert!(
relevant > irrelevant,
"graph wall rebuilt: irrelevant+connected {irrelevant:.4} beat relevant {relevant:.4}"
);
}
#[test]
fn graph_uplift_is_bounded_by_graph_scale() {
let s_low = 0.50;
let s_high = s_low * (1.0 + GRAPH_SCALE) * 1.001; let connected = graph_composite_score_with_sentiment(s_low, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
let plain = graph_composite_score_with_sentiment(s_high, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
assert!(
plain > connected,
"a gap wider than GRAPH_SCALE was reversed: {plain:.6} vs {connected:.6}"
);
}
#[test]
fn an_infinitesimal_edge_does_not_penalise() {
let no_edge = graph_composite_score_with_sentiment(0.4, 0.5, 0.5, 0.8, 0.0, 0.0, 0.0);
let tiny_edge = graph_composite_score_with_sentiment(0.4, 0.5, 0.5, 0.8, 0.0, 1e-9, 0.0);
assert!(
tiny_edge >= no_edge,
"a tiny edge reduced the score: {tiny_edge:.6} < {no_edge:.6}"
);
assert!((tiny_edge - no_edge).abs() < 1e-6, "discontinuity at zero");
}
#[test]
fn zero_similarity_scores_zero_even_when_connected() {
let s = graph_composite_score_with_sentiment(0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0);
assert_eq!(s, 0.0, "a connected but irrelevant record scored {s}");
}
#[test]
fn adaptive_path_obeys_the_same_wall() {
let w = LearnedWeights::default();
let irrelevant = adaptive_graph_composite_score(0.0026, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, &w);
let relevant = adaptive_graph_composite_score(0.309, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, &w);
assert!(
relevant > irrelevant,
"adaptive graph wall: {irrelevant:.4} beat {relevant:.4}"
);
let no_edge = adaptive_graph_composite_score(0.4, 0.5, 0.5, 0.8, 0.0, 0.0, 0.0, &w);
let tiny = adaptive_graph_composite_score(0.4, 0.5, 0.5, 0.8, 0.0, 1e-9, 0.0, &w);
assert!(
(tiny - no_edge).abs() < 1e-6,
"adaptive discontinuity at zero"
);
}
#[test]
fn the_old_additive_form_inverted_the_ranking() {
let old = |sim: f64, prox: f64| {
let base_rel =
(GW_SIM * sim + GW_GRAPH * prox) * freshness_mult(1.0, 1.0, GW_DECAY, GW_RECENCY);
let imp_mult = 1.0 + importance_gate(sim) * GW_ALPHA_IMP * 1.0;
base_rel * imp_mult
};
let old_irrelevant = old(0.0026, 1.0);
let old_relevant = old(0.309, 0.0);
assert!(
old_irrelevant > old_relevant,
"history not reproduced: {old_irrelevant:.4} vs {old_relevant:.4}"
);
let now_irrelevant =
graph_composite_score_with_sentiment(0.0026, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0);
let now_relevant =
graph_composite_score_with_sentiment(0.309, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0);
assert!(
now_relevant > now_irrelevant,
"the fix does not fix it: {now_relevant:.4} vs {now_irrelevant:.4}"
);
}
#[test]
fn graph_still_breaks_ties_among_near_equals() {
let connected = graph_composite_score_with_sentiment(0.40, 0.5, 0.5, 0.5, 0.0, 1.0, 0.0);
let alone = graph_composite_score_with_sentiment(0.40, 0.5, 0.5, 0.5, 0.0, 0.0, 0.0);
assert!(
connected > alone,
"graph evidence became inert: {connected:.6} vs {alone:.6}"
);
}
}
#[cfg(test)]
mod policy_budget_tests {
use super::*;
#[test]
fn policy_weights_partition_one_budget() {
let total = PW_FRESHNESS + PW_IMPORTANCE + PW_GRAPH + PW_AGREEMENT + PW_USAGE;
assert!(
total <= 1.0 + 1e-12,
"prior weights must partition ONE budget; sum = {total}"
);
}
#[test]
fn policy_layer_never_exceeds_its_stated_ceiling() {
let ceiling = POLICY_BUDGET_LN.exp();
assert!((ceiling - 1.30).abs() < 1e-9, "ceiling drifted: {ceiling}");
let maxed = policy_mult(1.0, 1.0, 1.0, 1.0, 1.0);
assert!(
(maxed - ceiling).abs() < 1e-9,
"all priors maxed must equal exactly the budget, got {maxed}"
);
assert!(policy_mult(9.0, 9.0, 9.0, -9.0, 9.0) <= ceiling + 1e-9);
assert!(policy_mult(9.0, 9.0, 9.0, -9.0, 9.0) < ceiling);
assert!((policy_mult(9.0, 9.0, 9.0, 9.0, 9.0) - ceiling).abs() < 1e-9);
assert_eq!(policy_mult(0.0, 0.0, 0.0, 0.0, 0.0), 1.0);
}
#[test]
fn priors_cannot_invert_a_real_relevance_gap() {
let weak_but_privileged = composite_score_with_sentiment(0.30, 1.0, 1.0, 1.0, 1.0, 1.0);
let strong_but_bare = composite_score_with_sentiment(0.60, 0.0, 0.0, 0.0, 0.0, 0.0);
assert!(
strong_but_bare > weak_but_privileged,
"a 2x similarity gap must survive every prior: bare 0.60 scored {strong_but_bare}, \
privileged 0.30 scored {weak_but_privileged}"
);
}
#[test]
fn priors_still_break_ties_between_near_equals() {
let fresh_important = composite_score_with_sentiment(0.50, 1.0, 1.0, 1.0, 0.0, 0.0);
let stale_trivial = composite_score_with_sentiment(0.50, 0.0, 0.0, 0.0, 0.0, 0.0);
assert!(
fresh_important > stale_trivial,
"priors must still order equally-relevant records"
);
let privileged = composite_score_with_sentiment(0.60, 1.0, 1.0, 1.0, 0.0, 0.0);
let plain = composite_score_with_sentiment(1.00, 0.0, 0.0, 0.0, 0.0, 0.0);
assert!(privileged < plain, "budget must not reach beyond ~30%");
}
#[test]
fn learned_weights_cannot_widen_the_budget() {
let mut w = crate::types::LearnedWeights::default();
w.alpha_imp = 1.5;
w.w_decay = 1.0;
w.w_recency = 1.0;
for sim in [0.5_f64, 0.9, 0.99] {
let boosted = adaptive_composite_score(sim, 1.0, 1.0, 1.0, 0.0, 0.0, &w)
* graph_mult(1.0)
* agreement_mult(9);
let ceiling = w.w_sim * sim * POLICY_BUDGET_LN.exp();
assert!(
boosted <= ceiling + 1e-9,
"learned weights escaped the budget at sim={sim}: {boosted} > {ceiling}"
);
}
}
}
#[cfg(test)]
mod lane_and_weight_bound_tests {
use super::*;
use crate::types::LearnedWeights;
#[test]
fn exploration_lanes_cannot_outrank_a_real_match() {
let irrelevant_but_charged =
composite_score(0.02, 1.0, 1.0, 1.0, 1.0) * lane_lift_mult(1.0);
let relevant_plain = composite_score(0.80, 0.0, 0.0, 0.0, 0.0);
assert!(
relevant_plain > irrelevant_but_charged,
"a lane lift must not invert relevance: plain 0.80 = {relevant_plain}, \
lifted 0.02 = {irrelevant_but_charged}"
);
assert!((lane_lift_mult(0.0) - 1.0).abs() < 1e-12);
assert!((lane_lift_mult(5.0) - (1.0 + LANE_LIFT_MAX)).abs() < 1e-12);
}
#[test]
fn corrupt_learned_weights_are_clamped_not_trusted() {
let hostile = LearnedWeights {
w_sim: -3.0,
w_decay: 99.0,
w_recency: f64::NAN,
gate_tau: 0.0,
alpha_imp: 50.0,
keyword_boost: 500.0, generation: 7,
}
.clamped();
assert!(hostile.w_sim >= 0.05 && hostile.w_sim <= 1.0);
assert!(hostile.w_decay <= 1.0);
assert!(hostile.w_recency.is_finite(), "NaN must not survive");
assert!(hostile.gate_tau >= 0.05);
assert!(hostile.alpha_imp <= 1.5);
assert!(
hostile.keyword_boost <= 1.0,
"keyword_boost is ADDITIVE and has no similarity-relative ceiling \
of its own — the clamp is its only bound"
);
assert_eq!(hostile.generation, 7, "generation is data, not a weight");
let sane = LearnedWeights::default();
assert_eq!(sane.clone().clamped().w_sim, sane.w_sim);
assert_eq!(sane.clone().clamped().keyword_boost, sane.keyword_boost);
}
}
#[cfg(test)]
mod budget_composition_tests {
use super::*;
#[test]
fn stagewise_application_composes_to_one_budget() {
let stagewise = composite_score_with_sentiment(1.0, 1.0, 1.0, 1.0, 0.0, 0.0)
* graph_mult(1.0)
* agreement_mult(9);
let all_at_once = W_SIM * 1.0 * policy_mult(1.0, importance_gate(1.0), 1.0, 1.0, 0.0);
assert!(
(stagewise - all_at_once).abs() < 1e-9,
"stagewise {stagewise} must equal all-at-once {all_at_once}"
);
assert!(
stagewise <= W_SIM * POLICY_BUDGET_LN.exp() + 1e-9,
"the whole pipeline must stay inside one budget"
);
}
#[test]
fn full_pipeline_cannot_invert_a_real_relevance_gap() {
let privileged = composite_score_with_sentiment(0.30, 1.0, 1.0, 1.0, 1.0, 1.0)
* graph_mult(1.0)
* agreement_mult(9);
let bare = composite_score_with_sentiment(0.60, 0.0, 0.0, 0.0, 0.0, 0.0);
assert!(
bare > privileged,
"full-pipeline priors inverted a 2x similarity gap: bare {bare}, \
privileged {privileged}"
);
}
}