"""Persisted in-memory report jobs for legacy UI compatibility.""" from __future__ import annotations import json import threading import time import uuid from dataclasses import asdict, dataclass, field from pathlib import Path from typing import Any from backend.utils import tenant_store _lock = threading.RLock() @dataclass class UploadedDocument: document_id: str filename: str status: str = "complete" error: str | None = None ingested_chunks: int = 0 storage_path: str = "" file_size: int = 0 created_at: float = field(default_factory=time.time) @dataclass class ReportSession: report_id: str tenant_id: str draft_id: str survey_level: int = 3 primary_document_id: str = "" document_ids: list[str] = field(default_factory=list) status: str = "idle" error_message: str | None = None property_type: str = "semi-detached house" tenure: str = "freehold" generation_section_total: int | None = None generation_started_at: float | None = None sections_payload: dict[str, dict[str, Any]] = field(default_factory=dict) interference_level: str = "medium" created_at: float = field(default_factory=time.time) updated_at: float = field(default_factory=time.time) def _documents_path(tenant_id: str) -> Path: return tenant_store.tenant_root(tenant_id) / "compat_documents.json" def _reports_dir(tenant_id: str) -> Path: d = tenant_store.tenant_root(tenant_id) / "compat_reports" d.mkdir(parents=True, exist_ok=True) return d def _report_path(tenant_id: str, report_id: str) -> Path: return _reports_dir(tenant_id) / f"{report_id}.json" def save_document(tenant_id: str, doc: UploadedDocument) -> None: with _lock: path = _documents_path(tenant_id) rows: dict[str, dict] = {} if path.is_file(): rows = json.loads(path.read_text(encoding="utf-8")) rows[doc.document_id] = asdict(doc) path.write_text(json.dumps(rows, indent=2), encoding="utf-8") def list_documents(tenant_id: str) -> dict[str, UploadedDocument]: path = _documents_path(tenant_id) if not path.is_file(): return {} raw = json.loads(path.read_text(encoding="utf-8")) return {k: UploadedDocument(**v) for k, v in raw.items()} def get_document(tenant_id: str, document_id: str) -> UploadedDocument | None: return list_documents(tenant_id).get(document_id) def delete_document(tenant_id: str, document_id: str) -> bool: with _lock: path = _documents_path(tenant_id) if not path.is_file(): return False rows: dict[str, dict] = json.loads(path.read_text(encoding="utf-8")) if document_id not in rows: return False del rows[document_id] path.write_text(json.dumps(rows, indent=2), encoding="utf-8") return True def save_session(session: ReportSession) -> None: session.updated_at = time.time() with _lock: path = _report_path(session.tenant_id, session.report_id) path.write_text(json.dumps(asdict(session), indent=2), encoding="utf-8") def load_session(tenant_id: str, report_id: str) -> ReportSession | None: path = _report_path(tenant_id, report_id) if not path.is_file(): return None with _lock: data = json.loads(path.read_text(encoding="utf-8")) return ReportSession(**data) def create_report_session( tenant_id: str, *, survey_level: int, primary_document_id: str, document_ids: list[str], ) -> ReportSession: from backend.core.rics_canonical_l3 import PARENT_SECTION_COUNT session = ReportSession( report_id=uuid.uuid4().hex, tenant_id=tenant_id, draft_id=uuid.uuid4().hex, survey_level=survey_level, primary_document_id=primary_document_id, document_ids=document_ids, generation_section_total=PARENT_SECTION_COUNT, ) save_session(session) return session def new_document_id() -> str: return uuid.uuid4().hex