From d529e5f694e20b9c9833495b6cfdbb91446af952 Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Wed, 21 Oct 2026 14:08:56 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=97=E5=AD=90=E7=9A=84=E5=8F=AB=E6=B3=95?= =?UTF-8?q?=E5=8F=AA=E7=95=99=E5=A5=91=E7=BA=A6=E4=B8=80=E4=BB=BD=EF=BC=9A?= =?UTF-8?q?contract.OP=5FLABELS=20/=20MODE=5FLABELS=20=E6=98=AF=E5=94=AF?= =?UTF-8?q?=E4=B8=80=E6=9D=A5=E6=BA=90=EF=BC=8C=E8=B6=8B=E5=8A=BF=E5=AE=9A?= =?UTF-8?q?=E4=B8=BA=E3=80=8C=E8=B6=8B=E5=8A=BF=E5=88=A4=E6=96=AD=E3=80=8D?= =?UTF-8?q?=EF=BC=9Bengine/labels=20=E7=9A=84=20ANALYSIS=5FOP=5FLABEL=20?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E5=BC=95=E7=94=A8=20OP=5FLABELS=E3=80=81ANAL?= =?UTF-8?q?YSIS=5FMODE=5FLABEL=20=E6=8C=89=20MODES=20=E4=BB=8E=20MODE=5FLA?= =?UTF-8?q?BELS=20=E5=88=86=E5=88=B0=E5=90=84=E7=AE=97=E5=AD=90=EF=BC=88?= =?UTF-8?q?=E5=88=86=E6=9E=90=E5=8D=A1=E6=A0=A1=E9=AA=8C=E3=80=81=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E8=AF=B4=E6=98=8E=E3=80=81=E5=89=8D=E7=AB=AF=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E7=85=A7=E5=AE=83=EF=BC=89=EF=BC=8Cphrases=20?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=20OP=5FNAMES=EF=BC=8C=E3=80=8C{=E5=8F=AB?= =?UTF-8?q?=E6=B3=95}=E6=B2=A1=E6=9C=89=E5=81=9A=E6=88=90=E3=80=8D?= =?UTF-8?q?=E7=9A=84=E5=88=A4=E5=AE=9A=E8=AF=AD=E5=92=8C=E7=AE=97=E5=AD=90?= =?UTF-8?q?=E7=9A=84=E8=B0=83=E7=94=A8=E9=94=99=E8=AF=AF=E9=83=BD=E7=94=A8?= =?UTF-8?q?=E5=A5=91=E7=BA=A6=E7=9A=84=E5=8F=AB=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/analysis/__init__.py | 15 +++++++-------- backend/app/analysis/contract.py | 10 ++++++---- backend/app/analysis/phrases.py | 8 +++----- backend/app/engine/labels.py | 13 ++++++------- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/backend/app/analysis/__init__.py b/backend/app/analysis/__init__.py index 200fc7f2..314806da 100644 --- a/backend/app/analysis/__init__.py +++ b/backend/app/analysis/__init__.py @@ -24,8 +24,8 @@ from collections.abc import Callable, Mapping from datetime import date, datetime from typing import Any -from app.analysis.contract import (MODES, OPS, AnalysisRequest, AnalysisResult, CallError, Context, InputSnapshot, - effective_settings) +from app.analysis.contract import (MODES, OP_LABELS, OPS, AnalysisRequest, AnalysisResult, CallError, Context, + InputSnapshot, effective_settings) #: 按调用里写的名字取回输入快照;没有这个名字返回 None Resolver = Callable[[str], InputSnapshot | None] @@ -34,17 +34,15 @@ ParentResolver = Callable[[str], AnalysisResult | None] def _op_mode(request: Mapping[str, Any]) -> tuple[str, str | None]: - from app.analysis.phrases import OP_NAMES - op = request.get("op") if op not in OPS: - raise CallError("unknown_op", f"不认识的分析:{op}。能做的有{'、'.join(OP_NAMES[o] for o in OPS)}") + raise CallError("unknown_op", f"不认识的分析:{op}。能做的有{'、'.join(OP_LABELS[o] for o in OPS)}") modes = MODES.get(op) mode = request.get("mode") if modes is None: return op, None if mode not in modes: - raise CallError("unknown_mode", f"{OP_NAMES[op]}要写模式:{' / '.join(modes)}") + raise CallError("unknown_mode", f"{OP_LABELS[op]}要写模式:{' / '.join(modes)}") return op, mode @@ -156,9 +154,10 @@ def recompute(result: AnalysisResult, load: Callable[[str], InputSnapshot | None def exhausted(settings: Mapping[str, Any]) -> CallError: - """本运行分析次数用完(max_calls)的调用错误:入口数着次数,到了上限交回它,不再算。""" + """分析次数用完(max_calls)的调用错误:上限按每个 Agent 每次执行计,入口(本节点的分析容器)数着次数,到了上限交回它, + 不再算。""" limit = int(effective_settings(settings)["max_calls"]) - return CallError("max_calls", f"这次运行的分析次数已经用完(最多 {limit} 次):用已有的分析结论写,不再新做分析") + return CallError("max_calls", f"这个 Agent 这次执行最多做 {limit} 次分析,已经用完:用已有的分析结论写,不再新做分析") def make_prober(*args: Any, **kwargs: Any) -> Any: diff --git a/backend/app/analysis/contract.py b/backend/app/analysis/contract.py index 4212b817..5f1c4b8a 100644 --- a/backend/app/analysis/contract.py +++ b/backend/app/analysis/contract.py @@ -53,9 +53,10 @@ METHODS: dict[tuple[str, str | None], str] = { } #: agent 分析工具的名字(tools/builtin 里注册),一个算子一个工具:参数各自聚焦,模型少写错 TOOL_NAMES: dict[str, str] = {op: f"analysis_{op}" for op in OPS} -#: 算子、模式给人看的叫法:证据面板、写作目录、审计、导出、可验证报告、裁判摘录都用它(method_label 拼成一句) +#: 算子、模式给人看的叫法,只有这一份:证据面板、写作目录、审计、导出、可验证报告、裁判摘录、分析卡的校验与表单 +#: (engine/labels 的 ANALYSIS_OP_LABEL / ANALYSIS_MODE_LABEL 引用这里)、「{叫法}没有做成」的判定语都用它(method_label 拼成一句) OP_LABELS: dict[str, str] = { - "decompose": "变化拆解", "baseline": "波动基线", "trend": "趋势", "differ": "差异检验", "comparable": "可比性检查", + "decompose": "变化拆解", "baseline": "波动基线", "trend": "趋势判断", "differ": "差异检验", "comparable": "可比性检查", } MODE_LABELS: dict[str, str] = { "additive": "可加", "ratio": "比率", "factor": "乘积", "rate": "比率", "mean": "均值", "rows": "逐行明细", @@ -154,7 +155,7 @@ LABELS: dict[str, str] = { "min_n": "均值比较的最小样本数", "min_expected": "比率比较的最小期望频数", "min_denominator": "比率拆解的最小分母", - "max_calls": "一次运行最多分析几次", + "max_calls": "Agent 每次执行最多分析几次", "max_drill_depth": "最多下钻几层", "table_top_k": "列出前几段", } @@ -303,7 +304,8 @@ class AnalysisResult(TypedDict, total=False): class CallError(Exception): """调用错误(细案 01 §8.1):列不存在、列被遮罩、角色和目录度量类型冲突、同一键同一期多行、期次不在列里、乘积 - 不闭合、parent 不对、下钻超深、本运行分析次数用完。不记结论,message 交回 agent(一种说法,告诉它怎么改)。""" + 不闭合、parent 不对、下钻超深、这个 Agent 这次执行的分析次数用完(max_calls 按每个 Agent 每次执行计)。不记结论,message + 交回 agent(一种说法,告诉它怎么改)。""" def __init__(self, code: str, message: str) -> None: super().__init__(message) diff --git a/backend/app/analysis/phrases.py b/backend/app/analysis/phrases.py index 5c7f2412..0d2c8e11 100644 --- a/backend/app/analysis/phrases.py +++ b/backend/app/analysis/phrases.py @@ -26,11 +26,9 @@ from collections.abc import Iterable, Mapping, Sequence from fractions import Fraction from typing import Any +from app.analysis.contract import OP_LABELS from app.analysis.numbers import fixed, to_fraction -#: 算子的叫法:「算不了」的判定语、报错都用它 -OP_NAMES = {"decompose": "变化拆解", "baseline": "波动基线", "trend": "趋势判断", "differ": "差异检验", - "comparable": "可比性检查"} #: 附带说明进判定语的先后;不在这里的(measure_unlabeled、headline_basis)只在证据面板 PHRASE_ORDER = ("comparability", "simpson", "offset", "outlier_sensitive", "small_sample", "record_high", "record_low", "multiple_comparisons", "weekday_effect", "small_denominator", "null_values") @@ -322,7 +320,7 @@ def with_caveats(main: str, caveats: Sequence[Mapping[str, str]], *, skip: Itera def refused(op: str, reason: str) -> str: """算不了的判定语:{算子的叫法}没有做成:{原因}。""" - return f"{OP_NAMES[op]}没有做成:{reason}" + return f"{OP_LABELS[op]}没有做成:{reason}" # -------------------------------------------------------------------------- @@ -503,7 +501,7 @@ CRITERION_COMPARABLE = ("只差日期、窗口一样长、是整期、星期构 __all__ = [ - "CRITERION_COMPARABLE", "EMPTY_KEY", "HEADLINE_BASIS", "KEY_JOIN", "LABEL_MAX", "MEASURE_WORDS", "OP_NAMES", + "CRITERION_COMPARABLE", "EMPTY_KEY", "HEADLINE_BASIS", "KEY_JOIN", "LABEL_MAX", "MEASURE_WORDS", "PHRASE_CAVEATS", "PHRASE_ORDER", "UNVERIFIED_COMPLETE", "WEEKDAY_EFFECT", "amount", "band_text", "baseline_flat_phrase", "baseline_phrase", "caveat", "change", "clean_label", "comparable_phrase", "criterion_additive", "criterion_baseline", "criterion_differ", "criterion_factor", "criterion_ratio", diff --git a/backend/app/engine/labels.py b/backend/app/engine/labels.py index 4b1ea23e..b16ccee2 100644 --- a/backend/app/engine/labels.py +++ b/backend/app/engine/labels.py @@ -15,6 +15,8 @@ from __future__ import annotations from typing import Any +from app.analysis.contract import MODE_LABELS, MODES, OP_LABELS + #: 节点类型的叫法,和前端 lib/terms.ts 的 NODE_TYPE_LABEL 一致 TYPE_LABEL: dict[str, str] = { "input": "输入", "output": "成果", "llm": "模型调用", "agent": "Agent", @@ -106,13 +108,10 @@ _FIELD_BY_TYPE: dict[str, dict[str, str]] = { "challenge": CHALLENGE_LABEL, } -#: 分析卡一项里的分析方法、模式的叫法(app/analysis/contract.py 的 OPS、MODES;前端分析卡的表单照这一份) -ANALYSIS_OP_LABEL: dict[str, str] = {"decompose": "变化拆解", "baseline": "波动基线", "trend": "趋势", - "differ": "差异检验", "comparable": "可比性检查"} -ANALYSIS_MODE_LABEL: dict[str, dict[str, str]] = { - "decompose": {"additive": "可加(按维度算贡献)", "ratio": "比率(拆结构与自身)", "factor": "乘积(拆因子)"}, - "differ": {"rate": "比率", "mean": "均值", "rows": "逐行的值"}, -} +#: 分析卡一项里的分析方法、模式的叫法:只引用 app/analysis/contract.py 的 OP_LABELS / MODE_LABELS(按 MODES 分到各算子), +#: 不另写一份;前端分析卡的表单照这一份 +ANALYSIS_OP_LABEL: dict[str, str] = OP_LABELS +ANALYSIS_MODE_LABEL: dict[str, dict[str, str]] = {op: {m: MODE_LABELS[m] for m in modes} for op, modes in MODES.items()} #: 出具契约里的键(canvas/issues.ts 的 CONTRACT_KEYS、SUB_KEYS) CONTRACT_KEY_LABEL: dict[str, str] = {