Files
AgentLab/backend/tests/fixtures/catalog/large.py
T
ThearchyHelios 1a6082f033 大库的对象索引、探查范围和后台探查(next-round 03 A1),schema_view 预筛与按需加入、剖析事实的后端(A2、遗留 e 的底座)
为什么:几千张表的库以前用不了——探查每个源最多取 200 个对象、按数据库列出的顺序截断,同步请求、整份替换、不排队;
落在窗口外的表在目录、look、剖析里被说成「表已不在」。

做了什么:
- db/models_sources.py:source_objects(对象索引一行一个对象:范围、原因、结构指纹与 struct_version、first_seen / last_seen /
  removed_at、reflected_at、按需加入的留痕)、introspect_jobs / introspect_tasks(后台探查任务与逐表状态)。
- data/source_objects.py:每种方言一条字典查询建对象索引(失败退回 Inspector,上限 5 万,截断时不判删除);探查范围规则
  (options.introspect_scope:通配、按 name_key、排除优先、显式列出 / 移出;有目录的表、已发布模板写死 SQL 用到的表 pinned;
  命中超上限拒绝并说出命中数);唯一的 rebuild_schema_cache 从范围内已反射的行物化 schema_cache(形状不变,另加 scope 摘要);
  三种「不在」分开说(removed / missing / out_of_scope / reflect_failed);按需加入(当场只读反射、落盘、留痕、进程内按源加锁)。
- data/introspect_jobs.py:探查改成后台任务(core/job_runtime,lane kind introspect,和同源起草、剖析串行;按批反射、逐表记状态、
  可停止、读完的照样物化;重启标中断不续跑)。
- api/datasources.py:POST /introspect 返回 202 和任务,dry_run 同步只刷新索引、返回命中数;/introspect-jobs 读任务、停止;
  /introspect-scope 读、预览、保存规则;/schema?table= 带剖析事实、说清三种「不在」;DataSourceOut 加 schema_scope、introspect_job。
- schema_view:search_tables(BM25 倒排,遮罩列只用列名,范围外只按名字和注释、权重低一档)、profile_lines(剖析事实一节,
  附「只供写查询参考」,遮罩按渲染那一刻重滤,上传表格快照不一致时不给)、absent_detail。
- look:候选超过 PREFILTER_LIMIT 时只给预筛结果 + 已用到的表 + 一跳关联;点名或挑中范围外的表按需加入(每次查看最多 4 张),
  look 记录加 added_to_scope、prefiltered;表详情带剖析事实。
- tools/datasource:db_schema__ 加 keyword、不传 table 时只列前 50 张;点名范围外的表按需加入(每次运行最多 8 张);查询结果
  照实列「未按目录检查的表」(unchecked_tables,不新增检查规则);建工具时读剖析事实。
- 剖析:_relation_ends 分清「范围外,可以加入」和「表结构里没有」;任务记上传表格的 snapshot_id;last_facts。表助手、/schema
  用同一段剖析事实。
- 目录接口:清单只列范围内,absent_reason 分开说;GET /catalog/search 查对象索引,POST /catalog/scope/add 加入范围。
- 第 1 波留下的一处:check_valid_filter 和 stored_valid_filter 拼有效记录条件时按 quote_name 强制加引号。
- 工具参数纠错:空着的可选参数不止一个时,只认拼写明显接近的那一个(db_schema 多了 keyword 以后 tabel 仍能纠成 table)。
- 测试:3,000 张表的合成库(对象索引、缺省范围超上限拒绝、规则圈 400 张后台读完、停止、重启标中断、和剖析排队、同步起草
  409)、删除与结构版本、Inspector 回退、截断不判删除、范围规则、pinned、方言金样;改了同步探查的老测试改为等任务做完。
2026-10-16 14:17:06 +08:00

