From d211d2fac8a6b18b250ee3bc3922e7ce5ef2a17a Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Wed, 21 Oct 2026 20:03:54 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=84=E6=B5=8B=E9=A1=B5=E7=9A=84=E5=88=86?= =?UTF-8?q?=E6=9E=90=E6=96=B9=E6=B3=95=E3=80=81=E6=A8=A1=E5=BC=8F=E5=8F=AB?= =?UTF-8?q?=E6=B3=95=E6=94=B9=E4=B8=BA=E5=BC=95=E7=94=A8=20lib/termsAnalys?= =?UTF-8?q?is=20=E9=82=A3=E4=B8=80=E4=BB=BD=EF=BC=88=E5=8E=9F=E6=9D=A5=20t?= =?UTF-8?q?ermsEval=20=E5=8F=A6=E6=8A=84=E4=BA=86=E4=B8=80=E4=BB=BD=20OP?= =?UTF-8?q?=5FLABELS=20/=20MODE=5FLABELS=EF=BC=89=EF=BC=9AEVAL=5FTEXT.anal?= =?UTF-8?q?ysis.op=20/=20mode=20=E7=9B=B4=E6=8E=A5=E7=94=A8=20ANALYSIS=5FO?= =?UTF-8?q?P=5FLABEL=E3=80=81ANALYSIS=5FMODE=5FLABEL=EF=BC=8C=E5=AD=97?= =?UTF-8?q?=E4=B8=80=E6=A0=B7=E3=80=81=E7=95=8C=E9=9D=A2=E4=B8=8D=E5=8F=98?= =?UTF-8?q?=E3=80=82tests/test=5Ffrontend=5Feval=5Funions.py=20=E6=94=B9?= =?UTF-8?q?=E6=88=90=E8=AF=BB=20termsAnalysis.ts=20=E7=9A=84=E4=B8=A4?= =?UTF-8?q?=E5=BC=A0=E8=A1=A8=E5=92=8C=E5=A5=91=E7=BA=A6=E9=80=90=E9=A1=B9?= =?UTF-8?q?=E5=AF=B9=EF=BC=8C=E5=B9=B6=E5=AE=88=E4=BD=8F=E8=AF=84=E6=B5=8B?= =?UTF-8?q?=E9=A1=B5=E5=BC=95=E7=94=A8=E7=9A=84=E6=98=AF=E8=BF=99=E4=B8=80?= =?UTF-8?q?=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/tests/test_frontend_eval_unions.py | 20 +++++++++++++++++--- frontend/src/lib/termsEval.ts | 14 ++++++-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/backend/tests/test_frontend_eval_unions.py b/backend/tests/test_frontend_eval_unions.py index 4fc167a3..c4b35465 100644 --- a/backend/tests/test_frontend_eval_unions.py +++ b/backend/tests/test_frontend_eval_unions.py @@ -12,6 +12,8 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[2] TYPES_EVAL = ROOT / "frontend" / "src" / "typesEval.ts" TERMS_EVAL = ROOT / "frontend" / "src" / "lib" / "termsEval.ts" +#: 分析方法、模式的叫法只有这一份(termsEval 引用它,不另抄) +TERMS_ANALYSIS = ROOT / "frontend" / "src" / "lib" / "termsAnalysis.ts" _COMMENT = re.compile(r"/\*.*?\*/|//[^\n]*", re.S) @@ -111,16 +113,28 @@ def test_signal_bands_follow_the_server(): assert set(_labels("signals.band")) == {c["band"] for c in [*SIGNAL_CRITERIA["e1"], *SIGNAL_CRITERIA["e3"]]} +def _analysis_table(name: str) -> list[tuple[str, str]]: + """lib/termsAnalysis.ts 里一张叫法表(export const NAME … = { … })的 (键, 叫法),按写的顺序。""" + text = _source(TERMS_ANALYSIS) + found = re.search(rf"export const {name}\b[^=]*= \{{(.*?)\n\}}", text, re.S) + assert found is not None, f"termsAnalysis.ts 里没有 export const {name}" + return re.findall(r"(\w+): '([^']*)'", found.group(1)) + + def test_analysis_expectations_follow_the_server(): """分析期望:判定类和服务端 app/eval/judgements 一致;分析方法、模式的叫法和契约(app/analysis/contract 的 OP_LABELS、 - MODE_LABELS)一字不差——界面上的「变化拆解(可加)」和服务端拼的、证据面板写的是同一种说法。""" + MODE_LABELS)一字不差——界面上的「变化拆解(可加)」和服务端拼的、证据面板写的是同一种说法。叫法只有 lib/termsAnalysis + 一份:评测页(EVAL_TEXT.analysis.op / mode)引用它,不另抄一份。""" from app.analysis.contract import MODE_LABELS, MODES, OP_LABELS, OPS from app.eval.judgements import JUDGEMENTS assert _union("EvalJudgement") == list(JUDGEMENTS) assert _labels("analysis.judgement") == list(JUDGEMENTS) assert _union("EvalAnalysisOp") == list(OPS) - assert _pairs("analysis.op") == list(OP_LABELS.items()) + assert _analysis_table("ANALYSIS_OP_LABEL") == list(OP_LABELS.items()) assert _union("EvalAnalysisMode") == list(MODE_LABELS) - assert _pairs("analysis.mode") == list(MODE_LABELS.items()) + assert _analysis_table("ANALYSIS_MODE_LABEL") == list(MODE_LABELS.items()) assert {m for modes in MODES.values() for m in modes} == set(MODE_LABELS) + terms = _source(TERMS_EVAL) + assert re.search(r"\n\s*op: ANALYSIS_OP_LABEL\b", terms) and re.search(r"\n\s*mode: ANALYSIS_MODE_LABEL\b", terms), \ + "评测页的分析方法、模式叫法要引用 lib/termsAnalysis 的那一份" diff --git a/frontend/src/lib/termsEval.ts b/frontend/src/lib/termsEval.ts index 24c7583f..0fccaecf 100644 --- a/frontend/src/lib/termsEval.ts +++ b/frontend/src/lib/termsEval.ts @@ -10,6 +10,7 @@ import type { EvalTrigger, EvalUpgradePolicy, EvalVerdict, } from '../typesEval' import { formatCost, formatNumber } from './format' +import { ANALYSIS_MODE_LABEL, ANALYSIS_OP_LABEL } from './termsAnalysis' /** 比率写成百分数:0.756 → 「75.6%」;没有分母(null)写「—」 */ export function pct(rate: number | null | undefined, digits = 1): string { @@ -310,16 +311,13 @@ export const EVAL_TEXT = { refused: (reason: string) => `算不了:${reason}(这一项不能评分)`, }, /** - * 分析期望(贡献、判定)的叫法。分析方法、模式的叫法和服务端契约(app/analysis/contract 的 OP_LABELS、MODE_LABELS)一字 - * 不差(tests/test_frontend_eval_unions.py 逐个对照);判定类是评测自己的(app/eval/judgements.JUDGEMENTS) + * 分析期望(贡献、判定)的叫法。分析方法、模式的叫法只有 lib/termsAnalysis 一份(照服务端契约 app/analysis/contract 的 + * OP_LABELS、MODE_LABELS 一字不差,tests/test_frontend_eval_unions.py 与 check-studio 逐个对照),这里引用它,不另抄; + * 判定类是评测自己的(app/eval/judgements.JUDGEMENTS) */ analysis: { - op: { - decompose: '变化拆解', baseline: '波动基线', trend: '趋势判断', differ: '差异检验', comparable: '可比性检查', - } as Record, - mode: { - additive: '可加', ratio: '比率', factor: '乘积', rate: '比率', mean: '均值', rows: '逐行明细', - } as Record, + op: ANALYSIS_OP_LABEL as Record, + mode: ANALYSIS_MODE_LABEL as Record, judgement: { source: '主要来源', dispersed: '分散,没有主要来源', flat: '基本持平', mix: '主要来自结构变化', rate: '主要来自各段自身的变化', mixed: '几方面都有影响', normal: '常规波动', anomaly: '超出常规波动', rising: '上升趋势', falling: '下降趋势',