use std::collections::HashSet;
use crate::catalog::{
eval_case_key, eval_case_ordering, eval_golden_uri, normalize_eval_case_source,
parse_golden_cases_document,
};
use crate::error::{AxiomError, Result};
use crate::models::{EvalGoldenAddResult, EvalGoldenMergeReport, EvalQueryCase};
use crate::uri::AxiomUri;
use super::AxiomSync;
impl AxiomSync {
pub fn list_eval_golden_queries(&self) -> Result<Vec<EvalQueryCase>> {
let uri = eval_golden_uri()?;
let raw = match self.fs.read(&uri) {
Ok(raw) => raw,
Err(AxiomError::NotFound(_)) => return Ok(Vec::new()),
Err(err) => return Err(err),
};
let mut cases = parse_golden_cases_document(&raw)?;
for case in &mut cases {
normalize_eval_case_source(case, "golden");
}
cases.sort_by(eval_case_ordering);
Ok(cases)
}
pub fn add_eval_golden_query(
&self,
query: &str,
target_uri: Option<&str>,
expected_top_uri: Option<&str>,
) -> Result<EvalGoldenAddResult> {
let query = query.trim();
if query.is_empty() {
return Err(AxiomError::Validation(
"golden query cannot be empty".to_string(),
));
}
let target_uri = target_uri
.map(AxiomUri::parse)
.transpose()?
.map(|uri| uri.to_string());
let expected_top_uri = expected_top_uri
.map(AxiomUri::parse)
.transpose()?
.map(|uri| uri.to_string());
let mut cases = self.list_eval_golden_queries()?;
let key = (query.to_lowercase(), target_uri.clone());
let added = if let Some(existing) = cases.iter_mut().find(|case| eval_case_key(case) == key)
{
if expected_top_uri.is_some() {
existing.expected_top_uri = expected_top_uri;
}
existing.source_trace_id = "golden-manual".to_string();
existing.source = "golden".to_string();
false
} else {
cases.push(EvalQueryCase {
source_trace_id: "golden-manual".to_string(),
query: query.to_string(),
target_uri,
expected_top_uri,
source: "golden".to_string(),
});
true
};
cases.sort_by(eval_case_ordering);
let golden_uri = self.persist_eval_golden_queries(&cases)?;
Ok(EvalGoldenAddResult {
golden_uri,
added,
count: cases.len(),
})
}
pub fn merge_eval_golden_from_traces(
&self,
trace_limit: usize,
max_add: usize,
) -> Result<EvalGoldenMergeReport> {
let trace_limit = trace_limit.max(1);
let max_add = max_add.max(1);
let mut cases = self.list_eval_golden_queries()?;
let before_count = cases.len();
let mut seen = cases.iter().map(eval_case_key).collect::<HashSet<_>>();
let (trace_cases_raw, _) = self.collect_trace_eval_cases(trace_limit)?;
let mut added_count = 0usize;
for mut case in trace_cases_raw {
if added_count >= max_add {
break;
}
let Some(expected_top_uri) = case.expected_top_uri.as_deref() else {
continue;
};
if !is_merge_seed_expected_uri_acceptable(expected_top_uri) {
continue;
}
case.source = "golden-seed".to_string();
let key = eval_case_key(&case);
if !seen.insert(key) {
continue;
}
cases.push(case);
added_count += 1;
}
cases.sort_by(eval_case_ordering);
let golden_uri = self.persist_eval_golden_queries(&cases)?;
Ok(EvalGoldenMergeReport {
golden_uri,
before_count,
added_count,
after_count: cases.len(),
trace_limit,
max_add,
})
}
}
fn is_merge_seed_expected_uri_acceptable(uri: &str) -> bool {
let Ok(parsed) = AxiomUri::parse(uri) else {
return false;
};
if parsed.scope().is_internal() {
return false;
}
!parsed.is_root()
}