Spaces:
Runtime error
Runtime error
| """Shared test fixtures. | |
| Tests run fully offline: the FAISS store is backed by a deterministic fake | |
| embedder and no OpenAI key is configured, so mapping/grounding take their | |
| deterministic fallback paths. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| from pathlib import Path | |
| import pytest | |
| from docx import Document | |
| from backend.config import settings | |
| from backend.core import embeddings, notes_parser, pii_scrubber, rag_store | |
| from backend.core.rics_canonical_l3 import build_section_anchor_text, iter_canonical_leaf_sections | |
| _ANCHOR_OVERLAP_STOP = frozenset({ | |
| "about", "elements", "other", "property", "section", "inside", "outside", | |
| "survey", "rics", "level", "typical", "matters", "engagement", "terms", | |
| "looked", "overall", "fine", "noted", "specific", "defects", "general", | |
| "inspection", "opinion", "declaration", "diagram", "recommendations", | |
| "the", "and", "not", "with", "for", "are", "was", "from", "this", "that", | |
| "have", "has", "were", "its", "any", "all", "may", "can", "your", "our", | |
| }) | |
| class AnchorSemanticFakeEmbedder(embeddings.FakeEmbedder): | |
| """Deterministic embedder that approximates anchor similarity for offline tests.""" | |
| def __init__(self, dim: int = 384) -> None: | |
| super().__init__(dim) | |
| self._anchor_texts: dict[str, str] = {} | |
| self._anchor_vecs: dict[str, list[float]] = {} | |
| def register_anchors(self, anchors: dict[str, str]) -> None: | |
| self._anchor_texts = dict(anchors) | |
| self._anchor_vecs = {sid: super()._vec(text) for sid, text in anchors.items()} | |
| def _token_set(text: str) -> set[str]: | |
| return { | |
| w | |
| for w in re.findall(r"[a-z0-9]{3,}", (text or "").lower()) | |
| if w not in _ANCHOR_OVERLAP_STOP | |
| } | |
| def _vec(self, text: str) -> list[float]: | |
| base = super()._vec(text) | |
| if not self._anchor_texts: | |
| return base | |
| words = self._token_set(text) | |
| best_sid: str | None = None | |
| best_overlap = 0 | |
| for sid, anchor in self._anchor_texts.items(): | |
| overlap = len(words & self._token_set(anchor)) | |
| if overlap > best_overlap: | |
| best_overlap = overlap | |
| best_sid = sid | |
| if best_sid and best_overlap > 0: | |
| anchor_vec = self._anchor_vecs[best_sid] | |
| alpha = min(0.95, 0.55 + 0.12 * best_overlap) | |
| blended = [alpha * a + (1.0 - alpha) * b for a, b in zip(anchor_vec, base)] | |
| norm = sum(x * x for x in blended) ** 0.5 or 1.0 | |
| return [x / norm for x in blended] | |
| return base | |
| def isolate(tmp_path, monkeypatch): | |
| monkeypatch.setattr(settings, "data_dir", str(tmp_path / "data")) | |
| monkeypatch.setattr(settings, "openai_api_key", "") | |
| monkeypatch.setattr(settings, "pii_use_spacy", False) | |
| monkeypatch.setattr(settings, "reference_auto_ingest_enabled", False) | |
| fake = AnchorSemanticFakeEmbedder() | |
| anchors = { | |
| sid: build_section_anchor_text(sid) | |
| for sid, _, _, _ in iter_canonical_leaf_sections() | |
| } | |
| fake.register_anchors(anchors) | |
| monkeypatch.setattr(embeddings, "get_embedder", lambda: fake) | |
| monkeypatch.setattr(rag_store, "get_embedder", lambda: fake) | |
| embeddings.reset_embedder() | |
| rag_store.reset_rag_store() | |
| pii_scrubber.reset_nlp_cache() | |
| notes_parser.reset_section_anchors() | |
| notes_parser.initialize_section_anchors() | |
| yield | |
| notes_parser.reset_section_anchors() | |
| rag_store.reset_rag_store() | |
| def _write_report_template(path: Path) -> Path: | |
| """Minimal report template: section structure only.""" | |
| doc = Document() | |
| doc.add_paragraph("A About the inspection") | |
| doc.add_paragraph("E1 Chimney Stacks") | |
| doc.add_paragraph("E2 Roof Coverings") | |
| doc.add_paragraph("G4 Central Heating") | |
| doc.save(str(path)) | |
| return path | |
| def _write_standard_paragraphs(path: Path, *, with_rating: bool = False) -> Path: | |
| """Standard paragraphs Word master: headings + boilerplate body.""" | |
| doc = Document() | |
| doc.add_paragraph("Section E1 - Chimney Stacks") | |
| body = "The chimney stacks were inspected from ground level using binoculars." | |
| if with_rating: | |
| body += " Condition Rating 2 where defects require attention." | |
| doc.add_paragraph(body) | |
| doc.add_paragraph("Section E2 - Roof Coverings") | |
| doc.add_paragraph("The roof covering comprises slate laid to pitched timber rafters.") | |
| doc.add_paragraph("Section G4 - Central Heating") | |
| doc.add_paragraph("The property is served by a gas-fired central heating system.") | |
| doc.save(str(path)) | |
| return path | |
| def report_template_docx(tmp_path) -> Path: | |
| return _write_report_template(tmp_path / "report_template.docx") | |
| def standard_paragraphs_docx(tmp_path) -> Path: | |
| return _write_standard_paragraphs(tmp_path / "standard_paragraphs.docx") | |
| def standard_paragraphs_rated_docx(tmp_path) -> Path: | |
| return _write_standard_paragraphs(tmp_path / "standard_paragraphs_rated.docx", with_rating=True) | |
| # Legacy alias used by template_discoverer unit tests. | |
| def master_docx(standard_paragraphs_docx) -> Path: | |
| return standard_paragraphs_docx | |
| def master_docx_rated(standard_paragraphs_rated_docx) -> Path: | |
| return standard_paragraphs_rated_docx | |
| def client(configured_master): | |
| from backend.main import app | |
| from fastapi.testclient import TestClient | |
| with TestClient(app) as c: | |
| yield c | |
| def _write_past_report(path: Path) -> Path: | |
| """Simulated uploaded past report for REFERENCE-tier generation.""" | |
| doc = Document() | |
| doc.add_paragraph("Section E1 - Chimney Stacks") | |
| doc.add_paragraph( | |
| "The chimney stacks were inspected from ground level using binoculars. " | |
| "Inspection was limited to visible external surfaces at the time of survey." | |
| ) | |
| doc.add_paragraph("Section E2 - Roof Coverings") | |
| doc.add_paragraph( | |
| "The roof coverings were inspected from ground level using binoculars and, " | |
| "where accessible, from within the roof void. The main roof covering " | |
| "comprises natural slate tiles laid to pitched timber rafters with underfelt " | |
| "and counter battens." | |
| ) | |
| doc.add_paragraph("Section G4 - Central Heating") | |
| doc.add_paragraph( | |
| "The property was served by a gas-fired central heating system with " | |
| "radiators to most rooms." | |
| ) | |
| doc.save(str(path)) | |
| return path | |
| def configured_master(monkeypatch, report_template_docx, standard_paragraphs_docx) -> Path: | |
| """Point config at a split report template + standard paragraphs bundle. | |
| This fixture models an operator that explicitly configures AND enables a shared | |
| master, so it opts into ``master_template_auto_ingest`` (default-off in prod). | |
| """ | |
| folder = report_template_docx.parent | |
| _write_past_report(folder / "past_report_sample.docx") | |
| monkeypatch.setattr(settings, "master_template_dir", str(folder)) | |
| monkeypatch.setattr(settings, "report_template_filename", report_template_docx.name) | |
| monkeypatch.setattr(settings, "standard_paragraphs_filename", standard_paragraphs_docx.name) | |
| monkeypatch.setattr(settings, "master_template_filename", standard_paragraphs_docx.name) | |
| monkeypatch.setattr(settings, "master_template_auto_ingest", True) | |
| monkeypatch.setattr(settings, "reference_auto_ingest_enabled", True) | |
| return standard_paragraphs_docx | |