187 lines
5.2 KiB
Python
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",
|
|
}
|