""" code_refs.py - Pluggable code/standard reference retriever for Stage 7. Loads a local corpus of code/standard text and returns the excerpts most relevant to the active review paths and the drawing's subject matter. Kept dependency-free (keyword scoring, no embeddings) so it ships today; the retrieve() interface is the seam to swap in a vector store later. Corpus layout: drop plain-text files in backend/code_corpus/. The standard is inferred from the filename prefix (ada*.txt -> ADA, tas*.txt -> TAS, ibc*.txt -> IBC, ifc*.txt -> IFC, iebc*.txt -> IEBC, energy*.txt -> ENERGY). Each file is chunked on blank lines; a leading " ..." token on a chunk is captured as its section id. An empty corpus is fine -- Stage 7 then runs reasoning-only and cites nothing. """ import os import re from typing import Dict, List _CORPUS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "code_corpus") # review_paths flag -> standard tag the corpus uses. PATH_TO_STANDARD = { "ada_review": "ADA", "tas_tdlr_review": "TAS", "ibc_review": "IBC", "iebc_review": "IEBC", "fire_code_review": "IFC", "energy_review": "ENERGY", } _FILE_PREFIX_TO_STANDARD = [ ("ada", "ADA"), ("tas", "TAS"), ("iebc", "IEBC"), ("ibc", "IBC"), ("ifc", "IFC"), ("energy", "ENERGY"), ] _SECTION_RE = re.compile(r"^\s*(\d+[\d.]*\b)") _WORD_RE = re.compile(r"[a-z]{4,}") _corpus_cache: List[Dict] = None # type: ignore def _standard_for_file(name: str) -> str: stem = name.lower() for prefix, std in _FILE_PREFIX_TO_STANDARD: if stem.startswith(prefix): return std return stem.split("_")[0].split(".")[0].upper() def load_corpus() -> List[Dict]: """Load and chunk the corpus once. Each chunk: {standard, section, text}.""" global _corpus_cache if _corpus_cache is not None: return _corpus_cache chunks: List[Dict] = [] if os.path.isdir(_CORPUS_DIR): for fname in sorted(os.listdir(_CORPUS_DIR)): if not fname.lower().endswith(".txt"): continue std = _standard_for_file(fname) with open(os.path.join(_CORPUS_DIR, fname), encoding="utf-8") as f: raw = f.read() for para in re.split(r"\n\s*\n", raw): text = para.strip() if len(text) < 20: continue m = _SECTION_RE.match(text) chunks.append({ "standard": std, "section": m.group(1) if m else None, "text": text, }) _corpus_cache = chunks return chunks def _query_terms(assertions: List[Dict]) -> set: blob = " ".join( f"{a.get('attribute','')} {a.get('value','')} {a.get('source_text','')}" for a in assertions ).lower() return set(_WORD_RE.findall(blob)) def retrieve(review_paths: Dict, assertions: List[Dict], k: int = 24) -> List[Dict]: """Top-k corpus chunks for the active standards, scored by term overlap.""" corpus = load_corpus() if not corpus: return [] active = {std for flag, std in PATH_TO_STANDARD.items() if (review_paths or {}).get(flag)} # If the profile is empty/unknown, fall back to accessibility standards. pool = [c for c in corpus if not active or c["standard"] in active] if not pool: pool = corpus terms = _query_terms(assertions) scored = [] for c in pool: ctext = c["text"].lower() score = sum(1 for t in terms if t in ctext) if score: scored.append((score, c)) scored.sort(key=lambda sc: sc[0], reverse=True) return [c for _, c in scored[:k]] def format_excerpts(excerpts: List[Dict]) -> str: """Render retrieved excerpts for the prompt.""" if not excerpts: return "(no code excerpts available; do not cite section numbers)" out = [] for e in excerpts: sec = f" {e['section']}" if e.get("section") else "" out.append(f"[{e['standard']}{sec}] {e['text']}") return "\n\n".join(out)