"""Assemble a generated report into output formats (JSON preview + DOCX).""" from __future__ import annotations from backend.config import settings from backend.core.compat_adapter import payload_to_generated_section from backend.models.report import GeneratedSection, ReportResult from backend.models.schema import TemplateSchema from backend.utils import docx_builder _REVIEW_STATUSES = frozenset( {"NO_RAG_MATCH", "GROUNDING_REVIEW", "UNASSIGNED", "NOTES_ONLY"} ) def to_preview(result: ReportResult, schema: TemplateSchema) -> dict: """Schema-ordered JSON preview for the UI.""" sections_out = [] for s in sorted( result.sections, key=lambda s: schema.section_ids().index(s.section_id) if s.section_id in schema.section_ids() else 1_000_000, ): sections_out.append({ "section_id": s.section_id, "title": s.title, "text": s.text, "paragraph": s.text, "rating_value": s.rating_value, "status": s.status, "notes": s.notes, "rag_sources": s.rag_sources, "reference_sources": [rs.model_dump() for rs in s.reference_sources], "grounding_passed": s.grounding_passed, "unmatched_observations": s.unmatched_observations, }) mapped_ok = sum(1 for s in result.sections if s.status == "OK") needing_review = sum(1 for s in result.sections if s.status in _REVIEW_STATUSES) total = len(result.sections) or 1 review_ratio = needing_review / total manual_review_required = review_ratio > settings.grounding_alert_threshold return { "tenant_id": result.tenant_id, "schema_version": result.schema_version, "schema_report_type": schema.report_type, "rating_system": schema.rating_system.model_dump(), "sections_mapped": mapped_ok, "sections_needing_review": needing_review, "review_ratio": round(review_ratio, 4), "manual_review_required": manual_review_required, "sections": sections_out, "unassigned_text": result.unassigned_text, "property_type": result.property_type, "tenure": result.tenure, } def from_sections_payload( tenant_id: str, schema: TemplateSchema, sections_payload: dict[str, dict], *, property_type: str = "", tenure: str = "", unassigned_text: str = "", ) -> ReportResult: """Rebuild a ReportResult from persisted section payloads (legacy session or preview).""" sections: list[GeneratedSection] = [] for sec in schema.ordered_sections(): payload = sections_payload.get(sec.id) if not payload or not (payload.get("text") or "").strip(): continue sections.append( payload_to_generated_section(sec.id, sec.title, payload, schema) ) return ReportResult( tenant_id=tenant_id, schema_version=schema.version, property_type=property_type, tenure=tenure, sections=sections, unassigned_text=unassigned_text, ) def to_docx( result: ReportResult, schema: TemplateSchema, *, title: str = "Survey Report", include_footer: bool = False, template_docx_path: str | None = None, section_photo_paths: dict[str, list[str]] | None = None, ) -> bytes: path = template_docx_path if path is None and settings.branded_template_path is not None: path = str(settings.branded_template_path) return docx_builder.build_docx( result, schema, title=title, include_footer=include_footer, template_docx_path=path, section_photo_paths=section_photo_paths, )