Spaces:
Running
Running
| """Standalone test: decompose inspection notes into issues and write JSON. | |
| Does NOT run SP retrieval or generation — decompose only. | |
| Example: | |
| python scripts/test_sp_decompose_notes.py | |
| python scripts/test_sp_decompose_notes.py --force-llm | |
| python scripts/test_sp_decompose_notes.py --force-llm --section D1 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| _ROOT = Path(__file__).resolve().parents[1] | |
| if str(_ROOT) not in sys.path: | |
| sys.path.insert(0, str(_ROOT)) | |
| from backend.standard_paragraphs.decompose import decompose_notes_detailed | |
| from backend.standard_paragraphs.note_issues_manifest import record_note_issues | |
| TENANT = "khanchaudhry3@gmail.com" | |
| # Surveyor notes provided for standalone decompose testing (per subsection). | |
| CASES = [ | |
| { | |
| "section_id": "D1", | |
| "section_title": "Chimney stacks", | |
| "observations": [ | |
| "Chimney stack: Brick chimney with some repointing required. " | |
| "Flashing is cracked and lead flashing has come away. " | |
| "TV aerial appears to be leaning. " | |
| "Repairs and repointing should be carried out by a competent contractor." | |
| ], | |
| }, | |
| { | |
| "section_id": "D2", | |
| "section_title": "Roof coverings", | |
| "observations": [ | |
| "The roof tiles are new and heavier, internally we noted, shakes and " | |
| "new strutting support has been provided. We noted that underfelt not " | |
| "lapped inside of the gutter. The roof is butterfly roof and could not " | |
| "be inspected as there was no available vantage point. The verges of " | |
| "the roof at the rear is cracked. I noted moss and lichen growth. " | |
| "Roof lights have been installed on the roof. There are solar panels " | |
| "as well." | |
| ], | |
| }, | |
| { | |
| "section_id": "D3", | |
| "section_title": "Rainwater pipes and gutters", | |
| "observations": [ | |
| "Rainwater fittings: UPVC gutters and gullies present. " | |
| "A blockage was noted. The point of discharge is unknown. " | |
| "Front right rainwater downpipe discharges below ground. " | |
| "Some fittings are brittle and faded." | |
| ], | |
| }, | |
| { | |
| "section_id": "D4", | |
| "section_title": "Main walls", | |
| "observations": [ | |
| "There is history of underpinning at the property, which may have loan " | |
| "security implications. I could not ascertain whether the building " | |
| "contains RAAC. Confirm the implications. No hazardous material. " | |
| "There is lintel defect. I noted masonry bees. I noted crack to the " | |
| "front bay, which requires installation of Helifix bars and also " | |
| "resin injection, and also repair to the lintel." | |
| ], | |
| }, | |
| { | |
| "section_id": "D5", | |
| "section_title": "Windows", | |
| "observations": [ | |
| "Windows: Timber sash, double-glazed windows. " | |
| "Some surface rust and deterioration noted. " | |
| "Condensation/missing items also noted." | |
| ], | |
| }, | |
| { | |
| "section_id": "D6", | |
| "section_title": "Outside doors (including patio doors)", | |
| "observations": [ | |
| "External doors: Timber-framed external doors. No weather bar fitted." | |
| ], | |
| }, | |
| { | |
| "section_id": "D7", | |
| "section_title": "Conservatory and porches", | |
| "observations": [ | |
| "Conservatory: May require building regulation approval. " | |
| "Pitched glazed roof with no significant defects noted." | |
| ], | |
| }, | |
| { | |
| "section_id": "D8", | |
| "section_title": "Other joinery and finishes", | |
| "observations": [ | |
| "Surface rust/deterioration to timber boards.", | |
| ], | |
| }, | |
| { | |
| "section_id": "D9", | |
| "section_title": "Other", | |
| "observations": [ | |
| "No contamination noted.", | |
| ], | |
| }, | |
| { | |
| "section_id": "F1", | |
| "section_title": "Electricity", | |
| "observations": [ | |
| "modern electricity.", | |
| ], | |
| }, | |
| { | |
| "section_id": "F2", | |
| "section_title": "Gas/oil", | |
| "observations": [ | |
| "no smell of gas, otherwise okay.", | |
| ], | |
| }, | |
| { | |
| "section_id": "F3", | |
| "section_title": "Water", | |
| "observations": [ | |
| "super, water is lit.", | |
| ], | |
| }, | |
| { | |
| "section_id": "F4", | |
| "section_title": "Heating", | |
| "observations": [ | |
| "combination condensing boiler. The heating was functional, " | |
| "no whipped to radiators.", | |
| ], | |
| }, | |
| { | |
| "section_id": "F5", | |
| "section_title": "Water heating", | |
| "observations": [ | |
| "there was supply of hot water, combination condensing.", | |
| ], | |
| }, | |
| { | |
| "section_id": "F6", | |
| "section_title": "Drainage", | |
| "observations": [ | |
| "drainage, inspection chamber lifted, blockage was noted. " | |
| "Soil and vent stack UPVC, both balloon grating and clean rod.", | |
| ], | |
| }, | |
| { | |
| "section_id": "F7", | |
| "section_title": "Common services", | |
| "observations": [ | |
| "alarm with burglar alarm with alarm box and sensors.", | |
| ], | |
| }, | |
| ] | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description="Test SP note decompose only") | |
| parser.add_argument( | |
| "--force-llm", | |
| action="store_true", | |
| help="Always call the LLM (even for short single-issue notes)", | |
| ) | |
| parser.add_argument( | |
| "--run-id", | |
| default="", | |
| help="Manifest id (default: sp-decompose-YYYYMMDD-HHMMSS)", | |
| ) | |
| parser.add_argument( | |
| "--tenant", | |
| default=TENANT, | |
| help=f"Tenant id (default: {TENANT})", | |
| ) | |
| parser.add_argument( | |
| "--section", | |
| default="", | |
| help="Optional section id filter (e.g. D1). Default: all cases.", | |
| ) | |
| args = parser.parse_args() | |
| run_id = args.run_id.strip() or ( | |
| "sp-decompose-" + datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S") | |
| ) | |
| want = (args.section or "").strip().upper() | |
| cases = [c for c in CASES if not want or c["section_id"].upper() == want] | |
| if not cases: | |
| print(f"No cases match --section={want!r}") | |
| return 1 | |
| print("tenant=", args.tenant) | |
| print("run_id=", run_id) | |
| print("force_llm=", args.force_llm) | |
| print("sections=", [c["section_id"] for c in cases]) | |
| print("(decompose only — not wired into SP generation)") | |
| summary: dict = { | |
| "run_id": run_id, | |
| "tenant_id": args.tenant, | |
| "force_llm": args.force_llm, | |
| "sections": {}, | |
| } | |
| for case in cases: | |
| sid = case["section_id"] | |
| title = case["section_title"] | |
| obs = list(case["observations"]) | |
| result = decompose_notes_detailed( | |
| obs, | |
| section_id=sid, | |
| section_title=title, | |
| force_llm=args.force_llm, | |
| allow_when_disabled=True, | |
| ) | |
| path = record_note_issues( | |
| args.tenant, | |
| run_id, | |
| section_id=sid, | |
| section_title=title, | |
| observations=obs, | |
| issues=list(result.issues), | |
| source="standalone_decompose_test", | |
| used_llm=result.used_llm, | |
| ) | |
| summary["sections"][sid] = { | |
| "section_title": title, | |
| "observations": obs, | |
| "issues": list(result.issues), | |
| "issue_count": len(result.issues), | |
| "used_llm": result.used_llm, | |
| "method": result.method, | |
| "manifest_path": str(path), | |
| } | |
| print(f"\n=== {sid} ({title}) ===") | |
| print("method=", result.method, "used_llm=", result.used_llm) | |
| print("observations:") | |
| for o in obs: | |
| print(" -", o) | |
| print("issues:") | |
| for i, issue in enumerate(result.issues, 1): | |
| print(f" {i}. {issue}") | |
| sample_out = ( | |
| _ROOT | |
| / "backend" | |
| / "standard_paragraphs" | |
| / "samples" | |
| / f"{run_id}.note_issues.json" | |
| ) | |
| sample_out.write_text( | |
| json.dumps(summary, indent=2, ensure_ascii=False), encoding="utf-8" | |
| ) | |
| first = cases[0]["section_id"] | |
| print("\ntenant manifest=", summary["sections"][first]["manifest_path"]) | |
| print("sample copy=", sample_out) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |