105 lines
4.0 KiB
Python
105 lines
4.0 KiB
Python
"""初稿 HTML 预览组装。
|
||
|
||
与 Word 初稿同构内容写入 `{skill_data}/html/draft-{id}.html`,宿主侧栏可预览;
|
||
重建走固定 Action id `render-html`。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
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 _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]) -> str:
|
||
html_text = build_preview_html(draft)
|
||
log = get_skill_logger()
|
||
path = write_html_page("draft-preview.html", html_text)
|
||
log.info("draft_preview_written project_no=%s path=%s stage=report", draft.get("project_no"), path)
|
||
return path
|
||
|
||
|
||
def build_preview_for_draft_id(draft_id: int) -> str:
|
||
"""按初稿记录 id 重建 HTML 预览(重建入口用)。"""
|
||
from db.connection import get_conn
|
||
|
||
log = get_skill_logger()
|
||
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()
|
||
|
||
try:
|
||
completion = json.loads(record.get("completion_json") or "[]")
|
||
except (TypeError, ValueError):
|
||
completion = []
|
||
try:
|
||
narrative_points = json.loads(record.get("narrative_points_json") or "[]")
|
||
except (TypeError, ValueError):
|
||
narrative_points = []
|
||
|
||
sections: dict[str, list[str]] = {
|
||
"完成情况(自动生成)": [f"{row.get('type')}:{row.get('detail')}" for row in completion] or ["指标未配置(W1)。"],
|
||
"叙事素材要点": [f"· {p.get('point')}(来源:{p.get('source')})" for p in narrative_points] or ["无素材,请补录成果/年度报告摘句。"],
|
||
"Word 初稿": [f"文件:{record.get('draft_file') or '—'}(output 目录可取)"],
|
||
}
|
||
draft = {
|
||
"project_no": record.get("project_no"),
|
||
"project_name": record.get("project_name"),
|
||
"batch_no": record.get("batch_no"),
|
||
"sections": sections,
|
||
}
|
||
html_text = build_preview_html(draft)
|
||
path = write_html_page(f"draft-{draft_id}.html", html_text)
|
||
log.info("preview_render_done draft_id=%s path=%s stage=render", draft_id, path)
|
||
return path
|