Files
2026-09-09 13:03:41 +03:00

187 lines
5.2 KiB
Python

"""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"<p>{self.ro_ai_content.replace(chr(10), '</p><p>')}</p>"
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",
}