mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 22:21:59 +00:00
ce71f7d5c1
复核第 1 轮(abb2707)找到四处:写作目录按证据面板宁可多遮的那一套认指标出处,SQL 认不准时没碰号码列的销售额也成了
「已遮罩」,正式运行引用它就判失败(相对基线回退);「紧挨着负号」连破折号「——」「—」和汉字「一」都算,「运费统一10元」
「合计——7,036.50元」原样导出变红(相对基线回退);紧挨着改字只堵住了数字、负号、小数点,接「万」、画删除线、从右往左
书写的字母、l / I / O 照样全绿,根子在总览把无法核对的项也给绿色;第二张口径卡转抄遮罩指标(nodes.<卡>.metrics[0].value、
vars.<assign_to>.metrics[0].value、nodes.<卡>.text)时号码照样进写作目录、文档证据目录和可验证报告。
取自遮罩格的指标只按确定的那一套认(写作目录、报告自查、可验证报告同一个判断)
- 写作目录(api/evidence.py 的 withheld_metrics)认指标出处时遮罩列只认确定取自遮罩列的(_InputMasks 的 sure:读得出来的
快照按单元格引用自查的 _cited_masks,读不出来的按数据源现在设的列名),和 [[v:…]] 引用同一格的自查同一个口径:WITH 写了
列名表、VALUES 当表、子查询里有没起别名的号码表达式、只装着分档标签的列,指标照常显示、照常引用;改了名字、套了函数的
号码列照旧「已遮罩」。证据面板、推演照旧宁可多遮。报告自查按目录里记的 masked 认,跟着同一个判断
- 可验证报告认老文档(证据目录里还写着原值,整份只给哈希)按同一个判断:api/bundle.py 先按证据面板的那一套认好每份查询
快照里确定取自遮罩列的列和这次运行查到过的遮罩值(certain_masks),交给 build_bundle(sure_columns、sure_secrets),
_Store.metric_word 的 sure 按它们认。认口径卡能不能随文件附带照旧宁可多遮(卡上记着输入的值),SQL 认不准的卡同基线只给 id
转抄上游口径卡的指标按上游那个指标认
- recompute.card_input 认出输入转抄了哪张卡的哪几个指标:nodes.<卡>.metrics[i].value、vars.<卡的 assign_to>.metrics[i]、
整个指标、输入、渲染算转抄那个指标;整张卡、清单文字、整份指标列表、下标不是字面量的同整份查询结果(值里有遮罩值才不给,
数指标个数的照给);只取名称、单位、口径名的不算
- 证据面板 / 写作目录(_InputMasks._copied)、可验证报告(_Store._copied_kind)都用同一个 _metric_masks / metric_word 往上追,
上游取自遮罩格,转抄的也不给;追了 CARD_DEPTH(8)张还没追到头(循环里抄自己上一轮的产出)的宁可多遮
- 写作目录读快照前把运行状态里每张口径卡的输入一并读好,不在事件循环里读盘
显示文字核对(verifier.js)
- 判不通过只剩紧挨着的数字、小数点,和真当负号用的三个(半角连字符减号、数学减号、全角减号)紧挨着数字开头、前面又不是
字母数字时(「SKU-12」「A-3」的连字符是编号的一部分)。破折号(——、—、–、―,小号的、竖排的)、波浪线、汉字「一」、
假名长音、制表符横线这些易混字当普通的字。连字符、不断行连字符、上下标负号、粗减号这类平时不当负号用的短横紧挨着数字
开头判无法核对(以前判不通过;基线也是无法核对)
- 判无法核对(带原因):后面接着量级单位(万、亿、千、百、兆、萬、億、%、‰、‱、k、K、M、B 及全角,中间隔着空格也算;
核对过的字以「万」「亿」结尾再接一个也算),核对过的字画着删除线或紧挨着画了删除线的字,紧挨着长得像数字的字母和数字
符号(l、I、O、上下标数字、圈码、罗马数字、汉字〇),从右往左书写的字母和方向控制字符(照旧)
- 总览(overallOf):一项无法核对都没有、每份报告都核对上才是绿色的「全部通过」;有无法核对的写中性的「可核对的项全部通过,
另有 N 项无法核对」,下面醒目地写明这个文件判断不了真伪,要在系统中核对封存(运行 ID、GET /api/runs/<id>/verify、定时任务
通知里的清单哈希前 12 位)。以前无法核对的项照样给绿色,伪造者只要让读者看到的那个数落进无法核对就行
- 交出 layoutOf 给 node 里逐条核排法;开发数据 39 份报告 1591 处、验收数据 58 份报告 1695 处引用按新规则扫过,没有一处判
不通过或无法核对
测试
- 新增 tests/test_report_masked_metric_sure.py(四种 SQL 认不准的销售额、分档标签照给照引、确定取自号码列的照旧遮、老文档
照常附带、写作目录和可验证报告逐个指标同一个判断)、tests/test_report_masked_card_copies.py(三种转抄写法在写作目录、文档、
自查、证据面板、可验证报告里都不给号码,没有报告的运行、老文档,转抄普通指标照给,认转抄的各种写法,追不到头的宁可多遮)、
tests/test_bundle_prose_export.py(「统一」「——」「—」「SKU-」「共…家」「–」真跑报告撰写节点、从接口导出,全部通过、总览绿)
- test_bundle_shown:负号只认三个、前面是汉字的照判不通过;像负号的短横、量级单位、删除线、长得像数字的字母各自判无法核对、
总览中性;正常写法原样导出全绿;总览只在一项无法核对都没有时是绿的
- 样本页新增 prose(写作者自己写在引用旁边的普通文字,原样导出全绿);check-bundle 补数学减号、紧挨着汉字加负号判不通过,
接万(隔空格)、接亿元、接 %、接 k、订单号接万、画删除线另写一个数、前面加小写 L、后面接大写 O、长得像负号的连字符判无法
核对,各自点名原因、总览中性并写明去系统里核对;有无法核对项的样本页(base、legacy、continued、missing-card、masked-card、
masked-source、nan)总览改断言中性,从右往左书写的字母那条不再断言总览是绿的
- 每处改动都做了「去掉实现、测试变红」:写作目录不用 sure、老文档不用 sure、sure 列不用、转抄认不出(三处各自)、追不到头
按核对不了、负号的字母数字例外、负号恢复成 \p{Pd} 全部、数学减号全角减号不算、量级单位、隔空格、以万结尾、删除线、像数字
的字母、像负号的短横、总览有无法核对照样绿
102 lines
5.5 KiB
Python
102 lines
5.5 KiB
Python
"""原样导出的可验证报告:写作者自己写在引用旁边的普通文字不能把核对过的数判成不通过、无法核对,总览要是绿的。
|
||
|
||
复核第 1 轮发现(相对基线的回退):显示文字核对把「紧挨着负号」改判不通过,负号却连破折号「——」「—」和汉字「一」都算上了。
|
||
写作者写「全场运费统一[[m:fee]],消费合计——[[m:total]]。」,服务端自查通过、正式运行成功,原样导出断网打开,总览却是
|
||
「有 2 项不通过」。现在只有真当负号用的三个(半角、数学减号、全角)、前面又不是字母数字的才判不通过;破折号、汉字「一」
|
||
当普通的字;总览只有一项无法核对都没有才是绿的。这里真跑一遍报告撰写节点、从接口导出,再在 node 里执行页面上的核对脚本。
|
||
|
||
数据全是虚构的。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sqlite3
|
||
|
||
import pytest
|
||
from httpx import ASGITransport, AsyncClient
|
||
from sqlalchemy import select
|
||
|
||
from app.data.engine import engines
|
||
from app.db.base import SessionLocal
|
||
from app.db.models import DataSource
|
||
from app.engine.runner import run_manager
|
||
from app.main import app
|
||
from tests.test_bundle_masked_metrics import data_of
|
||
from tests.test_bundle_shown import verdicts
|
||
from tests.test_report_masked_metrics import events, finish, node, script
|
||
|
||
#: 写作者的写法:引用前面紧挨着「统一」「——」「—」「–」「SKU-」「共」,后面紧挨着「家」「元」
|
||
PROSE = ("全场运费统一[[m:fee]],消费合计——[[m:total]],退款—[[m:refund]],共[[m:stores]]家门店,"
|
||
"热销款 SKU-[[v:Q1.r0.sku]],单价区间 [[v:Q1.r0.price]]–[[v:Q1.r1.price]]。")
|
||
|
||
|
||
@pytest.fixture
|
||
async def engine_up():
|
||
await run_manager.setup()
|
||
yield
|
||
await run_manager.shutdown()
|
||
|
||
|
||
@pytest.fixture
|
||
async def client(engine_up):
|
||
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
|
||
yield c
|
||
|
||
|
||
@pytest.fixture
|
||
async def shop(tmp_path, engine_up):
|
||
"""门店库:没设遮罩。"""
|
||
path = tmp_path / "shop.db"
|
||
db = sqlite3.connect(path)
|
||
db.executescript("CREATE TABLE items (sku INTEGER, price REAL);"
|
||
"CREATE TABLE stores (id INTEGER, amount REAL, refund REAL);")
|
||
db.executemany("INSERT INTO items VALUES (?, ?)", [(1207, 19.9), (1311, 39.5)])
|
||
db.executemany("INSERT INTO stores VALUES (?, ?, ?)", [(1, 1234.5, -20.5), (2, 2345.25, 0), (3, 3456.75, -8)])
|
||
db.commit()
|
||
db.close()
|
||
async with SessionLocal() as session:
|
||
row = (await session.execute(select(DataSource).where(DataSource.name == "n1shop"))).scalar_one_or_none()
|
||
if row is None:
|
||
row = DataSource(name="n1shop", kind="sqlite", database=str(path), readonly=True, enabled=True)
|
||
session.add(row)
|
||
row.database, row.kind, row.enabled, row.readonly, row.options = str(path), "sqlite", True, True, {}
|
||
await session.commit()
|
||
source_id = row.id
|
||
await engines.invalidate(source_id)
|
||
yield path
|
||
await engines.invalidate(source_id)
|
||
|
||
|
||
async def test_ordinary_writing_next_to_citations_exports_all_green(client, monkeypatch, shop):
|
||
"""报告撰写节点照写作者的稿子组装(正式运行,违规处置判失败),自查通过;导出的可验证报告里每个数字都核对通过,总览
|
||
是绿色的「全部通过」。"""
|
||
script(monkeypatch, PROSE)
|
||
metric = {"format": "thousands", "decimals": 2}
|
||
nodes = [
|
||
node("start", "input"),
|
||
node("q", "tool", tool="db_query__n1shop", args={"sql": "SELECT sku, price FROM items ORDER BY sku"}),
|
||
node("q2", "tool", tool="db_query__n1shop", args={
|
||
"sql": "SELECT SUM(amount) AS total, SUM(refund) AS refund, COUNT(*) AS stores, 10 AS fee FROM stores"}),
|
||
node("card", "metrics", caliber="门店口径", caliber_version="v1", metrics=[
|
||
{"id": "fee", "name": "运费", "unit": "元", "format": "plain", "expression": "cell(nodes.q2, 0, 'fee')"},
|
||
{"id": "total", "name": "消费合计", "unit": "元", **metric, "expression": "cell(nodes.q2, 0, 'total')"},
|
||
{"id": "refund", "name": "退款", "unit": "元", **metric, "expression": "cell(nodes.q2, 0, 'refund')"},
|
||
{"id": "stores", "name": "门店数", "format": "plain", "expression": "cell(nodes.q2, 0, 'stores')"}]),
|
||
node("write", "report", instructions="写一句", on_violation="fail"),
|
||
node("out", "output", fields=[{"name": "报告", "value": "{{ nodes.write.text }}"}]),
|
||
]
|
||
spec = {"nodes": nodes, "edges": [{"source": a["id"], "target": b["id"]} for a, b in zip(nodes, nodes[1:])]}
|
||
row = await finish(spec)
|
||
assert row.status == "succeeded", row.error
|
||
[checked] = [e.data for e in await events(row.id) if e.type == "report.checked"]
|
||
assert checked["ok"] is True and checked["repairs"] == 0, checked
|
||
resp = await client.get(f"/api/runs/{row.id}/bundle.html")
|
||
assert resp.status_code == 200, resp.text
|
||
got = verdicts(data_of(resp.text))
|
||
shown = {n["ref"]: (n["text"], n["result"]) for n in got["numbers"]}
|
||
assert shown == {
|
||
"m:fee": ("10元", "pass"), "m:total": ("7,036.50元", "pass"), "m:refund": ("-28.50元", "pass"),
|
||
"m:stores": ("3", "pass"), "v:Q1.r0.sku": ("1,207", "pass"), "v:Q1.r0.price": ("19.9", "pass"),
|
||
"v:Q1.r1.price": ("39.5", "pass"),
|
||
}, shown
|
||
assert got["overall"]["result"] == "pass" and got["overall"]["text"] == "全部通过", got["overall"]
|