use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::indexing::sparse::Bm25Index;
use crate::types::Chunk;
pub fn make_chunk_id(indexed_path: &str, slot: usize) -> String {
format!("{indexed_path}:{slot}")
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileManifestEntry {
pub hash: String,
pub start: usize,
pub count: usize,
}
impl FileManifestEntry {
pub fn end(&self) -> usize {
self.start.saturating_add(self.count)
}
}
pub type FileManifest = BTreeMap<String, FileManifestEntry>;
#[derive(Debug)]
pub struct PreviousIndex {
pub chunks: Vec<Chunk>,
pub vectors: Vec<Vec<f32>>,
pub files: FileManifest,
pub bm25_index: Bm25Index,
}
impl PreviousIndex {
pub fn try_new(
chunks: Vec<Chunk>,
vectors: Vec<Vec<f32>>,
files: FileManifest,
bm25_index: Bm25Index,
) -> Result<Self, String> {
let chunk_count = chunks.len();
if chunk_count != vectors.len() || chunk_count != bm25_index.doc_order().len() {
return Err("Persisted index components have inconsistent document counts".into());
}
if files.is_empty() {
return Err("Persisted index has no file manifest".into());
}
let mut entries: Vec<(&String, &FileManifestEntry)> = files.iter().collect();
entries.sort_by_key(|(_, entry)| (entry.start, entry.count));
let mut expected_ids: Vec<String> = Vec::with_capacity(chunk_count);
let mut next_start = 0usize;
for (indexed_path, entry) in entries {
if entry.start != next_start || entry.end() > chunk_count {
return Err(format!(
"File manifest entry for {indexed_path} does not tile the chunk list"
));
}
if chunks[entry.start..entry.end()]
.iter()
.any(|chunk| chunk.file_path != *indexed_path)
{
return Err(format!(
"Chunks in the range recorded for {indexed_path} belong to another file"
));
}
expected_ids.extend((0..entry.count).map(|slot| make_chunk_id(indexed_path, slot)));
next_start = entry.end();
}
if next_start != chunk_count {
return Err("File manifest does not cover every chunk".into());
}
if bm25_index.doc_order() != expected_ids.as_slice() {
return Err("BM25 document order does not match the file manifest".into());
}
Ok(Self {
chunks,
vectors,
files,
bm25_index,
})
}
}