mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:11:59 +00:00
合入 W1-a 补丁:快照列类型的标注照实际形状;结果表带列类型(文本码值不加千分位)
This commit is contained in:
@@ -52,7 +52,8 @@ def _compute(op: str, mode: str | None, raw: Mapping[str, Any], env: Any) -> Ana
|
||||
from app.analysis import comparable, decompose, differ, series
|
||||
|
||||
if op == "decompose":
|
||||
return {"additive": decompose.additive, "ratio": decompose.ratio, "factor": decompose.factor}[mode](raw, env) # type: ignore[index]
|
||||
modes = {"additive": decompose.additive, "ratio": decompose.ratio, "factor": decompose.factor}
|
||||
return modes[mode](raw, env) # type: ignore[index]
|
||||
if op == "baseline":
|
||||
return series.baseline(raw, env)
|
||||
if op == "trend":
|
||||
|
||||
@@ -128,9 +128,24 @@ def closes(total: Fraction, cell: Fraction, raw: Any) -> bool:
|
||||
return scale != 0 and gap <= scale * Fraction(1, 10 ** 9)
|
||||
|
||||
|
||||
def column_types(columns: Sequence[str], *, dims: Mapping[str, str | None] | None = None,
|
||||
texts: Sequence[str] = ()) -> dict[str, str]:
|
||||
"""结果表各列的类型(ResultTable.column_types,按列的先后):dims 里的列照给的类型(维度列照输入快照里那一列查询时记下的
|
||||
类型,没记的为 None、不写,同快照「拿不准的列不在里面」);texts 里的是文字列(说明、叫法)写 text;其余都是数值列写
|
||||
number(格子是十进制字符串)。"""
|
||||
out: dict[str, str] = {}
|
||||
for column in columns:
|
||||
if dims is not None and column in dims:
|
||||
if dims[column]:
|
||||
out[column] = dims[column] # type: ignore[assignment]
|
||||
else:
|
||||
out[column] = "text" if column in texts else "number"
|
||||
return out
|
||||
|
||||
|
||||
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", "label_for", "need", "refuse", "sign_word",
|
||||
"today_param", "written_places"]
|
||||
__all__ = ["DERIVED_PARAMS", "Env", "build", "closes", "column_label", "column_types", "label_for", "need", "refuse",
|
||||
"sign_word", "today_param", "written_places"]
|
||||
|
||||
@@ -435,9 +435,11 @@ def table_of(outcome: Outcome) -> dict[str, Any]:
|
||||
"""检查表(结论的 table):检查项、结果、基期、本期、说明。"""
|
||||
rows = [[CHECK_NAMES[c.id], STATUS_NAMES[c.status], c.base, c.target, ";".join(c.reasons) or None]
|
||||
for c in outcome.checks]
|
||||
return {"columns": ["check", "status", "base", "target", "note"],
|
||||
columns = ["check", "status", "base", "target", "note"]
|
||||
return {"columns": columns,
|
||||
"labels": {"check": "检查项", "status": "结果", "base": "基期", "target": "本期", "note": "说明"},
|
||||
"rows": rows}
|
||||
"rows": rows,
|
||||
"column_types": dict.fromkeys(columns, "text")}
|
||||
|
||||
|
||||
def values_of(outcome: Outcome) -> dict[str, Any]:
|
||||
|
||||
@@ -13,7 +13,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable, Mapping
|
||||
from datetime import date
|
||||
from typing import Any, Literal, TypedDict
|
||||
from typing import Any, Literal, NotRequired, TypedDict
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 名字(一种说法:别处一律引用这里,不另写字面量)
|
||||
@@ -184,7 +184,9 @@ class InputSnapshot(TypedDict, total=False):
|
||||
source: str # 数据源名
|
||||
sql: str
|
||||
columns: list[str]
|
||||
column_types: list[str | None]
|
||||
# 查询时记下的列类型(number / text / date / datetime / time / boolean,拿不准的列不在里面)。tools 的 to_payload 给的是
|
||||
# {列: 类型}(data/engine.QueryResult.column_types);按列序排的列表也认(analysis/inputs.Snap.kind 两种都读)
|
||||
column_types: dict[str, str] | list[str | None]
|
||||
rows: list[list[Any]]
|
||||
truncated: bool
|
||||
masked: list[str] # 不能当维度、度量的列(遮罩列、按 SQL 认出改了名字的遮罩列),比较时不分大小写
|
||||
@@ -244,6 +246,9 @@ class ResultTable(TypedDict):
|
||||
columns: list[str] # 稳定的列 id(维度列照输入的列名,其余是 base、target、delta、share……)
|
||||
labels: dict[str, str] # 列 id → 给人看的列名(基期、本期、变化、占变化的比重……)
|
||||
rows: list[list[Any]] # 数一律十进制字符串或 None,维度值照原样
|
||||
# 列 id → 类型(写法同查询快照的 column_types;第 1 波 W1-a 加,可选):维度列照输入快照里那一列查询时记下的类型
|
||||
# (文本型的码值 "1203" 才不会被渲染成「1,203」;输入没记类型的不写),数值列 number,说明、叫法这类文字列 text
|
||||
column_types: NotRequired[dict[str, str]]
|
||||
|
||||
|
||||
class AnalysisRequest(TypedDict, total=False):
|
||||
|
||||
@@ -26,7 +26,8 @@ from typing import Any
|
||||
|
||||
from app.analysis import comparable as cmp
|
||||
from app.analysis import phrases as ph
|
||||
from app.analysis.common import Env, build, closes, column_label, label_for, need, refuse, sign_word, today_param
|
||||
from app.analysis.common import (Env, build, closes, column_label, column_types, label_for, need, refuse, sign_word,
|
||||
today_param)
|
||||
from app.analysis.contract import CallError
|
||||
from app.analysis.inputs import Snap, as_list, hashable, same_value
|
||||
from app.analysis.numbers import canon, to_fraction
|
||||
@@ -390,10 +391,12 @@ def _additive_result(env: Env, params: dict[str, Any], used: list[str], segs: li
|
||||
d = deltas[x.key]
|
||||
rows.append([*x.key, canon(x.base), canon(x.target), canon(d), canon(d / x.base) if x.base != 0 else None,
|
||||
canon(shares[x.key]) if shares else None, "、".join(x.flags) or None])
|
||||
table = {"columns": [*by, *_FIXED_ADDITIVE],
|
||||
columns = [*by, *_FIXED_ADDITIVE]
|
||||
table = {"columns": columns,
|
||||
"labels": {**dict(zip(by, dims)), "base": "基期", "target": "本期", "delta": "变化", "pct": "变化率",
|
||||
"share": "占变化的比重", "flag": "说明"},
|
||||
"rows": rows}
|
||||
"rows": rows,
|
||||
"column_types": column_types(columns, dims={c: side_t.snap.kind(c) for c in by}, texts=("flag",))}
|
||||
values: dict[str, Any] = {
|
||||
"base_total": canon(B), "target_total": canon(T), "delta_total": canon(delta),
|
||||
"pct_total": canon(delta / abs(B)) if B != 0 else None, "top_key": ph.key_text(top.key),
|
||||
@@ -472,7 +475,7 @@ def ratio(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
ph.say(ph.key_text(x.key), f"{when}的", den_label, "为 0 而", num_label,
|
||||
"不为 0,比率拆不了"), criterion=criterion)
|
||||
return _ratio_result(env, params, used, segs, by, dims, label, num_label, den_label, heads, parent_row, outcome,
|
||||
criterion)
|
||||
criterion, target_snap=ts)
|
||||
|
||||
|
||||
def ratio_effects(segs: Sequence[tuple[Fraction, Fraction, Fraction, Fraction]]
|
||||
@@ -501,7 +504,7 @@ def ratio_effects(segs: Sequence[tuple[Fraction, Fraction, Fraction, Fraction]]
|
||||
def _ratio_result(env: Env, params: dict[str, Any], used: list[str], segs: list[Segment], by: list[str],
|
||||
dims: list[str], label: str, num_label: str, den_label: str,
|
||||
heads: Mapping[str, tuple[Fraction, Any]], parent_row: Mapping[str, Any] | None,
|
||||
outcome: cmp.Outcome | None, criterion: str) -> Any:
|
||||
outcome: cmp.Outcome | None, criterion: str, *, target_snap: Snap) -> Any:
|
||||
main_share, flat_pct = env.threshold("main_share"), env.threshold("flat_pct")
|
||||
min_den = env.threshold("min_denominator")
|
||||
live = [x for x in segs if x.base[1] != 0 or x.target[1] != 0]
|
||||
@@ -589,12 +592,14 @@ def _ratio_result(env: Env, params: dict[str, Any], used: list[str], segs: list[
|
||||
for x in dropped:
|
||||
rows.append([*x.key, canon(x.base[0]), canon(x.target[0]), "0", "0", None, None, None, None, None, None, None,
|
||||
"两期分母都为 0,不计入"])
|
||||
table = {"columns": [*by, *_FIXED_RATIO],
|
||||
columns = [*by, *_FIXED_RATIO]
|
||||
table = {"columns": columns,
|
||||
"labels": {**dict(zip(by, dims)), "n_base": ph.say("基期", num_label), "n_target": ph.say("本期", num_label),
|
||||
"d_base": ph.say("基期", den_label), "d_target": ph.say("本期", den_label),
|
||||
"w_base": "基期权重", "w_target": "本期权重", "r_base": "基期比率", "r_target": "本期比率",
|
||||
"mix": "结构效应", "rate": "自身效应", "total": "合计影响", "flag": "说明"},
|
||||
"rows": rows}
|
||||
"rows": rows,
|
||||
"column_types": column_types(columns, dims={c: target_snap.kind(c) for c in by}, texts=("flag",))}
|
||||
values: dict[str, Any] = {
|
||||
"r_base_total": canon(R), "r_target_total": canon(R2), "delta": canon(delta), "mix_total": canon(mix_total),
|
||||
"rate_total": canon(rate_total), "mix_share": canon(mix_share), "rate_share": canon(rate_share),
|
||||
@@ -717,10 +722,12 @@ def factor(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
b, t = cells["base"][i + 1][0], cells["target"][i + 1][0]
|
||||
rows.append([names[i], canon(b), canon(t), canon((t - b) / b), canon(contrib[i]),
|
||||
canon(shares[i]) if shares[i] is not None else None])
|
||||
table = {"columns": ["factor", "base", "target", "pct", "contrib", "share"],
|
||||
columns = ["factor", "base", "target", "pct", "contrib", "share"]
|
||||
table = {"columns": columns,
|
||||
"labels": {"factor": "因子", "base": "基期", "target": "本期", "pct": "变化率", "contrib": "贡献",
|
||||
"share": "占变化的比重"},
|
||||
"rows": rows}
|
||||
"rows": rows,
|
||||
"column_types": column_types(columns, texts=("factor",))}
|
||||
values = {"base_total": canon(M), "target_total": canon(M2), "delta_total": canon(delta),
|
||||
"pct_total": canon(delta / abs(M)), "top_key": names[top],
|
||||
"top_share": canon(shares[top]) if shares[top] is not None else None}
|
||||
@@ -729,4 +736,5 @@ def factor(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
phrase=ph.with_caveats(main, caveats), caveats=caveats)
|
||||
|
||||
|
||||
__all__ = ["HEADLINE_SHAPE", "PARENT_SHAPE", "SAME_QUERY", "SIMPSON_SHARE", "additive", "factor", "lmdi", "ratio", "ratio_effects"]
|
||||
__all__ = ["HEADLINE_SHAPE", "PARENT_SHAPE", "SAME_QUERY", "SIMPSON_SHARE", "additive", "factor", "lmdi", "ratio",
|
||||
"ratio_effects"]
|
||||
|
||||
@@ -28,7 +28,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, label_for, need, refuse, sign_word
|
||||
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.inputs import Snap, as_list, hashable, same_value
|
||||
from app.analysis.numbers import canon, to_fraction
|
||||
@@ -181,7 +181,7 @@ def differ(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
pairs = _pairs(raw, params, groups, mode, dim_label, by_period, group_col) # type: ignore[arg-type]
|
||||
env.inputs.use(snap, "data", [group_col, *cols.values()],
|
||||
sorted({r for p in pairs for g in (p.a, p.b) for r in g.rows}))
|
||||
return _decide(env, params, used, pairs, mode, label, criterion) # type: ignore[arg-type]
|
||||
return _decide(env, params, used, pairs, mode, label, criterion, kind=snap.kind(group_col)) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def _groups(snap: Snap, group_col: str, cols: Mapping[str, str], mode: str, by_period: bool,
|
||||
@@ -274,7 +274,7 @@ def _pairs(raw: Mapping[str, Any], params: dict[str, Any], groups: dict[Any, Gro
|
||||
|
||||
|
||||
def _decide(env: Env, params: dict[str, Any], used: list[str], pairs: list[Pair], mode: str, label: str,
|
||||
criterion: str) -> Any:
|
||||
criterion: str, *, kind: str | None) -> Any:
|
||||
alpha = env.threshold("differ_alpha")
|
||||
min_expected = env.threshold("min_expected") if mode == "rate" else Fraction(0)
|
||||
for pair in pairs:
|
||||
@@ -324,12 +324,16 @@ def _decide(env: Env, params: dict[str, Any], used: list[str], pairs: list[Pair]
|
||||
rows.append([p.a.name, p.b.name, canon(p.a_value), canon(p.b_value), canon(p.diff),
|
||||
canon(p.ci[0]) if p.ci else None, canon(p.ci[1]) if p.ci else None, str(p.a.n), str(p.b.n),
|
||||
TESTS.get(p.test or "") or None, canon(p.p), canon(p.p_adj), "、".join(flags) or None])
|
||||
table = {"columns": ["a", "b", "a_value", "b_value", "diff", "ci_lo", "ci_hi", "a_n", "b_n", "test", "p", "p_adj",
|
||||
"flag"],
|
||||
columns = ["a", "b", "a_value", "b_value", "diff", "ci_lo", "ci_hi", "a_n", "b_n", "test", "p", "p_adj", "flag"]
|
||||
# 组、对照组照分组列(期次列)在快照里的类型;和「其余各组合计」比时对照组那一列有一格是叫法,写 text
|
||||
rest = any(p.b.key == ("__rest__",) for p in pairs)
|
||||
table = {"columns": columns,
|
||||
"labels": {"a": "组", "b": "对照组", "a_value": f"组的{value_name}", "b_value": f"对照组的{value_name}",
|
||||
"diff": "差", "ci_lo": "差的区间下限", "ci_hi": "差的区间上限", "a_n": "组的样本量",
|
||||
"b_n": "对照组的样本量", "test": "检验", "p": "p 值", "p_adj": "校正后的 p 值", "flag": "说明"},
|
||||
"rows": rows}
|
||||
"rows": rows,
|
||||
"column_types": column_types(columns, dims={"a": kind, "b": "text" if rest else kind},
|
||||
texts=("test", "flag"))}
|
||||
shown = head if head is not None else pairs[0]
|
||||
values = {"a": canon(shown.a_value), "b": canon(shown.b_value), "diff": canon(shown.diff),
|
||||
"ci_lo": canon(shown.ci[0]) if shown.ci else None, "ci_hi": canon(shown.ci[1]) if shown.ci else None,
|
||||
|
||||
@@ -512,6 +512,6 @@ __all__ = [
|
||||
"factor_driver", "key_text", "multiple_text", "names", "no_evidence_phrase", "null_periods_text",
|
||||
"null_segments_text", "number", "offset_text", "outlier_text", "pct", "pick_label", "points", "ratio_both",
|
||||
"ratio_change", "ratio_mix", "ratio_rate", "ratio_value", "record_text", "refused", "say", "share_words",
|
||||
"simpson_text", "small_denominator_text", "threshold_pct", "small_sample_mean_text", "small_sample_rate_text", "trend_phrase",
|
||||
"unconfirmed_text", "unlabeled_text", "with_caveats",
|
||||
"simpson_text", "small_denominator_text", "small_sample_mean_text", "small_sample_rate_text", "threshold_pct",
|
||||
"trend_phrase", "unconfirmed_text", "unlabeled_text", "with_caveats",
|
||||
]
|
||||
|
||||
@@ -29,7 +29,7 @@ from app.analysis import comparable as cmp
|
||||
from app.analysis import periods as pd
|
||||
from app.analysis import phrases as ph
|
||||
from app.analysis import stats
|
||||
from app.analysis.common import Env, build, column_label, label_for, need, refuse, sign_word, today_param
|
||||
from app.analysis.common import Env, build, column_label, column_types, label_for, need, refuse, sign_word, today_param
|
||||
from app.analysis.contract import CallError
|
||||
from app.analysis.inputs import Snap, same_value
|
||||
from app.analysis.numbers import canon, to_fraction
|
||||
@@ -198,10 +198,13 @@ def baseline(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
main = ph.baseline_phrase(code, series.label, current, n, unit, ph.band_text(lo, hi, basis=basis))
|
||||
rows = [[p.raw, canon(lv), canon(h) if basis == "change" else None, "历史"] for p, h, lv in history]
|
||||
rows.append([target.raw, canon(x_level), canon(x) if basis == "change" else None, "本期"])
|
||||
table = {"columns": [series.period, "value", "change", "role"],
|
||||
columns = [series.period, "value", "change", "role"]
|
||||
table = {"columns": columns,
|
||||
"labels": {series.period: column_label(env, series.snap, series.period), "value": series.label,
|
||||
"change": "变化率", "role": "说明"},
|
||||
"rows": rows}
|
||||
"rows": rows,
|
||||
"column_types": column_types(columns, dims={series.period: series.snap.kind(series.period)},
|
||||
texts=("role",))}
|
||||
values = {"x": canon(x), "value": canon(x_level), "median": canon(m), "band_lo": canon(lo), "band_hi": canon(hi),
|
||||
"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))}
|
||||
@@ -285,10 +288,12 @@ def trend(raw: Mapping[str, Any], env: Env) -> Any:
|
||||
main = ph.trend_phrase(code, series.label, n, unit, per, slope_text,
|
||||
(run, direction) if run >= 3 and direction else None) # type: ignore[arg-type]
|
||||
rows = [[p_.raw, canon(v), canon(v - tail[i - 1][1]) if i else None] for i, (p_, v) in enumerate(tail)]
|
||||
table = {"columns": [series.period, "value", "change"],
|
||||
columns = [series.period, "value", "change"]
|
||||
table = {"columns": columns,
|
||||
"labels": {series.period: column_label(env, series.snap, series.period), "value": series.label,
|
||||
"change": "比上一期"},
|
||||
"rows": rows}
|
||||
"rows": rows,
|
||||
"column_types": column_types(columns, dims={series.period: series.snap.kind(series.period)})}
|
||||
values = {"slope": canon(slope), "rel_slope": canon(rel) if rel is not None else None, "p": canon(mk.p),
|
||||
"s": str(mk.s), "n": str(n), "run_length": str(run), "run_direction": direction}
|
||||
verdict = {"code": code, "subjects": [], "direction": "up" if code == "rising" else "down" if code == "falling"
|
||||
|
||||
@@ -35,6 +35,7 @@ async def test_two_weeks_that_differ_only_in_dates_are_comparable():
|
||||
assert result["params"]["today"] == "2026-10-21" and result["params"]["measure"] == "gmv"
|
||||
assert [p["column"] for p in result["probes"]] == ["order_date"]
|
||||
assert result["table"]["columns"] == ["check", "status", "base", "target", "note"]
|
||||
assert result["table"]["column_types"] == dict.fromkeys(result["table"]["columns"], "text")
|
||||
|
||||
|
||||
async def test_other_literals_differing_mean_two_segments_not_two_periods():
|
||||
|
||||
@@ -47,6 +47,8 @@ async def test_a_driver_is_named_with_its_share_and_the_whole_shape_is_filled():
|
||||
table = result["table"]
|
||||
assert table["columns"] == ["region", "base", "target", "delta", "pct", "share", "flag"]
|
||||
assert table["labels"]["region"] == "地区" and table["labels"]["share"] == "占变化的比重"
|
||||
assert table["column_types"] == {"region": "text", "base": "number", "target": "number", "delta": "number",
|
||||
"pct": "number", "share": "number", "flag": "text"}
|
||||
assert [r[0] for r in table["rows"]] == ["华东", "华北", "华南", "西南"] # 按变化的绝对值从大到小
|
||||
assert result["settings"] == {"main_share": 0.5, "concentration": 0.8, "offset_ratio": 0.25, "flat_pct": 0.005,
|
||||
"table_top_k": 10}
|
||||
@@ -418,3 +420,31 @@ async def test_the_same_query_cannot_be_both_periods():
|
||||
assert error.code == "same_query"
|
||||
error = await call_error("comparable", snaps=[weekly()], base_query="Q2", target_query="Q2")
|
||||
assert error.code == "same_query"
|
||||
|
||||
|
||||
async def test_dimension_columns_keep_the_type_recorded_at_query_time():
|
||||
"""文本型的码值(门店编码 "1203")在结果表里仍是 text,渲染时不会写成「1,203」;查询时没记类型的维度列不写。"""
|
||||
sql = "SELECT store_code, week, SUM(amount) AS gmv FROM orders GROUP BY 1, 2"
|
||||
rows = [["1203", "W1", 500], ["1203", "W2", 300], ["0871", "W1", 200], ["0871", "W2", 260]]
|
||||
typed = snap("Q2", sql, ["store_code", "week", "gmv"], rows,
|
||||
types={"store_code": "text", "week": "text", "gmv": "number"})
|
||||
result = await analyze("decompose", "additive", snaps=[typed], query="Q2", by="store_code", period="week",
|
||||
base="W1", target="W2", measure="gmv")
|
||||
assert result["table"]["column_types"]["store_code"] == "text"
|
||||
assert [r[0] for r in result["table"]["rows"]] == ["1203", "0871"]
|
||||
untyped = snap("Q2", sql, ["store_code", "week", "gmv"], rows)
|
||||
result = await analyze("decompose", "additive", snaps=[untyped], query="Q2", by="store_code", period="week",
|
||||
base="W1", target="W2", measure="gmv")
|
||||
assert "store_code" not in result["table"]["column_types"]
|
||||
assert result["table"]["column_types"]["delta"] == "number"
|
||||
listed = snap("Q2", sql, ["store_code", "week", "gmv"], rows, types=None, column_types=["text", "text", "number"])
|
||||
result = await analyze("decompose", "additive", snaps=[listed], query="Q2", by="store_code", period="week",
|
||||
base="W1", target="W2", measure="gmv")
|
||||
assert result["table"]["column_types"]["store_code"] == "text" # 按列序的列表也认
|
||||
ratio_result = await ratio([["线上", "W41", 50, 1000], ["线上", "W42", 80, 1000], ["线下", "W41", 20, 1000],
|
||||
["线下", "W42", 26, 1000]])
|
||||
ratio_types = ratio_result["table"]["column_types"]
|
||||
assert ratio_types["flag"] == "text" and ratio_types["mix"] == "number" and "channel" not in ratio_types
|
||||
factor_result = await factor([["W41", "1000", 100, "10"], ["W42", "1320", 120, "11"]])
|
||||
assert factor_result["table"]["column_types"] == {"factor": "text", "base": "number", "target": "number",
|
||||
"pct": "number", "contrib": "number", "share": "number"}
|
||||
|
||||
@@ -53,6 +53,9 @@ async def test_several_groups_each_against_the_rest_with_holm():
|
||||
table = result["table"]
|
||||
assert [r[0] for r in table["rows"]] == ["西南", "华东", "华南", "华北"]
|
||||
assert table["rows"][0][1] == "其余各地区"
|
||||
# 对照组那一列有「其余各地区」这样的叫法:写 text;没记类型的分组列(组那一列)不写
|
||||
assert table["column_types"]["b"] == "text" and "a" not in table["column_types"]
|
||||
assert table["column_types"]["p"] == "number" and table["column_types"]["test"] == "text"
|
||||
raw = [Fraction(r[10]) for r in table["rows"]]
|
||||
adjusted = [Fraction(r[11]) for r in table["rows"]]
|
||||
assert all(a >= p for a, p in zip(adjusted, raw))
|
||||
|
||||
@@ -57,7 +57,10 @@ def _assert_shape(result):
|
||||
assert all(isinstance(s, list) and all(isinstance(x, str) for x in s) for s in result["verdict"]["subjects"])
|
||||
assert result["verdict"]["direction"] in (None, "up", "down")
|
||||
table = result["table"]
|
||||
assert set(table) == {"columns", "labels", "rows"} and set(table["labels"]) == set(table["columns"])
|
||||
assert set(table) == {"columns", "labels", "rows", "column_types"}
|
||||
assert set(table["labels"]) == set(table["columns"]) and set(table["column_types"]) <= set(table["columns"])
|
||||
assert list(table["column_types"]) == [c for c in table["columns"] if c in table["column_types"]]
|
||||
assert set(table["column_types"].values()) <= {"number", "text", "date", "datetime", "time", "boolean"}
|
||||
assert all(len(r) == len(table["columns"]) for r in table["rows"])
|
||||
assert all(v is None or isinstance(v, str) for v in result["values"].values())
|
||||
assert all(set(cv) == {"code", "text"} and cv["code"] in c.CAVEATS for cv in result["caveats"])
|
||||
|
||||
@@ -56,6 +56,7 @@ async def test_level_basis_with_hand_computed_median_and_mad():
|
||||
assert result["params"]["grain"] == "seq" and result["params"]["lag"] == 1
|
||||
assert result["settings"] == {"k_warn": 2.0, "k_alert": 3.0, "min_history": 8, "max_history": 52}
|
||||
assert result["table"]["rows"][-1][-1] == "本期"
|
||||
assert result["table"]["column_types"] == {"week": "text", "value": "number", "change": "number", "role": "text"}
|
||||
|
||||
|
||||
async def test_change_basis_within_outside_and_far_outside():
|
||||
@@ -167,6 +168,7 @@ async def test_rising_and_falling_trends_with_run_length():
|
||||
assert rising["phrase"].startswith("过去 12 周线上销售额呈上升趋势(每周约 +")
|
||||
assert ",已连续 11 周上升" in rising["phrase"]
|
||||
assert rising["settings"] == {"trend_min_points": 6, "trend_alpha": 0.05, "max_history": 52}
|
||||
assert rising["table"]["column_types"] == {"week": "text", "value": "number", "change": "number"}
|
||||
falling = await trend(weeks([200, 196, 197, 190, 185, 186, 180, 181, 175, 170]))
|
||||
assert falling["verdict"]["code"] == "falling"
|
||||
assert falling["values"]["run_length"] == "2" and "已连续" not in falling["phrase"]
|
||||
|
||||
Reference in New Issue
Block a user