Spaces:
Sleeping
Sleeping
File size: 6,211 Bytes
a671976 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | """Discovered template schema models.
The schema is the structural authority for a tenant. It is produced by
``core.template_discoverer`` from the operator master template and persisted as
``schema.json``. Nothing in the pipeline hardcodes RICS section codes; everything
flows from this schema.
"""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, Field, computed_field
class RatingValue(BaseModel):
"""One allowed value of a rating/condition system."""
value: str = Field(
description="Canonical rating token, e.g. '1', '2', '3' or 'NI'."
)
label: str = Field(
default="", description="Human label, e.g. 'No repair currently needed'."
)
colour: str = Field(
default="", description="Optional display colour hint (e.g. 'green')."
)
@property
def meaning(self) -> str | None:
return self.label or None
class RatingSystem(BaseModel):
"""Rating system discovered in the master, if any.
``detected`` gates all rating behaviour downstream: if the template defines no
rating system, the pipeline must never emit ratings.
"""
detected: bool = Field(default=False)
name: str = Field(
default="", description="Discovered rating system label from the template."
)
type: str | None = Field(
default=None,
description="numeric | letter | text | boolean — None if absent.",
)
values: list[RatingValue] = Field(default_factory=list)
format_template: str | None = Field(
default=None,
description="How the rating appears in template text, e.g. 'Rating [VALUE]'.",
)
inline_example: str | None = Field(
default=None,
description="Verbatim rating example from the template.",
)
class PlaceholderSyntax(BaseModel):
"""How blanks/options are marked in the master so notes can be slotted in."""
free_text_markers: list[str] = Field(
default_factory=lambda: ["<text>"],
description="Tokens denoting a free-text blank, e.g. '<text>'.",
)
option_pattern: str = Field(
default=r"\(([^()]+)\)",
description="Regex matching an inline option group, e.g. '(pitched)(flat)'.",
)
variant_pattern: str = Field(
default=r"\[([A-Z])\]",
description="Regex matching a variant tag, e.g. '[A]' / '[B]'.",
)
detected_formats: list[str] = Field(default_factory=list)
primary_format: str | None = Field(default=None)
class SectionDefinition(BaseModel):
"""One section discovered in the master template."""
id: str = Field(
description="Stable section id (discovered code or slug), e.g. 'E1'."
)
title: str = Field(description="Section heading text.")
order: int = Field(description="Zero-based order within the report.")
level: int = Field(default=1, description="Heading depth (1 = top level).")
parent_id: str | None = Field(default=None)
has_rating_field: bool = Field(
default=False,
description="Whether this section carries a rating in the master.",
)
rating_inline_format: str | None = Field(
default=None,
description="Verbatim rating format for this section, if any.",
)
keywords: list[str] = Field(
default_factory=list,
description="Terms used to route free-text notes to this section.",
)
placeholder_hints: list[str] = Field(default_factory=list)
@computed_field # type: ignore[prop-decorator]
@property
def label(self) -> str:
"""Alias for ``title`` (spec compatibility)."""
return self.title
@property
def keyword_set(self) -> set[str]:
return {k.lower() for k in self.keywords if k.strip()}
class TemplateSchema(BaseModel):
"""Full discovered schema for a tenant's report template."""
tenant_id: str = Field(default="", description="Owning tenant id.")
version: int = Field(default=1)
source_filename: str = Field(
default="", description="Primary source file (report template)."
)
report_type: str | None = Field(
default=None, description="Detected report type name."
)
extracted_at: str | None = Field(
default=None, description="ISO timestamp of discovery."
)
section_hierarchy: str = Field(
default="flat",
description="flat | two-level | three-level",
)
report_template_source: str = Field(
default="",
description="PDF report template used for section structure discovery.",
)
standard_paragraphs_source: str = Field(
default="",
description="Word file used for approved standard paragraph wording.",
)
section_alias_map: dict[str, str] = Field(
default_factory=dict,
description=(
"Maps report-template section id -> standard-paragraph section id "
"(e.g. PDF D1 -> Word E1 for Chimney stacks)."
),
)
paragraph_section_titles: dict[str, str] = Field(
default_factory=dict,
description="Standard-paragraph section id -> title from the Word master.",
)
raw_section_texts: dict[str, str] = Field(
default_factory=dict,
description="section_id -> template body text from report template.",
)
rating_system: RatingSystem = Field(default_factory=RatingSystem)
placeholders: PlaceholderSyntax = Field(default_factory=PlaceholderSyntax)
sections: list[SectionDefinition] = Field(default_factory=list)
additional_metadata: dict[str, Any] = Field(default_factory=dict)
def ordered_sections(self) -> list[SectionDefinition]:
return sorted(self.sections, key=lambda s: s.order)
def section_ids(self) -> list[str]:
return [s.id for s in self.ordered_sections()]
def get_section(self, section_id: str) -> SectionDefinition | None:
for s in self.sections:
if s.id == section_id:
return s
return None
def paragraph_section_id(self, report_section_id: str) -> str:
"""Resolve the Word MASTER bucket id for a report-template section."""
return self.section_alias_map.get(report_section_id, report_section_id)
|