92 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""虚构的大库:3,000 张表(next-round 03 的大库验收夹具)。名字、注释、需求一律虚构。
- 2,990 张「同一种写法」的表:20 个业务域 × 15 种表 × 10 个分区(华东、华南……),表名 {域}_{表}_{分区},注释
「{域中文}{表中文}({分区中文})」。它们彼此只差几个字,正好是挑表时最容易混淆的那一类。
- 10 张目标表(TARGETS):名字和注释各不相同,每张配一句脚本化的需求。前 5 张给数据目录(中文名),有目录的表一律在探查
范围里(pinned);后 5 张在探查范围外,只有对象索引里的注释——SQLite 没有表注释,测试里直接写进对象索引的 comment,
模拟 MySQL 的 TABLE_COMMENT、PostgreSQL 的 obj_description。
每张表 5 列(id 主键、业务日期、金额、状态、所属域的编号),没有数据:大库的验收看的是对象索引、范围、挑表,不查数。
"""
from __future__ import annotations
import sqlite3
from dataclasses import dataclass
from pathlib import Path
DOMAINS = [
("ord", "订单"), ("inv", "库存"), ("pur", "采购"), ("sal", "销售"), ("fin", "财务"), ("hrm", "人事"),
("crm", "客户"), ("lgs", "物流"), ("prd", "商品"), ("mkt", "营销"), ("svc", "客服"), ("whs", "仓储"),
("pay", "支付"), ("rtn", "退货"), ("sto", "门店"), ("emp", "员工"), ("sup", "供应商"), ("prm", "促销"),
("mbr", "会员"), ("aud", "审计"),
]
ENTITIES = [
("hdr", "表头"), ("line", "明细"), ("log", "日志"), ("snap", "快照"), ("daily", "日汇总"), ("monthly", "月汇总"),
("cfg", "配置"), ("map", "映射"), ("hist", "历史"), ("tmp", "临时表"), ("bak", "备份"), ("stat", "统计"),
("dim", "维度"), ("fact", "事实"), ("queue", "队列"),
]
REGIONS = [
("east", "华东"), ("south", "华南"), ("north", "华北"), ("west", "西部"), ("central", "华中"),
("ne", "东北"), ("arch", "历史归档"), ("test", "测试"), ("y2019", "2019年"), ("y2020", "2020年"),
]
@dataclass(frozen=True)
class Target:
name: str
comment: str
need: str
#: 有数据目录(中文名)、一律在探查范围里
cataloged: bool
TARGETS = [
Target("member_points_ledger", "会员积分流水", "会员积分每个月发放了多少、消耗了多少", True),
Target("store_traffic_counter", "门店每日客流计数", "各门店上周每天的客流量", True),
Target("refund_reason_dict", "退货原因字典", "按退货原因统计退货单数", True),
Target("coupon_redemption", "优惠券核销记录", "优惠券的核销率", True),
Target("supplier_delivery_sla", "供应商到货时效", "供应商的到货准时率", True),
Target("warehouse_turnover_snap", "仓库库存周转快照", "各仓库的库存周转天数", False),
Target("shift_roster", "员工排班表", "下周员工的排班情况", False),
Target("complaint_ticket", "客户投诉工单", "客户投诉工单的平均处理时长", False),
Target("online_payment_flow", "线上支付流水", "线上支付的成功率", False),
Target("waybill_track", "物流运单签收轨迹", "物流运单从发货到签收用了多久", False),
]
TOTAL = len(DOMAINS) * len(ENTITIES) * len(REGIONS) - len(TARGETS) + len(TARGETS)
def generic_names() -> list[tuple[str, str]]:
"""2,990 张同一种写法的表 (表名, 注释):按域、表、分区的顺序,最后 10 个组合让给目标表。"""
out: list[tuple[str, str]] = []
for code, cn in DOMAINS:
for ent, ent_cn in ENTITIES:
for reg, reg_cn in REGIONS:
out.append((f"{code}_{ent}_{reg}", f"{cn}{ent_cn}({reg_cn})"))
return out[: len(out) - len(TARGETS)]
def comments() -> dict[str, str]:
"""全部 3,000 张表的注释(写进对象索引,模拟有表注释的数据库)。"""
out = dict(generic_names())
out.update({t.name: t.comment for t in TARGETS})
return out
def build(path: Path | str) -> Path:
path = Path(path)
db = sqlite3.connect(path)
for name, _comment in generic_names():
code = name.split("_", 1)[0]
db.execute(f"CREATE TABLE {name} (id INTEGER PRIMARY KEY, biz_date TEXT, amount REAL, status TEXT, "
f"{code}_id INTEGER)")
for t in TARGETS:
db.execute(f"CREATE TABLE {t.name} (id INTEGER PRIMARY KEY, biz_date TEXT, amount REAL, status TEXT, "
"ref_id INTEGER)")
db.commit()
db.close()
return path
__all__ = ["DOMAINS", "ENTITIES", "REGIONS", "TARGETS", "TOTAL", "Target", "build", "comments", "generic_names"]