114 lines
4.2 KiB
Python
114 lines
4.2 KiB
Python
"""初稿 HTML 预览组装。
|
|
|
|
与 Word 初稿同构内容写入 `{skill_data}/html/draft-{id}.html`,宿主侧栏可预览;
|
|
重建走固定 Action id `render-html`(按台账全量重建,与生成时同一内核)。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from service.html_page_service import load_html_template, render_html_placeholders, write_html_page
|
|
from util.logging_config import get_skill_logger
|
|
|
|
_TEMPLATE_REL = "assets/html/draft-preview.html"
|
|
|
|
|
|
def _log():
|
|
try:
|
|
return get_skill_logger()
|
|
except RuntimeError:
|
|
return None
|
|
|
|
|
|
def _esc(value: Any) -> str:
|
|
import html
|
|
|
|
return html.escape(str(value if value is not None else "—"), quote=True)
|
|
|
|
|
|
def _sections_html(sections: dict[str, list[str]]) -> str:
|
|
parts = []
|
|
for title, paragraphs in (sections or {}).items():
|
|
items = "".join(f"<p class='para{' placeholder' if p.startswith('[待补充') else ''}'>{_esc(p)}</p>" for p in paragraphs)
|
|
parts.append(f"<div class='section'><h2>{_esc(title)}</h2>{items}</div>")
|
|
return "".join(parts)
|
|
|
|
|
|
def build_preview_html(draft: dict[str, Any]) -> str:
|
|
body = f"""
|
|
<div class="verdict-line">
|
|
<span class="tag tier-info">初稿</span>
|
|
<span class="muted">项目 {_esc(draft.get("project_no"))} · 批次 {_esc(draft.get("batch_no"))}</span>
|
|
</div>
|
|
<div class="section"><h2>生成说明</h2>
|
|
<p class="para">数据章节(完成情况/决算/成果清单)由台账自动生成,请逐项核对;叙事章节仅提供素材要点,须由项目负责人本人终审。</p>
|
|
</div>
|
|
{_sections_html(draft.get("sections") or {})}
|
|
<footer>本预览与 Word 初稿同构;严禁引入台账之外的成果或结论。以本单位结题报告模板为准。</footer>
|
|
"""
|
|
template = load_html_template(_TEMPLATE_REL)
|
|
return render_html_placeholders(
|
|
template,
|
|
{
|
|
"TITLE": f"结题报告初稿预览 · {draft.get('project_name') or draft.get('project_no') or ''}",
|
|
"GENERATED_AT": "",
|
|
"BODY": body,
|
|
},
|
|
)
|
|
|
|
|
|
def write_preview_for_kernel(*, draft: dict[str, Any], draft_id: int) -> str:
|
|
"""生成期写预览:按初稿记录编号命名,多次生成互不覆盖。"""
|
|
html_text = build_preview_html(draft)
|
|
log = _log()
|
|
path = write_html_page(f"draft-{draft_id}.html", html_text)
|
|
if log:
|
|
log.info("draft_preview_written draft_id=%s project_no=%s path=%s stage=report", draft_id, draft.get("project_no"), path)
|
|
return path
|
|
|
|
|
|
def build_preview_for_draft_id(draft_id: int) -> str:
|
|
"""按初稿记录 id 从台账全量重建 HTML 预览(与 Word 初稿同构,含决算/成果/占位章节)。"""
|
|
from db.connection import get_conn
|
|
from service.draft_service import build_draft_sections, query_pending_projects
|
|
from service.ledger_service import list_achievements, list_expenses
|
|
|
|
log = _log()
|
|
if log:
|
|
log.info("preview_render_start draft_id=%s stage=render", draft_id)
|
|
conn = get_conn()
|
|
try:
|
|
cur = conn.cursor()
|
|
cur.execute("SELECT * FROM report_drafts WHERE id = ?", (draft_id,))
|
|
row = cur.fetchone()
|
|
if not row:
|
|
raise ValueError(f"初稿记录不存在:{draft_id}")
|
|
columns = [d[0] for d in cur.description]
|
|
record = dict(zip(columns, row))
|
|
finally:
|
|
conn.close()
|
|
|
|
project_no = str(record.get("project_no") or "")
|
|
projects = query_pending_projects(project_no=project_no)
|
|
project = projects[0] if projects else {
|
|
"project_no": project_no,
|
|
"project_name": str(record.get("project_name") or ""),
|
|
}
|
|
sections_data = build_draft_sections(
|
|
project,
|
|
list_achievements(project_no),
|
|
list_expenses(project_no),
|
|
)
|
|
draft = {
|
|
"project_no": project_no,
|
|
"project_name": str(project.get("project_name") or record.get("project_name") or ""),
|
|
"batch_no": str(record.get("batch_no") or ""),
|
|
"sections": sections_data["sections"],
|
|
}
|
|
html_text = build_preview_html(draft)
|
|
path = write_html_page(f"draft-{draft_id}.html", html_text)
|
|
if log:
|
|
log.info("preview_render_done draft_id=%s path=%s stage=render", draft_id, path)
|
|
return path
|