Skip to content

Commit 94606e1

Browse files
committed
feat(search): integrate temporal lane with candidate injection and 5-lane RRF
1 parent 65a24de commit 94606e1

1 file changed

Lines changed: 90 additions & 4 deletions

File tree

src/search.rs

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,37 @@ pub fn search_with_intelligence(
201201
let fts_results = dedup_by_file(all_fts);
202202

203203
// --- Graph lane from combined seeds ---
204-
let combined_seeds = merge_seeds(&semantic_results, &fts_results);
204+
let mut combined_seeds = merge_seeds(&semantic_results, &fts_results);
205+
206+
// Inject temporal candidates as graph seeds when date_range is present
207+
let temporal_seeds: Vec<RankedResult> = if let Some(range) = &orchestration.date_range {
208+
config
209+
.store
210+
.get_files_in_date_range(range.0, range.1)
211+
.unwrap_or_default()
212+
.iter()
213+
.map(|f| RankedResult {
214+
file_path: f.path.clone(),
215+
file_id: f.id,
216+
score: 1.0,
217+
heading: None,
218+
snippet: String::new(),
219+
docid: f.docid.clone(),
220+
})
221+
.collect()
222+
} else {
223+
vec![]
224+
};
225+
for ts in &temporal_seeds {
226+
let dominated = combined_seeds
227+
.iter()
228+
.any(|s| s.file_path == ts.file_path && s.score >= ts.score);
229+
if !dominated {
230+
combined_seeds.retain(|s| s.file_path != ts.file_path);
231+
combined_seeds.push(ts.clone());
232+
}
233+
}
234+
205235
let graph_results =
206236
graph::graph_expand(config.store, &combined_seeds, query, 2, 20).unwrap_or_default();
207237

@@ -217,8 +247,8 @@ pub fn search_with_intelligence(
217247
);
218248

219249
// --- Step 4: Reranker (4th lane) if available ---
220-
let final_fused = if let Some(reranker) = &mut config.reranker {
221-
let mut rerank_results: Vec<RankedResult> = Vec::new();
250+
let mut rerank_results: Vec<RankedResult> = Vec::new();
251+
let reranker_used = if let Some(reranker) = &mut config.reranker {
222252
for candidate in fused_pass1.iter().take(config.rerank_candidates) {
223253
let score = reranker
224254
.rerank_score(query, &candidate.snippet)
@@ -237,8 +267,63 @@ pub fn search_with_intelligence(
237267
.partial_cmp(&a.score)
238268
.unwrap_or(std::cmp::Ordering::Equal)
239269
});
270+
true
271+
} else {
272+
false
273+
};
274+
275+
// --- Step 5: Temporal lane (5th lane) when date_range is present ---
276+
let final_fused = if let Some(range) = &orchestration.date_range {
277+
// Build temporal lane: score ALL candidates from pass1/reranked by date proximity
278+
let base_fused = if reranker_used {
279+
fusion::rrf_fuse(
280+
&[
281+
("semantic", &semantic_results, weights.semantic),
282+
("fts", &fts_results, weights.fts),
283+
("graph", &graph_results, weights.graph),
284+
("rerank", &rerank_results, weights.rerank),
285+
],
286+
RRF_K,
287+
)
288+
} else {
289+
// Use pass1 as the candidate source; avoid clone by re-referencing
290+
fused_pass1
291+
};
292+
let mut temporal_results: Vec<RankedResult> = base_fused
293+
.iter()
294+
.filter_map(|c| {
295+
let file = config.store.get_file(&c.file_path).ok()??;
296+
let nd = file.note_date?;
297+
let score = crate::temporal::temporal_score(nd, range.0, range.1);
298+
Some(RankedResult {
299+
file_path: c.file_path.clone(),
300+
file_id: c.file_id,
301+
score,
302+
heading: c.heading.clone(),
303+
snippet: c.snippet.clone(),
304+
docid: c.docid.clone(),
305+
})
306+
})
307+
.collect();
308+
temporal_results.sort_by(|a, b| {
309+
b.score
310+
.partial_cmp(&a.score)
311+
.unwrap_or(std::cmp::Ordering::Equal)
312+
});
240313

241-
// RRF Pass 2 (4-lane)
314+
// 5-lane RRF (rerank_results is empty when reranker absent, weight 0)
315+
fusion::rrf_fuse(
316+
&[
317+
("semantic", &semantic_results, weights.semantic),
318+
("fts", &fts_results, weights.fts),
319+
("graph", &graph_results, weights.graph),
320+
("rerank", &rerank_results, weights.rerank),
321+
("temporal", &temporal_results, weights.temporal),
322+
],
323+
RRF_K,
324+
)
325+
} else if reranker_used {
326+
// Non-temporal with reranker: 4-lane (existing behavior)
242327
fusion::rrf_fuse(
243328
&[
244329
("semantic", &semantic_results, weights.semantic),
@@ -249,6 +334,7 @@ pub fn search_with_intelligence(
249334
RRF_K,
250335
)
251336
} else {
337+
// Non-temporal without reranker: 3-lane (existing behavior)
252338
fused_pass1
253339
};
254340

0 commit comments

Comments
 (0)