"""《核验报告》HTML 组装。
- 每案例一份 `report-{case_id}.html`(三色结论 + 达标矩阵 + 补充材料清单)
- 批量核验另生成汇总页 `brief-{batch_no}.html`
写入 `{skill_data}/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
def _log():
try:
return get_skill_logger()
except RuntimeError:
return None
_TEMPLATE_REL = "assets/html/verify-report.html"
_COLOR_BADGE = {"green": "tier-strong", "yellow": "tier-try", "red": "tag-red", "gray": "tier-not"}
_COLOR_MARK = {"green": "🟢 达标", "yellow": "🟡 边缘", "red": "🔴 硬伤", "gray": "⚪ 待核"}
def _esc(value: Any) -> str:
import html
return html.escape(str(value if value is not None else "—"), quote=True)
def _verdict_badge(verdict: str) -> str:
return {"安全": "tier-strong", "边缘": "tier-try", "硬伤": "tag-red"}.get(verdict, "tier-not")
def _matrix_rows(matrix: list[dict[str, Any]]) -> str:
rows = []
for item in matrix:
color = str(item.get("color") or "gray")
badge = _COLOR_BADGE.get(color, "tier-not")
required = item.get("required")
required_text = "—" if required is None else f"{required:g}"
margin = item.get("margin")
margin_text = "—" if margin is None else f"{margin * 100:.1f}%"
rows.append(
"
"
f"
{_esc(item.get('metric'))}
"
f"
{_esc(required_text)}
"
f"
{_esc(item.get('actual'))}
"
f"
{_esc(margin_text)}
"
f"
{_COLOR_MARK.get(color, color)}
"
f"
{_esc(item.get('basis'))}
"
"
"
)
if not rows:
rows.append('
无
')
return "".join(rows)
def _advice_list(matrix: list[dict[str, Any]]) -> str:
advices = [item.get("advice") for item in matrix if item.get("advice")]
if not advices:
return '
无补充事项。
'
items = "".join(f"
{_esc(a)}
" for a in advices)
return f"{items}"
def build_verify_html(result: dict[str, Any], batch_no: str = "") -> str:
verdict = str(result.get("verdict") or "")
mark = {"安全": "🟢 安全", "边缘": "🟡 边缘", "硬伤": "🔴 硬伤"}.get(verdict, verdict)
body = f"""
{mark}{_esc(result.get("department") or "")} · 申报 {_esc(result.get("apply_rank") or "—")} · 批次 {_esc(batch_no or result.get("batch_no"))}
{_esc(result.get("summary"))}
硬指标达标矩阵
指标
最低要求
实有
余量
判定
依据
{_matrix_rows(result.get("matrix") or [])}
补充材料清单
{_advice_list(result.get("matrix") or [])}
"""
template = load_html_template(_TEMPLATE_REL)
return render_html_placeholders(
template,
{
"TITLE": f"职称申报核验报告 · {result.get('teacher_name') or ''}",
"GENERATED_AT": "",
"BODY": body,
},
)
def write_report_for_kernel(*, result: dict[str, Any], batch_no: str) -> str:
html_text = build_verify_html(result, batch_no)
log = _log()
path = write_html_page(f"report-{result['case_id']}.html", html_text)
if log:
log.info("verify_report_written case_id=%s path=%s stage=report", result["case_id"], path)
return path
def build_report_for_case_id(case_id: int) -> str:
"""按案例 id 从库中取数并重写 HTML(重建入口用)。"""
from db.connection import get_conn
log = _log()
if log:
log.info("report_render_start case_id=%s stage=render", case_id)
conn = get_conn()
try:
cur = conn.cursor()
cur.execute("SELECT * FROM verify_cases WHERE id = ?", (case_id,))
row = cur.fetchone()
if not row:
raise ValueError(f"核验案例不存在:{case_id}")
columns = [d[0] for d in cur.description]
record = dict(zip(columns, row))
finally:
conn.close()
result = dict(record)
try:
result["matrix"] = json.loads(record.get("matrix_json") or "[]")
except (TypeError, ValueError):
result["matrix"] = []
html_text = build_verify_html(result)
path = write_html_page(f"report-{case_id}.html", html_text)
if log:
log.info("report_render_done case_id=%s path=%s stage=render", case_id, path)
return path
def build_batch_brief(*, batch_no: str, results: list[dict[str, Any]]) -> str:
rows = []
for result in results:
verdict = str(result.get("verdict") or "")
badge = _verdict_badge(verdict)
red_items = [m for m in result.get("matrix") or [] if m.get("color") == "red"]
red_text = "、".join(str(m.get("metric")) for m in red_items) if red_items else "—"
rows.append(
"
"
f"
{_esc(result.get('teacher_name'))}
"
f"
{_esc(result.get('department'))}
"
f"
{_esc(result.get('apply_rank'))}
"
f"
{_esc(verdict)}
"
f"
{_esc(red_text)}
"
"
"
)
if not rows:
rows.append('
本期无核验结果。
')
counts: dict[str, int] = {}
for result in results:
counts[str(result.get("verdict"))] = counts.get(str(result.get("verdict")), 0) + 1
chips = " ".join(
f"{_esc(v)} × {n}" for v, n in sorted(counts.items())
)
body = f"""
{chips or '无'}
批次 {_esc(batch_no)} · 案例 {_esc(len(results))} 个