from __future__ import annotations import json import re from pathlib import Path from typing import Any from app.settings import Settings TOKEN_PATTERN = re.compile(r"[a-zA-Z0-9_]+") def tokens(value: str) -> set[str]: return {token.lower() for token in TOKEN_PATTERN.findall(value)} class CdslLibrary: def __init__(self, settings: Settings) -> None: self.settings = settings self.index_path = settings.library_root / "index" / "catalog.json" def _records(self) -> list[dict[str, Any]]: if not self.index_path.is_file(): return [] payload = json.loads(self.index_path.read_text(encoding="utf-8")) return payload.get("samples", []) def count(self) -> int: return len(self._records()) def search(self, query: str, limit: int = 5) -> list[dict[str, Any]]: query_tokens = tokens(query) if not query_tokens: return [ { "part_id": record["part_id"], "profiles": record.get("profiles", []), "features": record.get("features", []), "summary": record.get("summary", ""), } for record in self._records()[:limit] ] scored: list[tuple[int, dict[str, Any]]] = [] for record in self._records(): corpus = " ".join([ record.get("part_id", ""), record.get("source_name", ""), " ".join(record.get("profiles", [])), " ".join(record.get("features", [])), " ".join(record.get("parameters", [])), ]) score = len(query_tokens & tokens(corpus)) if score: scored.append((score, record)) scored.sort(key=lambda item: (-item[0], item[1]["part_id"])) return [ { "part_id": record["part_id"], "profiles": record.get("profiles", []), "features": record.get("features", []), "summary": record.get("summary", ""), } for _, record in scored[:limit] ] def read_sample(self, part_id: str) -> dict[str, Any]: for record in self._records(): if record.get("part_id") == part_id: source = self.settings.library_root / "samples" / part_id / "model.cdsl.json" if not source.is_file(): break return json.loads(source.read_text(encoding="utf-8")) raise ValueError(f"CDSL sample not found: {part_id}")