"""《核验报告》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))} 个

    核验结论一览

    {''.join(rows)}
    教师学院申报级别结论硬伤项
    """ template = load_html_template(_TEMPLATE_REL) page = render_html_placeholders( template, {"TITLE": "职称核验汇总", "GENERATED_AT": "", "BODY": body}, ) path = write_html_page(f"brief-{batch_no}.html", page) log = _log() if log: log.info("verify_brief_written batch_no=%s path=%s stage=report", batch_no, path) return path