From f4a6125aebedccf199555a65a91f29c4df4a2ce9 Mon Sep 17 00:00:00 2001 From: Romonaga Date: Sat, 13 Jun 2026 13:32:45 -0400 Subject: [PATCH] Stabilize Project Finder relevance and loading state --- db/lab_inventory.py | 30 +++++++++++++++++-- .../components/ProjectCandidateFilters.tsx | 4 +++ frontend/src/components/ProjectFinderView.tsx | 4 ++- frontend/src/styles.css | 6 ++++ tests/test_lab_inventory.py | 22 ++++++++++++++ 5 files changed, 62 insertions(+), 4 deletions(-) diff --git a/db/lab_inventory.py b/db/lab_inventory.py index 117db23..42026df 100644 --- a/db/lab_inventory.py +++ b/db/lab_inventory.py @@ -31,6 +31,7 @@ CODE_SAMPLE_TITLE_RE = re.compile(r"\bcode\s+sample\b", re.IGNORECASE) MIN_PROJECT_TEXT_CHARS = 90 PROJECT_FINDER_CHUNK_SOURCE_LIMIT = 5000 +PROJECT_FINDER_CHUNK_PREFILTER_LIMIT = 20000 PROJECT_FINDER_INTELLIGENCE_SOURCE_LIMIT = 400 GENERIC_PART_PATTERNS: tuple[tuple[re.Pattern[str], str, str], ...] = ( @@ -280,7 +281,7 @@ def find( with self.database.connection() as conn: chunk_rows = conn.execute( load_query("project_finder_chunk_candidates.sql"), - (self._term_regex(terms), PROJECT_FINDER_CHUNK_SOURCE_LIMIT), + (self._term_regex(terms), PROJECT_FINDER_CHUNK_PREFILTER_LIMIT), ).fetchall() intelligence_rows = conn.execute( load_query("project_finder_intelligence_candidates.sql"), @@ -288,7 +289,9 @@ def find( ).fetchall() inventory_index = self._inventory_index(inventory, term_rows) - chunk_rows = self._annotate_chunk_matches(chunk_rows, self._prepared_search_terms(terms)) + chunk_rows = self._rank_chunk_rows( + self._annotate_chunk_matches(chunk_rows, self._prepared_search_terms(terms)) + )[:PROJECT_FINDER_CHUNK_SOURCE_LIMIT] candidates = [ self._chunk_candidate(row, inventory_index) for row in chunk_rows @@ -406,12 +409,33 @@ def _annotate_chunk_matches(self, rows: list[dict[str, Any]], terms: list[tuple[ annotated.append(payload) return annotated + @staticmethod + def _rank_chunk_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: + return sorted( + rows, + key=lambda row: ( + int(row.get("matched_count") or 0), + float(row.get("quality_score") or 0.0), + str(row.get("source_path") or ""), + -(int(row.get("page_number") or 0)), + -(int(row.get("chunk_index") or 0)), + ), + reverse=True, + ) + def _matched_terms_for_text(self, text: str, terms: list[tuple[str, str]]) -> list[str]: normalized_text = normalize_part_name(text) + text_tokens = set(normalized_text.split()) compact_text = compact_part_key(text) matched = [] for normalized_term, compact_term in terms: - if normalized_term in normalized_text or (compact_term and compact_term in compact_text): + term_tokens = normalized_term.split() + if len(term_tokens) == 1: + text_match = term_tokens[0] in text_tokens + else: + text_match = normalized_term in normalized_text + compact_match = bool(compact_term and len(compact_term) >= 4 and any(char.isdigit() for char in compact_term) and compact_term in compact_text) + if text_match or compact_match: matched.append(normalized_term) return matched diff --git a/frontend/src/components/ProjectCandidateFilters.tsx b/frontend/src/components/ProjectCandidateFilters.tsx index c0bb72d..d754fca 100644 --- a/frontend/src/components/ProjectCandidateFilters.tsx +++ b/frontend/src/components/ProjectCandidateFilters.tsx @@ -1,4 +1,5 @@ import type { ProjectCandidateFilter } from "../types"; +import { LoadingSpinner } from "./LoadingSpinner"; const filters: Array<{ id: ProjectCandidateFilter; label: string }> = [ { id: "all", label: "All" }, @@ -9,10 +10,12 @@ const filters: Array<{ id: ProjectCandidateFilter; label: string }> = [ export function ProjectCandidateFilters({ active, counts, + loading, onChange }: { active: ProjectCandidateFilter; counts: Record; + loading?: boolean; onChange: (filter: ProjectCandidateFilter) => void; }) { return ( @@ -25,6 +28,7 @@ export function ProjectCandidateFilters({ onClick={() => onChange(filter.id)} > {filter.label} + {loading && active === filter.id ? : null} {counts[filter.id]} ))} diff --git a/frontend/src/components/ProjectFinderView.tsx b/frontend/src/components/ProjectFinderView.tsx index ff9d2b4..c23ecec 100644 --- a/frontend/src/components/ProjectFinderView.tsx +++ b/frontend/src/components/ProjectFinderView.tsx @@ -121,7 +121,9 @@ export function ProjectFinderView({ title="Build candidates" description={`${formatNumber(candidates.length)} visible of ${formatNumber(activeFilterTotal)} ${filterDescription(candidateFilter)} matches`} /> - {hasFinderRun ? : null} + {hasFinderRun ? ( + + ) : null}