Files
verify-promotion-metric-def…/scripts/service/report_service.py

185 lines
7.2 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 组装。
- 每案例一份 `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(
"<tr>"
f"<td>{_esc(item.get('metric'))}</td>"
f"<td class='nowrap'>{_esc(required_text)}</td>"
f"<td class='nowrap'>{_esc(item.get('actual'))}</td>"
f"<td class='nowrap'>{_esc(margin_text)}</td>"
f"<td class='nowrap'><span class='tag {badge}'>{_COLOR_MARK.get(color, color)}</span></td>"
f"<td class='muted'>{_esc(item.get('basis'))}</td>"
"</tr>"
)
if not rows:
rows.append('<tr><td colspan="6" class="muted">无</td></tr>')
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 '<p class="muted">无补充事项。</p>'
items = "".join(f"<li>{_esc(a)}</li>" for a in advices)
return f"<ol class='reasons'>{items}</ol>"
def build_verify_html(result: dict[str, Any], batch_no: str = "") -> str:
verdict = str(result.get("verdict") or "")
mark = {"安全": "🟢 安全", "边缘": "🟡 边缘", "硬伤": "🔴 硬伤"}.get(verdict, verdict)
body = f"""
<div class="verdict-line">
<span class="tag {_verdict_badge(verdict)}">{mark}</span>
<span class="muted">{_esc(result.get("department") or "")} · 申报 {_esc(result.get("apply_rank") or "—")} · 批次 {_esc(batch_no or result.get("batch_no"))}</span>
</div>
<p class="para">{_esc(result.get("summary"))}</p>
<div class="section"><h2>硬指标达标矩阵</h2>
<table>
<tr><th>指标</th><th>最低要求</th><th>实有</th><th>余量</th><th>判定</th><th>依据</th></tr>
{_matrix_rows(result.get("matrix") or [])}
</table>
</div>
<div class="section"><h2>补充材料清单</h2>{_advice_list(result.get("matrix") or [])}</div>
<footer>核验仅针对硬指标形式审查,不构成评审结论;门槛以人事处最新文件为准,是否通过评审由评委会决定。</footer>
"""
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(
"<tr>"
f"<td>{_esc(result.get('teacher_name'))}</td>"
f"<td class='muted'>{_esc(result.get('department'))}</td>"
f"<td class='nowrap'>{_esc(result.get('apply_rank'))}</td>"
f"<td class='nowrap'><span class='tag {badge}'>{_esc(verdict)}</span></td>"
f"<td class='muted'>{_esc(red_text)}</td>"
"</tr>"
)
if not rows:
rows.append('<tr><td colspan="5" class="muted">本期无核验结果。</td></tr>')
counts: dict[str, int] = {}
for result in results:
counts[str(result.get("verdict"))] = counts.get(str(result.get("verdict")), 0) + 1
chips = " ".join(
f"<span class='tag {_verdict_badge(v)}'>{_esc(v)} × {n}</span>" for v, n in sorted(counts.items())
)
body = f"""
<div class="verdict-line">
{chips or '<span class="muted">无</span>'}
<span class="muted">批次 {_esc(batch_no)} · 案例 {_esc(len(results))} 个</span>
</div>
<div class="section"><h2>核验结论一览</h2>
<table><tr><th>教师</th><th>学院</th><th>申报级别</th><th>结论</th><th>硬伤项</th></tr>{''.join(rows)}</table>
</div>
<footer>汇总由批量核验自动生成,供师资科排序复核;核验不构成评审结论。</footer>
"""
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