"""ESPD document types and request models (ported from GostGenerator2).""" from __future__ import annotations from enum import Enum from typing import Any, Optional from pydantic import BaseModel, ConfigDict, Field class DocumentType(str, Enum): SPEC = "spec" TEXT = "text" RSP = "rsp" RP = "rp" RO = "ro" PMI = "pmi" IMP = "imp" DOC_TYPE_CONFIG: dict[DocumentType, dict[str, Any]] = { DocumentType.SPEC: { "name": "Спецификация", "code": "", "suffix": "", "template": "nSpec.docx", "order": 0, }, DocumentType.TEXT: { "name": "Текст программы", "code": "12", "suffix": " 12", "template": "nText.docx", "order": 1, }, DocumentType.RSP: { "name": "Руководство системного программиста", "code": "32", "suffix": " 32", "template": "rsp.docx", "order": 2, }, DocumentType.RP: { "name": "Руководство программиста", "code": "33", "suffix": " 33", "template": "rp.docx", "order": 3, }, DocumentType.RO: { "name": "Руководство оператора", "code": "34", "suffix": " 34", "template": "nRo.docx", "order": 4, }, DocumentType.PMI: { "name": "Программа и методика испытаний", "code": "51", "suffix": " 51", "template": "pmi.docx", "order": 5, }, DocumentType.IMP: { "name": "Исполняемые модули программы", "code": "91", "suffix": " 91", "template": "nImp.docx", "order": 6, }, } class SpecItem(BaseModel): decimal: str name: str note: str = "" class SelectedDocument(BaseModel): model_config = ConfigDict(populate_by_name=True) doc_type: DocumentType with_dvd: bool = Field(default=False, alias="withDvd") class CommonFields(BaseModel): decimal: str = "" progname: str = "" author: str = "" checker: str = "" ncontrol: str = "" approver: str = "" year: str = "2026" class ImpZipInfo(BaseModel): folder_table_rows: list[list[str]] zip_checksum: str class GenerateRequest(BaseModel): doc_type: DocumentType common: CommonFields spec_items: list[SpecItem] = Field(default_factory=list) imp_zip_info: Optional[ImpZipInfo] = None extra_context: Optional[dict[str, Any]] = None class DocContent(BaseModel): html: str = "" updated_at: Optional[str] = None class EspdState(BaseModel): """Persisted ESPD form state for a project.""" common: CommonFields = Field(default_factory=CommonFields) selected_docs: list[SelectedDocument] = Field(default_factory=list) spec_items: list[SpecItem] = Field(default_factory=list) ro_description: str = "" ro_ai_content: str = "" # legacy; mirrored into doc_contents.ro doc_contents: dict[str, DocContent] = Field(default_factory=dict) imp_zip_info: Optional[ImpZipInfo] = None def get_doc_html(self, doc_type: str | DocumentType) -> str: key = doc_type.value if isinstance(doc_type, DocumentType) else str(doc_type) entry = self.doc_contents.get(key) if entry and entry.html: return entry.html if key == DocumentType.RO.value and self.ro_ai_content: return f"

{self.ro_ai_content.replace(chr(10), '

')}

" return "" def set_doc_html(self, doc_type: str | DocumentType, html: str) -> None: from datetime import datetime, timezone from app.services.espd.html_util import html_to_plain key = doc_type.value if isinstance(doc_type, DocumentType) else str(doc_type) self.doc_contents[key] = DocContent( html=html or "", updated_at=datetime.now(timezone.utc).isoformat(), ) if key == DocumentType.RO.value: self.ro_ai_content = html_to_plain(html or "") class EspdGenerateOneRequest(BaseModel): doc_type: DocumentType common: Optional[CommonFields] = None spec_items: Optional[list[SpecItem]] = None imp_zip_info: Optional[ImpZipInfo] = None ro_ai_content: Optional[str] = None doc_contents: Optional[dict[str, DocContent]] = None class EspdGenerateAllRequest(BaseModel): selected_docs: Optional[list[SelectedDocument]] = None common: Optional[CommonFields] = None spec_items: Optional[list[SpecItem]] = None imp_zip_info: Optional[ImpZipInfo] = None ro_ai_content: Optional[str] = None doc_contents: Optional[dict[str, DocContent]] = None class EspdRoTextRequest(BaseModel): progname: str = "" decimal: str = "" ro_description: str = "" doc_type: DocumentType = DocumentType.RO class EspdPreviewRequest(BaseModel): doc_type: DocumentType # Placeholder keys injected into docxtpl context for body content CONTENT_CONTEXT_KEYS: dict[DocumentType, str] = { DocumentType.RO: "RO_CONTENT", DocumentType.TEXT: "TEXT_CONTENT", DocumentType.RSP: "RSP_CONTENT", DocumentType.RP: "RP_CONTENT", DocumentType.PMI: "PMI_CONTENT", DocumentType.IMP: "IMP_CONTENT", }