mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 01:11:59 +00:00
运行对比加固(收尾):区域等维度列设了遮罩时照样拿它核对两次引用的是不是同一批行,说明只写行号、不写值,挤出引用行段的和真的少了分得清,没挪的照常是数据变化;补单格反方向、归因维度按两边各自引用的行认、写入者改了配置时不严格拆分、并行支路查询四处守护用例
This commit is contained in:
@@ -1146,8 +1146,9 @@ class _Compare:
|
||||
归因说的南区就不是同一组数——归因按哪组维度说,就要按哪组维度核对。只按归因用的那组也不够:只引用了一行的,
|
||||
引用的行里每列都只有一个值,认不出维度;整份结果里分得清的维度(区域, 渠道)也要切在同一处。
|
||||
|
||||
遮罩列只用没遮的列认,说明里写的也只有没遮的列的值和行号,遮住的值不进说明;两组都分不出维度(没有不是数值的
|
||||
列、维度列都设了遮罩)的,只能按行号认,返回 None。快照读不出来的说不清:只有单格会走到这里(行段在 _citation
|
||||
先只用没遮的列认,说明里写的是没遮的列的值和行号;有列设了遮罩的,再连遮住的列一起核对一遍(区域设了遮罩时它
|
||||
往往正是唯一认得出行的那一列,只用没遮的列等于对它不核对),这一遍的说明只写行号、不写是哪个值。两遍都分不出
|
||||
维度(没有不是数值的列)的,只能按行号认,返回 None。快照读不出来的说不清:只有单格会走到这里(行段在 _citation
|
||||
里先核对整份结果,读不出来的已经说不清),那一行两次是不是同一个维度值核对不了,和行段一样不按行号当成同一处。
|
||||
快照里有的行和列对不上(格数不对、不是一行)的,认不出各行是哪个维度值,同样说不清。"""
|
||||
if qb.snap is None or qt.snap is None:
|
||||
@@ -1167,12 +1168,29 @@ class _Compare:
|
||||
for dims in dict.fromkeys(tuple(d) for d in (whole, used) if d):
|
||||
if (found := self._cut(name, what, b, t, rb, rt, list(dims))) is not None:
|
||||
return found
|
||||
if hidden and any(str(c).lower() in hidden for snap in (qb.snap, qt.snap) for c in snap.get("columns") or []):
|
||||
# 拿遮住的列核对过(哪怕最后没核对出不同):整体说明照样提一句有列设了遮罩
|
||||
self.masked_seen = True
|
||||
if hidden:
|
||||
# 遮罩列不当维度,是为了值不进说明;可它往往正是认得出行的那一列(区域设了遮罩),只拿没遮的列核对,等于对它
|
||||
# 不核对,挤出引用的行就和真的没有了分不出来。连遮住的列一起再核对一遍,说明里只写行号、不写值
|
||||
whole, _ = _dimensions(qb.snap, qt.snap, rb, rt, measure, frozenset())
|
||||
used, _ = _dimensions(qb.snap, qt.snap, _attributed_rows(qb.snap, b) or [],
|
||||
_attributed_rows(qt.snap, t) or [], measure, frozenset())
|
||||
for dims in dict.fromkeys(tuple(d) for d in (whole, used) if d):
|
||||
if (found := self._cut(name, what, b, t, rb, rt, list(dims), redact=True)) is not None:
|
||||
return found
|
||||
return None
|
||||
|
||||
def _cut(self, name: str, what: str, b: Any, t: Any, rb: list[tuple[int, dict[str, Any]]],
|
||||
rt: list[tuple[int, dict[str, Any]]], dims: list[str]) -> _Change | None:
|
||||
rt: list[tuple[int, dict[str, Any]]], dims: list[str], *, redact: bool = False) -> _Change | None:
|
||||
"""按 dims 这组维度核对两边引用的是不是同一批维度值(规则见 _left_out):是返回 None,不是返回说明。rb、rt 是
|
||||
两边整份结果的行。"""
|
||||
两边整份结果的行。redact:dims 里有设了遮罩的列,说明里不写是哪个维度值,只写行号。"""
|
||||
|
||||
def named(key: tuple[Any, ...]) -> str:
|
||||
return "其中一个维度值" if redact else _key_text(key)
|
||||
|
||||
masked_note = "(维度列设了遮罩,不写出是哪个值)" if redact else ""
|
||||
|
||||
def split(records: list[tuple[int, dict[str, Any]]], inp: Any) -> tuple[dict[Any, int], dict[Any, list[int]]]:
|
||||
"""{维度键: 引用的行里有几行},{维度键: [引用的行外面的行号]}。一个键可能占几行(去掉日期这类每期都不同的
|
||||
@@ -1209,9 +1227,9 @@ class _Compare:
|
||||
# 那一行两次都是它(单格只有一行,nb、nt 都是 1):还要是它同样的那一行,排在前面的行数一样
|
||||
if ahead(ob) == ahead(ot):
|
||||
continue
|
||||
return _Change("unknown", f"{name}两次都引用{cited},两次都是{_key_text(key)},可不是它同样的那一行"
|
||||
return _Change("unknown", f"{name}两次都引用{cited},两次都是{named(key)},可不是它同样的那一行"
|
||||
f"(它排在这一行前面的,基准运行有 {ahead(ob)} 行,这次有 {ahead(ot)} 行),"
|
||||
f"取的不是{what}")
|
||||
f"取的不是{what}{masked_note}")
|
||||
# 不是同一个维度值:看只在一边被引用的它在另一边的快照里还有没有——有(只是不在这一行)是换了一个数,
|
||||
# 没有是真的没有了、这次才有
|
||||
if not (ot if nb > nt else ob):
|
||||
@@ -1222,9 +1240,9 @@ class _Compare:
|
||||
continue
|
||||
if cb is not None and ct is not None and nb == nt:
|
||||
# 都只引用了它的一部分、行数也一样,可排在引用的行前面的行数不同:引用的不是它同样的那几行
|
||||
return _Change("unknown", f"{name}两次都引用{cited},两次都引用了{_key_text(key)}的 {nb} 行,可不是"
|
||||
return _Change("unknown", f"{name}两次都引用{cited},两次都引用了{named(key)}的 {nb} 行,可不是"
|
||||
f"它同样的那几行(它排在引用的行前面的,基准运行有 {cb[0]} 行,这次有 "
|
||||
f"{ct[0]} 行),取的不是{what}")
|
||||
f"{ct[0]} 行),取的不是{what}{masked_note}")
|
||||
# 说引用的行外面有它的那一边;两边都有的,说引用得少的那一边
|
||||
if ot and (not ob or nt < nb):
|
||||
where = (f"这次有 {len(ot)} 行在查询结果{_row_numbers(ot)}" if nt
|
||||
@@ -1232,8 +1250,8 @@ class _Compare:
|
||||
else:
|
||||
where = (f"在基准运行中有 {len(ob)} 行位于查询结果{_row_numbers(ob)}" if nb
|
||||
else f"在基准运行中位于查询结果{_row_numbers(ob)}")
|
||||
return _Change("unknown", f"{name}两次都引用{cited},{_key_text(key)}{where},不在引用的行里,"
|
||||
f"取的不是{what}")
|
||||
return _Change("unknown", f"{name}两次都引用{cited},{named(key)}{where},不在引用的行里,"
|
||||
f"取的不是{what}{masked_note}")
|
||||
return None
|
||||
|
||||
def _hidden(self, b: Any, t: Any) -> frozenset[str]:
|
||||
|
||||
@@ -1229,6 +1229,9 @@ def test_an_agent_citing_other_rows_is_not_a_data_change():
|
||||
|
||||
|
||||
#: 按区域排序的结果这次多了北区,排在南区前面,西区挪到第 4 行
|
||||
#: 区域列设了遮罩、西区挪出引用的第 1 至 3 行时的说明:只写行号,不写是哪个区域
|
||||
MASKED_MOVED = ("Agent「区域分析」两次都引用第 1 至 3 行,其中一个维度值这次仍在查询结果第 4 行,不在引用的行里,"
|
||||
"取的不是同一组数(维度列设了遮罩,不写出是哪个值)")
|
||||
WITH_NORTH = [["东区", 1200.5, 100.0], ["北区", 90.0, 0.0], ["南区", 800.0, 50.0], ["西区", 600.0, 0.0]]
|
||||
|
||||
|
||||
@@ -1292,12 +1295,15 @@ def test_the_same_rows_are_not_the_same_citation_when_a_cited_region_moved_out_o
|
||||
(["东区"], 1200.0, 1300.0, 2.0), (["南区"], 800.0, 700.0, -2.0), (["西区"], 600.0, 650.0, 1.0)]
|
||||
assert sum(exact(r["share"]) for r in rows) == 1
|
||||
|
||||
# 区域列设了遮罩:不拿它认是哪一区,只能按行号认,遮住的区域名不出现在结果里
|
||||
# 区域列设了遮罩:遮住的区域名不进说明,但照样拿它核对(它是唯一认得出行的那一列,不核对就和真的没有了分不出来)——
|
||||
# 说明只写行号
|
||||
def masked(run):
|
||||
run.masked = {a: frozenset({"region"}) for a in run.queries}
|
||||
return run
|
||||
result = run_compare(masked(cited("r1", with_middle, span=(0, 2))), masked(cited("r2", WITH_NORTH, span=(0, 2))))
|
||||
assert by_id(result)["total"]["classification"] == "data_only"
|
||||
total = by_id(result)["total"]
|
||||
assert total["classification"] == "incomparable" and total["attribution"] is None
|
||||
assert total["reasons"] == [MASKED_MOVED]
|
||||
text = json.dumps(result, ensure_ascii=False)
|
||||
assert all(region not in text for region in ("东区", "南区", "西区", "北区", "中区"))
|
||||
|
||||
@@ -1323,8 +1329,8 @@ def test_a_mask_on_one_side_only_still_hides_the_column():
|
||||
text = json.dumps(part, ensure_ascii=False)
|
||||
leaked = [r for r in ("东区", "南区", "西区", "北区", "中区") if r in text]
|
||||
assert not leaked, (label, where, leaked)
|
||||
# 区域认不出,只能按行号认(同 test_the_same_rows_are_not_the_same_citation_when_a_cited_region_moved_out_of_them)
|
||||
assert (total["classification"], total["reasons"]) == ("data_only", [compare.DATA_FROM_QUERIES]), label
|
||||
# 照样拿遮住的区域列核对出西区挪出了引用的行,说明只写行号(同上一条用例的遮罩那一段)
|
||||
assert (total["classification"], total["reasons"]) == ("incomparable", [MASKED_MOVED]), label
|
||||
# 整体说明照样提一句有列设了遮罩:归因少了区域这一列,看的人要知道是遮掉了,不是没有
|
||||
assert compare.MASKED_NOTE in result["notes"], label
|
||||
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
"""运行对比的守护用例:对抗复验找出的几处「拿掉规则、测试仍全绿」和一处遮罩下的放行。
|
||||
|
||||
- 区域列设了遮罩时,部分行挤出引用的行段照样核对得出(说明只写行号),和「真的少了」分得清;什么都没挪的照常是数据变化
|
||||
- 单格「同一行」的规则两个方向都守住(基准运行里那个区域只有引用的那一行)
|
||||
- 归因按哪组维度说,就按哪组维度在两边各自引用的行上核对
|
||||
- 写入输入的节点改了配置时,严格拆分不拆
|
||||
- 记不出写入者时,并行支路上的查询也算进「它上游变了什么」
|
||||
|
||||
夹具全部虚构,形状照 tests/fixtures/compare_runs.py。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from app.engine import compare
|
||||
from tests.fixtures.compare_runs import (
|
||||
BASE_ROWS, COLUMNS, NEXT_ROWS, TOOL, TYPES, WEEK37, Fake, amounts_in, chain, metric, node, region_sql, scalar_in,
|
||||
)
|
||||
|
||||
REGIONS = ("东区", "南区", "西区", "北区", "中区")
|
||||
|
||||
#: 区域 × 日期:日期每期都不同、不当维度,同一个区域占几行
|
||||
DAY = {"sql": "SELECT region, day, SUM(amount) AS gmv FROM orders GROUP BY region, day ORDER BY region, day",
|
||||
"columns": ["region", "day", "gmv"], "types": {"region": "text", "day": "date", "gmv": "number"}}
|
||||
#: 区域 × 渠道 × 日期
|
||||
CH = {"sql": "SELECT region, channel, day, SUM(amount) AS gmv FROM orders GROUP BY region, channel, day ORDER BY gmv DESC",
|
||||
"columns": ["region", "channel", "day", "gmv"],
|
||||
"types": {"region": "text", "channel": "text", "day": "date", "gmv": "number"}}
|
||||
|
||||
|
||||
def spanned(run_id, rows, span, *, columns, types, sql, masked=()):
|
||||
"""Agent 交一段行(span),口径卡对它求和。masked:这次运行每份快照都遮的列。"""
|
||||
run = Fake(run_id)
|
||||
run.tool("fetch", sql, columns, rows, types=types)
|
||||
bot = run.agent("bot", sql, columns, rows, types=types, span=span)
|
||||
first, last = span
|
||||
at = columns.index("gmv")
|
||||
run.card([metric("total", "sum(vars.kpi.amounts)", sum(r[at] for r in rows[first:last + 1]),
|
||||
[amounts_in(bot, rows, span=span)])])
|
||||
if masked:
|
||||
run.masked = {a: frozenset(masked) for a in run.queries}
|
||||
return run
|
||||
|
||||
|
||||
def element(run_id, rows, span, *, index=0, sql=None, columns=COLUMNS, types=TYPES):
|
||||
"""口径卡取 Agent 数组字段的第 index 个元素(单格)。"""
|
||||
run = Fake(run_id)
|
||||
sql = sql or region_sql()
|
||||
run.tool("fetch", sql, columns, rows, types=types)
|
||||
bot = run.agent("bot", sql, columns, rows, types=types, span=span)
|
||||
row, at = span[0] + index, columns.index("gmv")
|
||||
item = {"path": f"vars.kpi.amounts[{index}]", "value": rows[row][at], "node_id": "bot", "via": "agent_field",
|
||||
"field": f"amounts[{index}]", "ref": f"Q1.r{row}.gmv", "artifact": bot,
|
||||
"locator": {"row": row, "column": "gmv"}, "eid": f"ev:cell:{bot[:8]}:{row}:gmv", "status": "verified"}
|
||||
run.card([metric("first", f"vars.kpi.amounts[{index}]", rows[row][at], [item])])
|
||||
return run
|
||||
|
||||
|
||||
def compared(base, target):
|
||||
result = compare.compare(base.side(), target.side())
|
||||
json.dumps(result, allow_nan=False)
|
||||
return result
|
||||
|
||||
|
||||
def one(base, target, metric_id):
|
||||
return {m["metric_id"]: m for m in compared(base, target)["metrics"]}[metric_id]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- 遮罩下的核对
|
||||
|
||||
#: 东、南、西各两天;引用第 1 至 4 行
|
||||
SIX = [["东区", "09-01", 100.0], ["东区", "09-02", 100.0], ["南区", "09-01", 50.0], ["南区", "09-02", 50.0],
|
||||
["西区", "09-01", 30.0], ["西区", "09-02", 30.0]]
|
||||
#: 东区这次多了一天,南区的第二天挤到第 5 行(还在快照里,只是不在引用的行里)
|
||||
PUSHED = [["东区", "09-07", 100.0], ["东区", "09-08", 100.0], ["东区", "09-09", 100.0], ["南区", "09-08", 50.0],
|
||||
["南区", "09-09", 50.0], ["西区", "09-08", 30.0], ["西区", "09-09", 30.0]]
|
||||
#: 对照:南区这次真的只有一天
|
||||
FEWER = [["东区", "09-07", 100.0], ["东区", "09-08", 100.0], ["东区", "09-09", 100.0], ["南区", "09-08", 50.0],
|
||||
["西区", "09-08", 30.0], ["西区", "09-09", 30.0]]
|
||||
|
||||
|
||||
def test_a_masked_dimension_still_tells_rows_pushed_out_from_rows_gone():
|
||||
"""区域列设了遮罩(两边都遮、只有一边遮):它是唯一认得出行的那一列。以前遮了就不拿它认、只能按行号认,南区第二天
|
||||
挤出引用的行段和南区真的少了一天的输出一字不差,都是「数据变化」。现在照样拿它核对,挤出去的说不清,说明只写行号;
|
||||
真的少了的照常是数据变化。区域名在哪儿都不出现。"""
|
||||
for label, mb, mt in (("两边都遮", ("region",), ("region",)), ("只有这次遮", (), ("region",)),
|
||||
("只有基准遮", ("region",), ())):
|
||||
moved = compared(spanned("r1", SIX, (0, 3), **DAY, masked=mb), spanned("r2", PUSHED, (0, 3), **DAY, masked=mt))
|
||||
total = {m["metric_id"]: m for m in moved["metrics"]}["total"]
|
||||
assert total["classification"] == "incomparable" and total["attribution"] is None, label
|
||||
assert "第 5 行" in total["reasons"][0] and "维度列设了遮罩" in total["reasons"][0], label
|
||||
assert compare.MASKED_NOTE in moved["notes"], label
|
||||
gone = compared(spanned("r1", SIX, (0, 3), **DAY, masked=mb), spanned("r2", FEWER, (0, 3), **DAY, masked=mt))
|
||||
total = {m["metric_id"]: m for m in gone["metrics"]}["total"]
|
||||
assert total["classification"] == "data_only", (label, total["reasons"])
|
||||
for result in (moved, gone):
|
||||
text = json.dumps(result, ensure_ascii=False)
|
||||
assert not [r for r in REGIONS if r in text], label
|
||||
|
||||
|
||||
def test_a_masked_dimension_with_nothing_moved_is_still_a_data_change():
|
||||
"""遮罩下照样核对,但没挪的不能因为看不见值就一律说不清:两次引用的是同样几个区域的同样几天、只是金额变了,照常是
|
||||
数据变化(定时周报常见的取前几名、区域列设了遮罩,不能每周都说不清)。"""
|
||||
changed = [[r, d, v * 1.1] for r, d, v in SIX]
|
||||
total = one(spanned("r1", SIX, (0, 3), **DAY, masked=("region",)),
|
||||
spanned("r2", changed, (0, 3), **DAY, masked=("region",)), "total")
|
||||
assert total["classification"] == "data_only", total["reasons"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- 单格、归因维度
|
||||
|
||||
def test_an_element_whose_region_had_one_row_at_base_is_not_the_same_row():
|
||||
"""单格(Agent 两次都交第 2 至 3 行,口径卡取第一个元素即第 2 行):基准运行那是南区唯一的一天(50);这次东区没有
|
||||
数据,南区两天排在第 1、2 行,第 2 行是南区的第二天(45),同一天的南区其实是 60。两次都是南区,可不是同样的那一行,
|
||||
说不清——按行号认会写成 50 → 45,正负号都反了。基准运行那边南区只有引用的那一行时,「排在前面的行数」也要比。"""
|
||||
before = [["东区", "2026-09-01", 100.0], ["南区", "2026-09-01", 50.0], ["西区", "2026-09-01", 30.0]]
|
||||
after = [["南区", "2026-09-08", 60.0], ["南区", "2026-09-09", 45.0], ["西区", "2026-09-08", 30.0]]
|
||||
m = one(element("r1", before, (1, 2), **DAY), element("r2", after, (1, 2), **DAY), "first")
|
||||
assert m["classification"] == "incomparable", m["reasons"]
|
||||
m = one(element("r1", after, (1, 2), **DAY), element("r2", before, (1, 2), **DAY), "first")
|
||||
assert m["classification"] == "incomparable", m["reasons"]
|
||||
|
||||
|
||||
def test_attribution_dimensions_are_checked_on_each_side_own_cited_rows():
|
||||
"""渠道在引用的行里两边没有相同的值(不当维度),归因按区域说。南区这次有一行(线下)在引用的行外面:按两边各自
|
||||
引用的行认维度,查得出南区不是同一组数;只拿一边的引用行认维度(两边都用基准的、或都用这次的),会放过它,归因
|
||||
把南区写成 80 → 90,和南区真的只有一行的对照一字不差。两个方向都核对。"""
|
||||
base = [["东区", "线上", "09-01", 100.0], ["东区", "线下", "09-01", 50.0], ["南区", "线上", "09-01", 80.0],
|
||||
["西区", "线上", "09-01", 30.0]]
|
||||
this = [["东区", "团购", "09-08", 100.0], ["东区", "团购", "09-09", 60.0], ["南区", "团购", "09-08", 90.0],
|
||||
["南区", "线下", "09-08", 40.0], ["西区", "线上", "09-08", 30.0]]
|
||||
for b, t in ((base, this), (this, base)):
|
||||
m = one(spanned("r1", b, (0, 2), **CH), spanned("r2", t, (0, 2), **CH), "total")
|
||||
assert m["classification"] == "incomparable", m["reasons"]
|
||||
|
||||
|
||||
def test_an_element_on_a_misshapen_snapshot_is_not_taken_as_the_same():
|
||||
"""单格,一边快照里有一行和列对不上(少了一格):认不出各行是哪个维度值,说不清,不按行号当成同一个数。"""
|
||||
broken = [["北区", 90.0, 0.0], *NEXT_ROWS[:2], ["西区", 600.0]]
|
||||
for b, t in ((BASE_ROWS, broken), (broken, BASE_ROWS)):
|
||||
m = one(element("r1", b, (0, 2)), element("r2", t, (0, 2)), "first")
|
||||
assert m["classification"] == "incomparable" and "同一个维度值" in m["reasons"][0], m["reasons"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- 严格拆分与写入者
|
||||
|
||||
def test_no_strict_split_when_the_writer_of_an_input_was_reconfigured():
|
||||
"""口径 ×2 改成 ×3(v1 → v2),输入 vars.adj 由代码节点「调整」写入,这次「调整」也改了代码:旧口径用到的输入本身
|
||||
就因为配置变了而不同,拆成「口径效应 12、数据效应 4」会把代码改动带来的变化算成数据效应。不拆,说明原因。"""
|
||||
|
||||
def run(run_id, rows, value, factor, version, code):
|
||||
graph = chain(node("start", "input", "开始"), node("fetch", "tool", "区域取数", tool=TOOL, args={"sql": region_sql()}),
|
||||
node("calc", "code", "调整", code=code, assign_to="adj"),
|
||||
node("card", "metrics", "周报口径卡", caliber="周报口径", caliber_version=version),
|
||||
node("out", "output", "成果"))
|
||||
r = Fake(run_id, graph=graph)
|
||||
r.tool("fetch", region_sql(), rows=rows)
|
||||
r.code("calc", value, "adj")
|
||||
r.card([metric("calc", f"vars.adj * {factor}", value * factor,
|
||||
[scalar_in("vars.adj", value, "calc", "code")])], version=version)
|
||||
return r
|
||||
|
||||
m = one(run("r1", BASE_ROWS, 10, 2, "v1", "x = 1"), run("r2", NEXT_ROWS, 12, 3, "v2", "x = 2"), "calc")
|
||||
assert m["decomposition"] is None and "无法严格拆分" in m["decomposition_note"], m
|
||||
|
||||
|
||||
def test_an_unknown_writer_counts_queries_on_parallel_branches():
|
||||
"""记不出 vars.adj 的写入者:并行支路(开始 → 旁路取数 → 成果)上的查询在口径卡之前可能执行过,也算进它上游。只有
|
||||
旁路取数的结果变了是数据变化;旁路取数只是代入的日期不同,是参数变化。"""
|
||||
|
||||
def run(run_id, value, aside_rows, aside_sql):
|
||||
graph = chain(node("start", "input", "开始"),
|
||||
node("fetch", "tool", "区域取数", tool=TOOL, args={"sql": region_sql()}),
|
||||
node("calc", "code", "调整", code="x", assign_to="adj"),
|
||||
node("card", "metrics", "周报口径卡", caliber="周报口径", caliber_version="v1"),
|
||||
node("out", "output", "成果"))
|
||||
graph["nodes"].append(node("aside", "tool", "旁路取数", tool=TOOL, args={"sql": region_sql()}))
|
||||
graph["edges"] += [{"source": "start", "target": "aside"}, {"source": "aside", "target": "out"}]
|
||||
r = Fake(run_id, graph=graph)
|
||||
r.tool("fetch", region_sql(), rows=BASE_ROWS)
|
||||
r.tool("aside", aside_sql, rows=aside_rows)
|
||||
r.code("calc", value, "adj")
|
||||
r.card([metric("calc", "vars.adj * 2", value * 2, [scalar_in("vars.adj", value, None, "state")])])
|
||||
return r
|
||||
|
||||
m = one(run("r1", 10, BASE_ROWS, region_sql()), run("r2", 12, NEXT_ROWS, region_sql()), "calc")
|
||||
assert m["classification"] == "data_only", m["reasons"]
|
||||
m = one(run("r1", 10, BASE_ROWS, region_sql()), run("r2", 12, NEXT_ROWS, region_sql(WEEK37)), "calc")
|
||||
assert m["classification"] == "params_only", m["reasons"]
|
||||
Reference in New Issue
Block a user