Spaces:
Runtime error
Runtime error
| """Per-section SCOPE FENCES for RICS Home Survey report generation. | |
| Each section is generated in isolation, so without an explicit scope the LLM | |
| "dumps everything it knows" into whichever section it is writing β Section D | |
| becomes a summary of the entire report, defects bleed across elements, and | |
| legal/hazard matters (knotweed, asbestos) land in the wrong place. | |
| A scope fence states, per RICS section code, what is IN scope and what is OUT | |
| of scope (with the correct destination code). The fence is injected into the | |
| section's drafting prompt. This is deterministic, free, and the single most | |
| effective lever against cross-section contamination. | |
| Standard RICS Home Survey numbering is the source of truth (E1 = Chimney | |
| stacks, E2 = Roof coverings, F1 = Roof structure, β¦). | |
| """ | |
| from __future__ import annotations | |
| # Element-level sections share one rule: write ONLY about their own element and | |
| # push everything else to its home section. Section D and the legal/risk | |
| # sections get bespoke fences because they are the worst offenders. | |
| _DESTINATIONS = ( | |
| "Roof coverings/tiles/slate β E2; roof structure/timbers/insulation β F1; " | |
| "chimney/flashing/stack β E1; gutters/downpipes/rainwater β E3; " | |
| "external walls/cracking/cavity β E4; windows β E5; external doors β E6; " | |
| "ceilings β F2; internal walls/partitions β F3; floors β F4; " | |
| "fireplaces/flues β F5; bathroom/kitchen/mould β F8; electrics β G1; " | |
| "gas β G2; water supply β G3; heating/boiler/radiators β G4; " | |
| "drainage/inspection chamber/soil stack β G6; garages/outbuildings β H1/H2; " | |
| "grounds/trees β H3; regulations/listed/conservation/alterations β I1; " | |
| "guarantees β I2; other legal matters β I3; knotweed/asbestos/hazards β I3/J4." | |
| ) | |
| # Bespoke fences for the high-risk sections. | |
| _SCOPE_OVERRIDES: dict[str, str] = { | |
| "D": ( | |
| "IN SCOPE for Section D (ABOUT THE PROPERTY β INTRODUCTION ONLY):\n" | |
| " + Property type and tenure (house/flat/bungalow; freehold/leasehold)\n" | |
| " + Address and date of inspection (only if present in notes)\n" | |
| " + Legal status that frames the whole report (listed building, " | |
| "conservation area) β state prominently, one sentence\n" | |
| " + One sentence on overall character\n" | |
| " + Reference to key alterations (one sentence, NO detail)\n" | |
| "OUT OF SCOPE for Section D (these have their own sections β do NOT " | |
| "describe them here):\n" | |
| " - Roof condition β E2/F1 - Damp β E3/F1 - Wall defects β E4\n" | |
| " - Floor construction β F4 - Services β G1\u2013G8\n" | |
| " - Legal detail β I1\u2013I3 - Grounds β H1\u2013H3\n" | |
| "Write 2\u20133 sentences maximum. If tempted to mention a defect or " | |
| "repair, stop and instead reference the relevant section. " | |
| 'End Section D with exactly: "Full details of each element are ' | |
| 'provided in sections E through K of this report."' | |
| ), | |
| "I1": ( | |
| "IN SCOPE for Section I1 (REGULATIONS): planning permission, building " | |
| "regulations, listed-building/conservation-area constraints, unknown " | |
| "approval status for alterations/conversions.\n" | |
| "OUT OF SCOPE: element condition/defects (E/F/G sections), guarantees " | |
| "(I2). " + _DESTINATIONS | |
| ), | |
| "I3": ( | |
| "IN SCOPE for Section I3 (OTHER LEGAL MATTERS): hazardous materials " | |
| "(asbestos), invasive plants (Japanese knotweed), tree/TPO legal " | |
| "matters, other solicitor referrals.\n" | |
| "OUT OF SCOPE: element condition (E/F/G), regulations detail (I1). " | |
| + _DESTINATIONS | |
| ), | |
| } | |
| _ELEMENT_FENCE_TEMPLATE = ( | |
| "IN SCOPE for Section {code} ({title}): observations, condition, defects " | |
| "and recommendations for {title_lower} ONLY.\n" | |
| "OUT OF SCOPE: anything about a different building element or a legal/" | |
| "hazard matter β do NOT describe it here, it belongs elsewhere. {dest}" | |
| ) | |
| def section_scope_block(code: str, title: str | None = None) -> str: | |
| """Return the scope-fence text for a RICS section code (never raises).""" | |
| c = (code or "").strip().upper() | |
| if c in _SCOPE_OVERRIDES: | |
| fence = _SCOPE_OVERRIDES[c] | |
| else: | |
| t = (title or c).strip() | |
| fence = _ELEMENT_FENCE_TEMPLATE.format( | |
| code=c or "this section", | |
| title=t, | |
| title_lower=t.lower(), | |
| dest=_DESTINATIONS, | |
| ) | |
| return "SECTION SCOPE (mandatory β keep content within this fence):\n" + fence | |
| def is_introduction_section(code: str) -> bool: | |
| """Section D is an introduction; it must never carry element-level detail.""" | |
| return (code or "").strip().upper() == "D" | |