mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:01:58 +00:00
分析算子给结论里以比率存的列和数记换算:契约加 ResultTable.formats、AnalysisResult.value_formats(换算同 [[m:]] 的后缀,另加 pp 表示百分点),各算子逐个填全——可加、乘积拆解的 pct、share;比率拆解的 w_*、r_* 记 pct,mix、rate、total 是比率之差记 pp;波动基线按变化率比较时的 change,趋势的相对斜率;差异检验比率模式的 a_value、b_value 记 pct,diff、ci_lo、ci_hi 记 pp;values 里的 pct_total、top_share、top_k_share、mix_share、rate_share 等。都为空时整项不写,结论和以前一字不差;run 与 recompute 逐字段相同、同输入两次 run 逐字节相同照旧成立(tests/test_analysis_run 的形状检查认这两项可选)
This commit is contained in:
@@ -64,13 +64,18 @@ def today_param(env: Env) -> str:
|
||||
def build(env: Env, params: dict[str, Any], used: Sequence[str], *, status: str = "ok",
|
||||
reason_code: str | None = None, reason: str | None = None, table: Mapping[str, Any] | None = None,
|
||||
values: Mapping[str, Any] | None = None, verdict: Mapping[str, Any] | None = None, criterion: str,
|
||||
phrase: str, caveats: Sequence[Mapping[str, str]] = ()) -> AnalysisResult:
|
||||
"""组装一份结论:形状严格按 contract.AnalysisResult,params 补上查过的目录事实。"""
|
||||
phrase: str, caveats: Sequence[Mapping[str, str]] = (),
|
||||
formats: Mapping[str, str] | None = None, named: Mapping[str, str] | None = None) -> AnalysisResult:
|
||||
"""组装一份结论:形状严格按 contract.AnalysisResult,params 补上查过的目录事实。formats 是结果表各列的换算(占比、
|
||||
变化率的列记 pct,比率的变化记 pp),named 是具名的数的换算(value_formats):都只留记了的,为空时整项不写。"""
|
||||
record = env.facts.record()
|
||||
out_params = {k: v for k, v in params.items() if v is not None}
|
||||
if record:
|
||||
out_params["catalog"] = record
|
||||
return {
|
||||
out_table = dict(table) if table is not None else None
|
||||
if out_table is not None and (table_formats := column_formats(formats or {})):
|
||||
out_table["formats"] = table_formats
|
||||
out: AnalysisResult = {
|
||||
"op": env.op,
|
||||
"mode": env.mode,
|
||||
"method": METHODS[(env.op, env.mode)],
|
||||
@@ -81,7 +86,7 @@ def build(env: Env, params: dict[str, Any], used: Sequence[str], *, status: str
|
||||
"status": status, # type: ignore[typeddict-item]
|
||||
"reason_code": reason_code,
|
||||
"reason": reason,
|
||||
"table": dict(table) if table is not None else None, # type: ignore[typeddict-item]
|
||||
"table": out_table, # type: ignore[typeddict-item]
|
||||
"values": dict(values or {}),
|
||||
"verdict": dict(verdict) if verdict is not None else None, # type: ignore[typeddict-item]
|
||||
"criterion": criterion,
|
||||
@@ -89,6 +94,9 @@ def build(env: Env, params: dict[str, Any], used: Sequence[str], *, status: str
|
||||
"caveats": [dict(c) for c in caveats], # type: ignore[misc]
|
||||
"hypothesis": env.hypothesis,
|
||||
}
|
||||
if named_formats := value_formats(named or {}):
|
||||
out["value_formats"] = named_formats
|
||||
return out
|
||||
|
||||
|
||||
def refuse(env: Env, params: dict[str, Any], used: Sequence[str], code: str, reason: str, *,
|
||||
@@ -155,9 +163,19 @@ def column_types(columns: Sequence[str], *, dims: Mapping[str, str | None] | Non
|
||||
return out
|
||||
|
||||
|
||||
def column_formats(formats: Mapping[str, str]) -> dict[str, str]:
|
||||
"""结果表的 formats(ResultTable.formats):列 id → 换算。只留记了换算的列,都为空时整项不写(结论和以前一字不差)。"""
|
||||
return {str(k): v for k, v in formats.items() if v}
|
||||
|
||||
|
||||
def value_formats(values: Mapping[str, Any]) -> dict[str, str]:
|
||||
"""结论的 value_formats(AnalysisResult.value_formats):键 → 换算。只留记了换算的数,都为空时整项不写。"""
|
||||
return {str(k): v for k, v in values.items() if v}
|
||||
|
||||
|
||||
def sign_word(value: Fraction | float | int) -> str | None:
|
||||
return "up" if value > 0 else "down" if value < 0 else None
|
||||
|
||||
|
||||
__all__ = ["DERIVED_PARAMS", "Env", "build", "closes", "column_label", "column_types", "label_for", "need", "refuse",
|
||||
"reserved_check", "sign_word", "today_param", "written_places"]
|
||||
__all__ = ["DERIVED_PARAMS", "Env", "build", "closes", "column_formats", "column_label", "column_types", "label_for",
|
||||
"need", "refuse", "reserved_check", "sign_word", "today_param", "value_formats", "written_places"]
|
||||
|
||||
@@ -277,6 +277,10 @@ class ResultTable(TypedDict):
|
||||
# 列 id → 类型(写法同查询快照的 column_types;第 1 波 W1-a 加,可选):维度列照输入快照里那一列查询时记下的类型
|
||||
# (文本型的码值 "1203" 才不会被渲染成「1,203」;输入没记类型的不写),数值列 number,说明、叫法这类文字列 text
|
||||
column_types: NotRequired[dict[str, str]]
|
||||
# 列 id → 换算(第 2 波加,可选):占比、变化率、权重这类以比率存的列,渲染时按它换算,换算的写法同 [[m:]] 的后缀
|
||||
# (pct 显示成百分数、pp 显示成「+1.2 个百分点」)。引用这一列的一格、展开整表时没写换算的按它渲染,写作者自己写了换算的
|
||||
# 照他的(引用上记下实际用的换算,导出、可验证报告照它渲染)
|
||||
formats: NotRequired[dict[str, str]]
|
||||
|
||||
|
||||
class AnalysisRequest(TypedDict, total=False):
|
||||
@@ -302,6 +306,9 @@ class AnalysisResult(TypedDict, total=False):
|
||||
reason: str | None
|
||||
table: ResultTable | None
|
||||
values: dict[str, Any] # 具名标量:[[a:A3.top_share]];数一律十进制字符串
|
||||
# 键 → 换算(第 2 波加,可选):占比、变化率这类以比率存的数,渲染时按它换算(写法同 ResultTable.formats)。
|
||||
# [[a:A3.top_share]] 没写换算的按它渲染,写作者自己写了换算的照他的
|
||||
value_formats: dict[str, str]
|
||||
verdict: Verdict | None
|
||||
criterion: str # 判定标准,一句,照实际阈值写
|
||||
phrase: str # 判定语(app/analysis/phrases 渲染),[[a:A3]] 渲染成它
|
||||
@@ -417,6 +424,16 @@ class ScopeBlock(TypedDict, total=False):
|
||||
#: 复算核对时不比的字段:hypothesis 是 agent 写的话,不是算出来的
|
||||
NOT_RECOMPUTED = frozenset({"hypothesis"})
|
||||
|
||||
#: 结论里记格式的两处:结果表的 formats(列 id → 换算)、values 的 value_formats(键 → 换算)。换算取值同 [[m:]] 的后缀,
|
||||
#: 另加 pp(比率的变化按百分点写:「+1.2 个百分点」)
|
||||
TABLE_FORMATS = "formats"
|
||||
VALUE_FORMATS = "value_formats"
|
||||
#: 比率的换算:pct 按百分数写(0.62 显示成 62.0%)
|
||||
CONV_PCT = "pct"
|
||||
#: 比率的变化按百分点写(0.012 显示成「+1.2 个百分点」,下降时带负号):[[m:]] 的后缀里原本没有这一种
|
||||
CONV_POINTS = "pp"
|
||||
FORMAT_CONVS: tuple[str, ...] = (CONV_PCT, CONV_POINTS)
|
||||
|
||||
|
||||
def same(a: Mapping[str, Any], b: Mapping[str, Any]) -> bool:
|
||||
"""两份结论(封存的、复算的)是否逐字段相同(hypothesis 除外)。"""
|
||||
@@ -425,8 +442,10 @@ def same(a: Mapping[str, Any], b: Mapping[str, Any]) -> bool:
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ARTIFACT_KIND", "CATALOG_PREFIX", "CAVEATS", "DEFAULTS", "LABELS", "LEDGER_KIND", "MARKER_KIND", "METHODS", "MODES",
|
||||
"MODE_LABELS", "NODE_TYPE", "NOT_RECOMPUTED", "OPS", "OP_LABELS", "RANGES", "REASONS", "SETTINGS_KEY", "TOOL_NAMES",
|
||||
"ARTIFACT_KIND", "CATALOG_PREFIX", "CAVEATS", "CONV_PCT", "CONV_POINTS", "DEFAULTS", "FORMAT_CONVS", "LABELS",
|
||||
"LEDGER_KIND", "MARKER_KIND", "METHODS", "MODES",
|
||||
"MODE_LABELS", "NODE_TYPE", "NOT_RECOMPUTED", "OPS", "OP_LABELS", "RANGES", "REASONS", "SETTINGS_KEY",
|
||||
"TABLE_FORMATS", "TOOL_NAMES", "VALUE_FORMATS",
|
||||
"VERDICTS", "AnalysisInput", "AnalysisRequest", "AnalysisResult", "CallError", "Caveat", "Context", "InputSnapshot",
|
||||
"MetricComparability", "Probe", "Prober", "ResultTable", "ScopeBlock", "ScopeItem", "Verdict", "catalog_result",
|
||||
"effective_settings", "is_result", "ledger_entry", "method_label", "same", "settings_problem",
|
||||
|
||||
@@ -29,7 +29,7 @@ from app.analysis import comparable as cmp
|
||||
from app.analysis import phrases as ph
|
||||
from app.analysis.common import (Env, build, closes, column_label, column_types, label_for, need, refuse,
|
||||
reserved_check, sign_word, today_param)
|
||||
from app.analysis.contract import CallError
|
||||
from app.analysis.contract import CONV_PCT, CONV_POINTS, CallError
|
||||
from app.analysis.inputs import Snap, as_list, hashable, same_value
|
||||
from app.analysis.numbers import canon, to_fraction
|
||||
|
||||
@@ -227,6 +227,17 @@ def _parent(raw: Mapping[str, Any], env: Env, mode: str, dims: int) -> tuple[dic
|
||||
return {"of": name, "key": key}, dict(zip(columns, hit)), depth
|
||||
|
||||
|
||||
#: 结果表里以比率存的列(占比、变化率、权重、比率本身):引用、展开时按百分数显示(ResultTable.formats 记 pct)
|
||||
_RATE_COLUMNS = ("pct", "share")
|
||||
|
||||
|
||||
def _rate_formats(columns: Sequence[str], rate: Sequence[str] = _RATE_COLUMNS,
|
||||
points: Sequence[str] = ()) -> dict[str, str]:
|
||||
"""结果表各列记的换算:rate 里的列(占比、变化率、权重、比率本身)记 pct,points 里的列(比率的变化)记 pp。"""
|
||||
return {**{c: CONV_PCT for c in rate if c in columns},
|
||||
**{c: CONV_POINTS for c in points if c in columns}}
|
||||
|
||||
|
||||
_FIXED_ADDITIVE = ("base", "target", "delta", "pct", "share", "flag")
|
||||
_FIXED_RATIO = ("n_base", "n_target", "d_base", "d_target", "w_base", "w_target", "r_base", "r_target", "mix", "rate",
|
||||
"total", "flag")
|
||||
@@ -470,7 +481,9 @@ def _additive_result(env: Env, params: dict[str, Any], used: list[str], segs: li
|
||||
skip = (("offset",) if code == "flat" else ()) + (("comparability",) if outcome is None else ())
|
||||
phrase = ph.with_caveats(main, caveats, skip=skip)
|
||||
return build(env, params, used, table=table, values=values, verdict=verdict,
|
||||
criterion=ph.criterion_additive(s, top_k_max=k_max, summed=summed), phrase=phrase, caveats=caveats)
|
||||
criterion=ph.criterion_additive(s, top_k_max=k_max, summed=summed), phrase=phrase, caveats=caveats,
|
||||
formats=_rate_formats(columns),
|
||||
named={k: CONV_PCT for k in ("pct_total", "top_share", "top_k_share")})
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -681,7 +694,13 @@ def _ratio_result(env: Env, params: dict[str, Any], used: list[str], segs: list[
|
||||
"direction": sign_word(delta) if code in ("mix_driven", "rate_driven", "both") else None}
|
||||
return build(env, params, used, table=table, values=values, verdict=verdict, criterion=criterion,
|
||||
phrase=ph.with_caveats(main, caveats, skip=("comparability",) if outcome is None else ()),
|
||||
caveats=caveats)
|
||||
caveats=caveats,
|
||||
# 权重、各段的比率按百分数显示;结构效应、自身效应、合计影响是比率的变化,按百分点
|
||||
formats=_rate_formats(columns, rate=("w_base", "w_target", "r_base", "r_target"),
|
||||
points=("mix", "rate", "total")),
|
||||
named={k: CONV_PCT for k in ("r_base_total", "r_target_total")}
|
||||
| {k: CONV_POINTS for k in ("delta", "mix_total", "rate_total")}
|
||||
| {"mix_share": CONV_PCT, "rate_share": CONV_PCT})
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -811,7 +830,8 @@ def factor(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
verdict = {"code": code, "subjects": subjects, "direction": sign_word(delta) if code != "flat" else None}
|
||||
return build(env, params, used, table=table, values=values, verdict=verdict, criterion=criterion,
|
||||
phrase=ph.with_caveats(main, caveats, skip=("comparability",) if outcome is None else ()),
|
||||
caveats=caveats)
|
||||
caveats=caveats, formats=_rate_formats(columns),
|
||||
named={k: CONV_PCT for k in ("pct_total", "top_share")})
|
||||
|
||||
|
||||
__all__ = ["HEADLINE_SHAPE", "PARENT_SHAPE", "SAME_QUERY", "SIMPSON_SHARE", "USED", "additive", "factor", "lmdi",
|
||||
|
||||
@@ -33,7 +33,7 @@ from typing import Any
|
||||
from app.analysis import phrases as ph
|
||||
from app.analysis import stats
|
||||
from app.analysis.common import Env, build, column_label, column_types, label_for, need, refuse, sign_word
|
||||
from app.analysis.contract import CallError
|
||||
from app.analysis.contract import CONV_PCT, CONV_POINTS, CallError
|
||||
from app.analysis.inputs import Snap, as_list, hashable, same_value
|
||||
from app.analysis.numbers import canon, to_fraction
|
||||
|
||||
@@ -389,8 +389,13 @@ def _decide(env: Env, params: dict[str, Any], used: list[str], pairs: list[Pair]
|
||||
"test": shown.test, "pairs": str(len(pairs)), "differs": str(len(hits))}
|
||||
verdict = {"code": code, "subjects": subjects, "direction": direction}
|
||||
phrase = ph.with_caveats(main, caveats, skip=("multiple_comparisons",) if len(tested) > 1 else ())
|
||||
# 比率模式的组的比率按百分数显示,两组的差、差的区间是比率之差,按百分点(均值模式是量,照原样)
|
||||
rate = ("a_value", "b_value") if mode == "rate" else ()
|
||||
points = ("diff", "ci_lo", "ci_hi") if mode == "rate" else ()
|
||||
return build(env, params, used, table=table, values=values, verdict=verdict, criterion=criterion, phrase=phrase,
|
||||
caveats=caveats)
|
||||
caveats=caveats, formats={**{c: CONV_PCT for c in rate}, **{c: CONV_POINTS for c in points}},
|
||||
named={**{k: CONV_PCT for k in ("a", "b")}, **{k: CONV_POINTS for k in ("diff", "ci_lo", "ci_hi")}}
|
||||
if mode == "rate" else {})
|
||||
|
||||
|
||||
def _sensitivity(pairs: list[Pair], mode: str, alpha: Fraction, min_expected: Fraction,
|
||||
|
||||
@@ -33,7 +33,7 @@ from app.analysis import phrases as ph
|
||||
from app.analysis import stats
|
||||
from app.analysis.common import (Env, build, column_label, column_types, label_for, need, refuse, reserved_check,
|
||||
sign_word, today_param)
|
||||
from app.analysis.contract import CallError
|
||||
from app.analysis.contract import CONV_PCT, CallError
|
||||
from app.analysis.inputs import Snap, same_value
|
||||
from app.analysis.numbers import canon, to_fraction
|
||||
|
||||
@@ -258,8 +258,13 @@ def baseline(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
"z": canon(z) if z is not None else None, "rank": str(stats.rank_desc(x, hv)), "n_history": str(n),
|
||||
"hist_min": canon(min(hv)), "hist_max": canon(max(hv))}
|
||||
verdict = {"code": code, "subjects": [], "direction": sign_word(x - m)}
|
||||
# 按变化率比时,本期的变化率、中位数、常规波动范围、历史最小最大都是变化率,按百分数显示;按值本身比时照原样
|
||||
change = ("change",) if basis == "change" else ()
|
||||
return build(env, params, used, table=table, values=values, verdict=verdict, criterion=criterion,
|
||||
phrase=ph.with_caveats(main, caveats, skip=("comparability",)), caveats=caveats)
|
||||
phrase=ph.with_caveats(main, caveats, skip=("comparability",)), caveats=caveats,
|
||||
formats={c: CONV_PCT for c in change},
|
||||
named={k: CONV_PCT for k in ("x", "median", "band_lo", "band_hi", "hist_min", "hist_max")
|
||||
if basis == "change"})
|
||||
|
||||
|
||||
def _compare_gap(series: Series, ordinal: int, lag: int, unit: str) -> str:
|
||||
@@ -360,7 +365,9 @@ def trend(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
verdict = {"code": code, "subjects": [], "direction": "up" if code == "rising" else "down" if code == "falling"
|
||||
else None}
|
||||
return build(env, params, used, table=table, values=values, verdict=verdict, criterion=criterion,
|
||||
phrase=ph.with_caveats(main, caveats, skip=("comparability",)), caveats=caveats)
|
||||
phrase=ph.with_caveats(main, caveats, skip=("comparability",)), caveats=caveats,
|
||||
# 相对斜率是比率(每期变百分之几),按百分数显示;斜率本身、p 值、各期之比照原样
|
||||
named={"rel_slope": CONV_PCT})
|
||||
|
||||
|
||||
def _tail_gap(series: Series, ordinal: int) -> str:
|
||||
|
||||
Reference in New Issue
Block a user