use std::sync::OnceLock;
#[derive(Debug, Clone, PartialEq)]
pub struct Tuning {
pub policy_budget: f64,
pub pw_freshness: f64,
pub pw_importance: f64,
pub pw_graph: f64,
pub pw_agreement: f64,
pub pw_usage: f64,
pub gate_tau: f64,
pub gate_k: f64,
pub lane_lift_max: f64,
pub fts_min_sim: f64,
pub cold_min_sim: f64,
pub valence_min_sim: f64,
pub mmr_lambda: f64,
pub keyword_boost_override: f64,
pub quota_vector: f64,
pub quota_lexical: f64,
pub quota_claims: f64,
pub quota_graph: f64,
pub quota_exploration: f64,
pub novelty_weight: f64,
}
impl Default for Tuning {
fn default() -> Self {
Self {
policy_budget: 1.30,
pw_freshness: 0.22,
pw_importance: 0.40,
pw_graph: 0.13,
pw_agreement: 0.13,
pw_usage: 0.12,
gate_tau: 0.25,
gate_k: 12.0,
lane_lift_max: 0.10,
fts_min_sim: 0.05,
cold_min_sim: 0.10,
valence_min_sim: 0.02,
mmr_lambda: 0.9,
keyword_boost_override: -1.0,
quota_vector: 1.0,
quota_lexical: 1.0,
quota_claims: 1.0,
quota_graph: 1.0,
quota_exploration: 1.0,
novelty_weight: 0.0,
}
}
}
fn env_f64(key: &str, fallback: f64) -> f64 {
std::env::var(key)
.ok()
.and_then(|v| v.parse::<f64>().ok())
.filter(|v| v.is_finite())
.unwrap_or(fallback)
}
impl Tuning {
pub fn from_env() -> Self {
let d = Self::default();
Self {
policy_budget: env_f64("YANTRIKDB_POLICY_BUDGET", d.policy_budget).max(1.0),
pw_freshness: env_f64("YANTRIKDB_PW_FRESHNESS", d.pw_freshness).max(0.0),
pw_importance: env_f64("YANTRIKDB_PW_IMPORTANCE", d.pw_importance).max(0.0),
pw_graph: env_f64("YANTRIKDB_PW_GRAPH", d.pw_graph).max(0.0),
pw_agreement: env_f64("YANTRIKDB_PW_AGREEMENT", d.pw_agreement).max(0.0),
pw_usage: env_f64("YANTRIKDB_PW_USAGE", d.pw_usage).max(0.0),
gate_tau: env_f64("YANTRIKDB_GATE_TAU", d.gate_tau).clamp(0.0, 1.0),
gate_k: env_f64("YANTRIKDB_GATE_K", d.gate_k).max(0.0),
lane_lift_max: env_f64("YANTRIKDB_LANE_LIFT_MAX", d.lane_lift_max).max(0.0),
fts_min_sim: env_f64("YANTRIKDB_FTS_MIN_SIM", d.fts_min_sim),
cold_min_sim: env_f64("YANTRIKDB_COLD_MIN_SIM", d.cold_min_sim),
valence_min_sim: env_f64("YANTRIKDB_VALENCE_MIN_SIM", d.valence_min_sim),
mmr_lambda: env_f64("YANTRIKDB_MMR_LAMBDA", d.mmr_lambda).clamp(0.0, 1.0),
keyword_boost_override: env_f64("YANTRIKDB_KEYWORD_BOOST", d.keyword_boost_override),
quota_vector: env_f64("YANTRIKDB_QUOTA_VECTOR", d.quota_vector).clamp(0.0, 1.0),
quota_lexical: env_f64("YANTRIKDB_QUOTA_LEXICAL", d.quota_lexical).clamp(0.0, 1.0),
quota_claims: env_f64("YANTRIKDB_QUOTA_CLAIMS", d.quota_claims).clamp(0.0, 1.0),
quota_graph: env_f64("YANTRIKDB_QUOTA_GRAPH", d.quota_graph).clamp(0.0, 1.0),
quota_exploration: env_f64("YANTRIKDB_QUOTA_EXPLORATION", d.quota_exploration)
.clamp(0.0, 1.0),
novelty_weight: env_f64("YANTRIKDB_NOVELTY_WEIGHT", d.novelty_weight).clamp(0.0, 1.0),
}
}
#[inline]
pub fn policy_budget_ln(&self) -> f64 {
self.policy_budget.max(1.0).ln()
}
pub fn normalized_weights(&self) -> (f64, f64, f64, f64, f64) {
let sum = self.pw_freshness
+ self.pw_importance
+ self.pw_graph
+ self.pw_agreement
+ self.pw_usage;
let k = if sum > 1.0 { 1.0 / sum } else { 1.0 };
(
self.pw_freshness * k,
self.pw_importance * k,
self.pw_graph * k,
self.pw_agreement * k,
self.pw_usage * k,
)
}
pub fn fingerprint(&self) -> String {
let (f, i, g, a, u) = self.normalized_weights();
format!(
"budget={:.3} w=[f{:.3},i{:.3},g{:.3},a{:.3},u{:.3}] gate=({:.3},{:.1}) \
lane={:.3} floors=[fts{:.3},cold{:.3},val{:.3}] mmr={:.2} nov={:.3}",
self.policy_budget,
f,
i,
g,
a,
u,
self.gate_tau,
self.gate_k,
self.lane_lift_max,
self.fts_min_sim,
self.cold_min_sim,
self.valence_min_sim,
self.mmr_lambda,
self.novelty_weight,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NamespaceProfile {
General,
Code,
Personal,
Reference,
}
impl NamespaceProfile {
pub fn parse(name: &str) -> Self {
match name.trim().to_ascii_lowercase().as_str() {
"code" => Self::Code,
"personal" => Self::Personal,
"reference" => Self::Reference,
_ => Self::General,
}
}
pub fn apply(self, base: &Tuning) -> Tuning {
let mut t = base.clone();
match self {
Self::General => {}
Self::Code => {
t.pw_freshness = 0.10;
t.pw_importance = 0.35;
t.pw_agreement = 0.20;
t.quota_lexical = 0.50;
t.quota_exploration = 0.15;
}
Self::Personal => {
t.pw_freshness = 0.35;
t.pw_importance = 0.35;
t.quota_lexical = 0.25;
t.quota_exploration = 0.35;
}
Self::Reference => {
t.pw_freshness = 0.05;
t.pw_importance = 0.35;
t.pw_agreement = 0.30;
t.quota_exploration = 0.10;
}
}
t
}
}
static TUNING: OnceLock<Tuning> = OnceLock::new();
#[inline]
pub fn tuning() -> &'static Tuning {
TUNING.get_or_init(Tuning::from_env)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_the_shipped_constants() {
let d = Tuning::default();
assert_eq!(d.policy_budget, 1.30);
assert_eq!(d.gate_tau, 0.25);
assert_eq!(d.mmr_lambda, 0.9);
let (f, i, g, a, u) = d.normalized_weights();
assert!(f + i + g + a + u <= 1.0 + 1e-12);
}
#[test]
fn careless_weights_are_renormalized_not_obeyed() {
let t = Tuning {
pw_freshness: 1.0,
pw_importance: 1.0,
pw_graph: 1.0,
pw_agreement: 1.0,
pw_usage: 1.0,
..Tuning::default()
};
let (f, i, g, a, u) = t.normalized_weights();
let sum = f + i + g + a + u;
assert!(
(sum - 1.0).abs() < 1e-12,
"weights must renormalize, got {sum}"
);
}
#[test]
fn a_budget_below_one_cannot_invert_the_multiplier() {
let t = Tuning {
policy_budget: 0.5,
..Tuning::default()
};
assert!(t.policy_budget_ln() >= 0.0);
}
#[test]
fn fingerprint_is_stable_and_descriptive() {
let s = Tuning::default().fingerprint();
assert!(s.contains("budget=1.300"));
assert!(s.contains("gate=(0.250,12.0)"));
}
}
#[cfg(test)]
mod namespace_profile_tests {
use super::*;
#[test]
fn profiles_cannot_buy_a_wider_budget() {
let base = Tuning::default();
for p in [
NamespaceProfile::General,
NamespaceProfile::Code,
NamespaceProfile::Personal,
NamespaceProfile::Reference,
] {
let t = p.apply(&base);
assert_eq!(
t.policy_budget, base.policy_budget,
"{p:?} changed the budget ceiling"
);
let (f, i, g, a, u) = t.normalized_weights();
assert!(
f + i + g + a + u <= 1.0 + 1e-12,
"{p:?} weights escape the partition"
);
}
}
#[test]
fn profiles_cannot_touch_eligibility() {
let base = Tuning::default();
for p in [
NamespaceProfile::Code,
NamespaceProfile::Personal,
NamespaceProfile::Reference,
] {
let t = p.apply(&base);
assert_eq!(t.fts_min_sim, base.fts_min_sim, "{p:?} moved a lane floor");
assert_eq!(
t.cold_min_sim, base.cold_min_sim,
"{p:?} moved a lane floor"
);
assert_eq!(
t.valence_min_sim, base.valence_min_sim,
"{p:?} moved a lane floor"
);
}
}
#[test]
fn profiles_differ_where_they_claim_to() {
let base = Tuning::default();
let code = NamespaceProfile::Code.apply(&base);
let personal = NamespaceProfile::Personal.apply(&base);
assert!(
code.quota_lexical > personal.quota_lexical,
"code should give the lexical lane more slots than personal"
);
assert!(
personal.pw_freshness > code.pw_freshness,
"personal should weight recency more than code"
);
assert_eq!(NamespaceProfile::General.apply(&base), base);
}
#[test]
fn an_unknown_profile_name_falls_back_rather_than_failing() {
assert_eq!(NamespaceProfile::parse("cdoe"), NamespaceProfile::General);
assert_eq!(NamespaceProfile::parse(" CODE "), NamespaceProfile::Code);
}
}