"""初稿 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"

{_esc(p)}

" for p in paragraphs) parts.append(f"

{_esc(title)}

{items}
") return "".join(parts) def build_preview_html(draft: dict[str, Any]) -> str: body = f"""
初稿 项目 {_esc(draft.get("project_no"))} · 批次 {_esc(draft.get("batch_no"))}

生成说明

数据章节(完成情况/决算/成果清单)由台账自动生成,请逐项核对;叙事章节仅提供素材要点,须由项目负责人本人终审。

{_sections_html(draft.get("sections") or {})} """ 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