Files
draft-closing-report-default/scripts/service/report_service.py
chenfuqiang 7b24521b43
All checks were successful
技能自动化发布 / release (push) Successful in 6s
feat: 结题报告初稿助手首版 v1.0.1(draft-closing-report-default)
2026-09-13 16:04:01 +08:00

105 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""初稿 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