use khive_bm25::Bm25Index;
use super::shadow::{log_validation_result, should_sample};
use super::{
PersistError, RetrievalPersistence, ShadowMetrics, ShadowValidationConfig,
ShadowValidationResult,
};
impl RetrievalPersistence {
pub async fn persist_bm25_index(&self, index: &Bm25Index) -> Result<(), PersistError> {
self.persist_snapshot("bm25", index).await
}
pub async fn load_bm25_index(&self) -> Result<Option<Bm25Index>, PersistError> {
let mut index = self.load_snapshot::<Bm25Index>("bm25").await?;
if let Some(ref mut idx) = index {
idx.ensure_doc_lengths_vec();
}
Ok(index)
}
pub async fn persist_bm25_with_validation(
&self,
index: &Bm25Index,
config: &ShadowValidationConfig,
) -> Result<Option<ShadowValidationResult>, PersistError> {
self.persist_bm25_index(index).await?;
if !config.enabled || !should_sample(config.sample_rate) {
return Ok(None);
}
let expected = ShadowMetrics {
item_count: index.doc_count(),
tombstone_count: 0, snapshot_size: 0,
};
let result = self.validate_bm25_snapshot(expected).await;
log_validation_result(&result);
Ok(Some(result))
}
pub(crate) async fn validate_bm25_snapshot(
&self,
expected: ShadowMetrics,
) -> ShadowValidationResult {
let mut result = ShadowValidationResult {
passed: false,
index_type: "bm25".to_string(),
expected: expected.clone(),
actual: None,
discrepancies: Vec::new(),
};
match self.load_bm25_index().await {
Ok(Some(index)) => {
let actual = ShadowMetrics {
item_count: index.doc_count(),
tombstone_count: 0,
snapshot_size: 0,
};
if actual.item_count != expected.item_count {
result.discrepancies.push(format!(
"doc_count mismatch: expected {}, got {}",
expected.item_count, actual.item_count
));
}
result.actual = Some(actual);
result.passed = result.discrepancies.is_empty();
}
Ok(None) => {
result
.discrepancies
.push("index not found after persist".to_string());
}
Err(e) => {
result
.discrepancies
.push(format!("failed to load index: {e}"));
}
}
result
}
}