v3.9.0 发版:规则库补强(E3 +20 条,548→568)+ 收敛层防护定型
This commit is contained in:
+16
-14
@@ -25,6 +25,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
@@ -36,7 +37,7 @@ DIM_ORDER = ["流年运势", "感情", "六亲", "事业", "性格", "学业", "
|
||||
|
||||
# 结构性占位符:值是装配层渲染的 HTML 片段或 base64 资源,填充时原样注入;
|
||||
# 其余占位符均为文本字段,一律 HTML 转义防注入(与盘面图装配层同口径)。
|
||||
STRUCTURAL = {"LOGO_B64", "FONT_B64", "DIM_BARS", "CONTEST_ROWS"}
|
||||
STRUCTURAL = {"LOGO_B64", "FONT_B64", "DIM_BARS", "CONTEST_BARS"}
|
||||
|
||||
|
||||
def asset_b64(assets: Path, name: str) -> str:
|
||||
@@ -66,26 +67,27 @@ def dim_bars(score: dict) -> str:
|
||||
return "\n ".join(rows)
|
||||
|
||||
|
||||
def contest_rows(score: dict) -> str:
|
||||
def contest_bars(score: dict) -> str:
|
||||
rows = []
|
||||
base = score["baseline"].get("per_contest", {})
|
||||
for c in sorted(score["per_contest"]):
|
||||
s = score["per_contest"][c]
|
||||
b = base.get(c)
|
||||
label = escape(re.sub(r"^contest\d+_", "", c) + " 届")
|
||||
ours = f'<div class="bar ours" style="width:{s["accuracy"]}%"></div>'
|
||||
ours += (f'<div class="bar base" style="width:{b}%"></div>'
|
||||
if isinstance(b, (int, float)) else "")
|
||||
val = f'<b>{s["accuracy"]}%</b>({s["correct"]}/{s["total"]})'
|
||||
if isinstance(b, (int, float)):
|
||||
diff = round(s["accuracy"] - b, 1)
|
||||
cls = "up" if diff >= 0 else "down"
|
||||
delta = (f'<span class="delta {cls}">{"+" if diff >= 0 else ""}'
|
||||
f'{diff}pp</span>')
|
||||
btxt = f"{b}%"
|
||||
else:
|
||||
delta, btxt = "—", "—"
|
||||
rows.append(
|
||||
f'<tr><td class="dim">{escape(c)}</td>'
|
||||
f'<td><b>{s["accuracy"]}%</b>({s["correct"]}/{s["total"]},'
|
||||
f'未答 {s.get("no_answer", 0)})</td>'
|
||||
f'<td>{btxt}</td><td>{delta}</td></tr>')
|
||||
return "\n ".join(rows)
|
||||
val += (f' <span style="color:var(--muted)">基线 {b}%</span>'
|
||||
f' <span class="delta {cls}">'
|
||||
f'{"+" if diff >= 0 else ""}{diff}pp</span>')
|
||||
rows.append(f'<div class="dimrow"><div class="yrname">{label}</div>'
|
||||
f'<div class="dimtrack">{ours}</div>'
|
||||
f'<div class="yrval">{val}</div></div>')
|
||||
return "\n ".join(rows)
|
||||
|
||||
|
||||
def render(score: dict, template: str, assets: Path, score_json_name: str) -> str:
|
||||
@@ -111,7 +113,7 @@ def render(score: dict, template: str, assets: Path, score_json_name: str) -> st
|
||||
"DELTA_CLASS": "up" if delta >= 0 else "down",
|
||||
"DELTA_TEXT": f'({"+" if delta >= 0 else ""}{delta}pp)',
|
||||
"DIM_BARS": dim_bars(score),
|
||||
"CONTEST_ROWS": contest_rows(score),
|
||||
"CONTEST_BARS": contest_bars(score),
|
||||
"SCORE_JSON": score_json_name,
|
||||
}
|
||||
out = template
|
||||
|
||||
@@ -92,7 +92,11 @@
|
||||
.delta.up { color: #2e7d32; }
|
||||
.delta.down { color: #c62828; }
|
||||
|
||||
/* 维度条形 */
|
||||
/* 两大区块卡片:顶部强调线 + 编号徽章,与头部信息卡明确分开 */
|
||||
.card.block { border-top: 3px solid var(--accent); }
|
||||
.sectno { color: var(--accent); margin-right: 6px; }
|
||||
|
||||
/* 维度/年度条形 */
|
||||
.sect { font-size: 15px; font-weight: 700; margin: 2px 0 10px;
|
||||
font-family: "Kaiti SC", "STKaiti", "KaiTi", serif; }
|
||||
.sect .note { font-size: 12px; color: var(--muted); font-weight: 400; margin-left: 8px; }
|
||||
@@ -114,14 +118,12 @@
|
||||
white-space: nowrap; }
|
||||
.dimval b { color: var(--ink); font-size: 13.5px; }
|
||||
|
||||
/* 分届表 */
|
||||
table.contest { width: 100%; border-collapse: collapse; }
|
||||
.contest th, .contest td {
|
||||
border: 1px solid var(--card-line); text-align: center;
|
||||
padding: 7px 4px; font-size: 13.5px;
|
||||
}
|
||||
.contest th { background: rgba(193, 188, 235, .18); color: var(--soft); font-weight: 600; }
|
||||
.contest td.dim { text-align: left; padding-left: 12px; }
|
||||
/* 分年度柱状条(行结构与维度条一致,名称/数值列更宽) */
|
||||
.yrname { width: 64px; text-align: right; font-size: 13px; color: var(--soft);
|
||||
flex: none; }
|
||||
.yrval { width: 252px; flex: none; font-size: 12px; color: var(--soft);
|
||||
white-space: nowrap; }
|
||||
.yrval b { color: var(--ink); font-size: 13.5px; }
|
||||
|
||||
.foot {
|
||||
margin-top: 14px; padding-top: 10px; font-size: 12px; color: var(--muted);
|
||||
@@ -164,18 +166,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="sect">分维度准确率<span class="note">九维度,条下灰线为官方基线</span></div>
|
||||
<div class="card block">
|
||||
<div class="sect"><span class="sectno">①</span>分年度准确率<span class="note">五届 × 40 题,条下灰线为官方分届基线</span></div>
|
||||
<div class="legend"><i class="ours"></i>本次评测 <i class="base"></i>官方基线</div>
|
||||
{{DIM_BARS}}
|
||||
{{CONTEST_BARS}}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="sect">分届准确率</div>
|
||||
<table class="contest">
|
||||
<tr><th>届别</th><th>本次</th><th>官方基线</th><th>差值</th></tr>
|
||||
{{CONTEST_ROWS}}
|
||||
</table>
|
||||
<div class="card block">
|
||||
<div class="sect"><span class="sectno">②</span>分维度准确率<span class="note">九维度,条下灰线为官方分维度基线</span></div>
|
||||
<div class="legend"><i class="ours"></i>本次评测 <i class="base"></i>官方基线</div>
|
||||
{{DIM_BARS}}
|
||||
<div class="foot">
|
||||
数据源:{{SCORE_JSON}} ・ 渲染:评测/工具/render_scorecard.py<br>
|
||||
盲测纪律与复测方法见 评测/README.md 与 评测/测试规则.md。
|
||||
|
||||
Reference in New Issue
Block a user