关于 tabular-cleanup
tabular-cleanup 是用于自主清理杂乱表格的技能,先推断并由用户确认数据契约,再按确定性检查修复数据。它仅保留满足约束且不引发回退的转换,并生成可重放的管道和审计记录。
主要功能
- 01推断并确认严格的数据契约
- 02将规则编译为确定性违规检查
- 03每轮应用一个 pandas 转换
- 04拒绝导致其他检查退步的更改
- 05将已接受的更改写入可重放管道
- 06生成概况、日志和最终报告
适用场景
- 01统一不一致的类别拼写
- 02修复类型、格式和越界值
- 03在保留必要行列的前提下清理表格
- 04建立可审计、可重复的数据清理流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ gaasher/ agent-loop-skills --skill tabular-cleanup安装后在 Claude Code 中输入/tabular-cleanup确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/run.example.yamlschemas/ledger.schema.jsonprofile.schema.json详细介绍
表格清理循环
一个单一 Agent,将杂乱的数据转储(<artifact>)清理到最可靠的状态,
运行后不需要人工介入。目标是检查清单,而非评分:Agent
推断数据契约,将其编译为确定性的二元检查(每项报告
违规数量,绝不报告加权浮点数),然后在每次迭代中分析表格,选出
最严重的未解决检查,应用一个 pandas 转换来解决它,并且只有在该检查的违规数严格减少且没有附带损害时才保留转换。每项获接受的转换都会追加到
可重放的 pipeline.py;每次尝试都会记录到 ledger。整个工作分为
结构(正确解析、整理成单一表格、类型合理)→ 契约合成(将每个观察到的异常转为检查)→ 修复循环。契约合成决定了质量的成败:分析器发现了问题,却从未将其编译为检查(典型情况是同一类别有多种拼写),问题就会悄然遗留——脏数据上的检查清单却全绿。检查读取的是存储的值,因此规范化是循环必须完成的实际工作,不能耍检查时的小把戏。
适用场景
当你需要自主清理杂乱表格,使其符合推断并经确认的数据契约时,使用此工具;每个缺陷都必须是确定性检查,并由循环将其修复为零违规或诚实地保留未解决项。默认采用严格的契约推断(宽松契约让脏数据快速“全绿”是最主要的失败模式);唯一的人工检查点是在设置阶段确认契约,之后循环会一直运行到满足停止条件。不适用于对已干净的数据集进行开放式探索、诊断某个已知异常,或根据来源核查外部声明——这些属于分析循环;本循环会改写数据以使其符合契约。
设置
以交互方式解析各项绑定。如果工作目录中存在 loop.run.yaml,则加载该文件,用一行确认各项值,然后跳转到循环。否则:在 Claude Code 中(可用 AskUserQuestion 工具)推断每项绑定的可能值,并将其作为推荐选项;在其他宿主环境中,逐项询问纯文本问题,并加上引号。然后编写 loop.run.yaml
(格式:examples/run.example.yaml),并在创建任何其他文件前确认各项值。
契约(见下文)是用户必须主动批准的唯一决策——先推断契约,再获得明确同意;之后的一切均由系统自主完成。
| 绑定 | 含义 | 默认值 | 推断方法 |
|---|---|---|---|
<artifact> | 要清理的杂乱表格;只读(v0 是副本) | — | 扫描工作目录中的数据文件;根据扩展名检测 <format> + 分隔符/编码/表头/引号 |
<contract> | 推断出的逐列契约 + 跨列规则 + 护栏;是判定“正确”的唯一依据 | — | 分析原始数据,然后合成契约(见下文)并确认 |
<retention_floor> | 累计的 unique_rows_kept / unique_rows_in_v0 必须 ≥ 此值(分母为 v0 去重后的行数) | 0.95 | — |
<protected_columns> | 必须保留的列;删除其中任意一列都需要明确许可 | 全部(*) | — |
<impute_cap> | 一列中最多可被插补(插入合成替代值)的单元格比例 | 0.20 | — |
<analysis_cmd> | 在用户环境中运行分析/转换代码的解释器(已安装 pandas) | python3 | 工作目录中的 pyproject.toml/.venv/uv |
<sandbox_root> | tcl/(版本、转换、分析结果、账本、流水线)的存放位置 | ./sandbox | — |
<gate> / <budget> | 仅作后备限制:iterations/tokens/time 及其上限 | iterations / 30 | — |
分析和转换代码会通过 <analysis_cmd> 在用户的环境中运行,因此可以使用 pandas。辅助代码应优先使用标准库:通过 try/except ImportError 探测大型依赖,缺少时采用降级方案,或在征得同意后执行 uv pip install "pandas==<ver>"——绝不假设 pandas 已安装。
合约合成(成败关键的一步——不要跳过)
先分析原始数据,然后分两部分将观察结果转化为合约:
第 1 部分——结构。 确认解析正确(分隔符/编码/表头),且数据是单个整洁表格(每列一个变量、每个单元格一个值、每行一条观测——无合并单元格、嵌套子表或多值单元格),列名/数据类型合理。表格解析错误会让所有列检查失去意义;结构缺陷也要转化为变换/检查。
第 2 部分——逐列合约 + 规则 + 护栏。 对每列确定其语义类型和规范形式,然后输出:数据类型、是否可为空?、是否为键?、范围(数值最小值/最大值)、正则表达式(格式)、规范类别 + merge_map(变体 → 规范值,作为修复指导,不是检查时的替换)、严重性(键/必填列默认为 high,否则为 normal),以及可选的异常值方法。添加数据所体现的跨列规则(例如 start ≤ end)。然后添加上述护栏(retention_floor、protected_columns、impute_cap)。以下三条规则可确保可靠性:承诺——每个已观察到的异常都必须编译为检查,或明确说明豁免原因(同一值的多种拼写形式必须进行 categories 检查,绝不能继续作为自由文本);规范形式即存储值——声明规范集合会产生开放违规项,循环会通过重写将其清除;严格性优先——不确定时,添加更严格的检查(过于严格很容易撤销;错误检查比缺少检查更糟,因此绝不要臆测数据未体现的列含义)。
与用户确认推断出的合约(Claude Code:通过 AskUserQuestion 以紧凑表格呈现;其他情况:以 YAML 输出并请求确认/修改)。用户签字确认后,将合约和编译后的检查清单写入 <sandbox_root>/tcl/schema.yaml;此后本次运行中的合约固定不变。完整合约结构见:examples/run.example.yaml。
检查清单
每条合约规则都会编译为一个二元检查,用于报告违规数和状态。没有加权浮点值,也没有 epsilon——仅这一点就消除了所有分母歧义。
| 检查 ID(模式) | 维度 | 违规计数条件 |
|---|---|---|
<col>.required | 完整性 | nullable:false 的单元格为空 |
<col>.type | 有效性 | 非空单元格无法解析为合约指定的数据类型 |
<col>.range | 有效性 | 非空数值单元格超出 [min,max] |
<col>.regex | 有效性 | 非空单元格不符合格式正则表达式 |
<col>.categories | 一致性 | 非空的存储值不是规范类别之一 |
<rule_id>(跨列) | 一致性 | 某行违反跨列规则 |
<col>.key | 唯一性 | 声明为键的值有重复 |
rows.unique | 唯一性 | 某行与另一行完全重复 |
<col>.outlier (可选启用) | 合理性 | 数值单元格为统计异常值(仅当声明了方法时) |
检查状态: pass(0 项违规)· open(仍有违规,仍可处理)· residual(仍有违规,但在不违反护栏且不导致其他检查退步的前提下无法修复——这是已接受并报告的死胡同)。空值仅在 .required 检查中算作违规——.type/.range/.regex 检查会忽略空值。待处理检查的优先级:先按 severity 从高到低,再按违规数从多到少。标题指标 checks_passing% = checks_in_pass / total_checks 仅用于 report.md 和状态行——绝不用于决定保留/回退。
*诚实的局限:*检查清单衡量的是格式良好且自洽,而非真实准确性(“John Smith”是否为正确姓名?)——异常值/范围检查只是合理性的替代指标。
循环
设 <best> 为当前接受的版本(从 v0 开始,它是 <artifact> 的精确副本)。
复制此检查清单并逐项勾选,在 <best> 上不断迭代,直到触发停止条件:
- 分析
<best>— 运行确定性分析代码 →profiles/profile-vN.json(下文提供架构):每列统计信息 + 每项检查的违规数 + 状态 +retention+ 概要checks_passing%;撰写 4–8 行人工总结。 - 选择目标并提出一项转换 — 在
open检查中按优先级选择(先看严重程度,高的优先,再看违规数最多的)。说明检查 id、违规数、修复策略和预期效果。绝不要提出无法对应到特定open检查的转换。 - 编写转换,作为纯
transform(df) -> dfpandas 函数,以确定性方式仅处理目标检查所需的内容(之后必须能在pipeline.py中独立运行)。 - 应用 → 候选版本 — 加载
<best>,应用转换并写入versions/vN+1.<ext>。出错时修复一次;若仍然失败,则记录status=crash并丢弃(不推进<best>)。 - 重新分析候选版本 — 对
vN+1重新运行检查清单,获取新的违规数 +retention。 - 保留或回退(纯检查清单逻辑,不使用 epsilon) — 仅当目标检查的违规数严格减少、其他检查的违规数均未增加且没有触发任何护栏时才接受。否则回退。若这是该目标最后一个满足护栏要求的策略,则将其标记为
residual。 - 记录 + 持久化 — 在
ledger.tsv中追加一行。如果保留:将函数复制到transforms/tNN_<slug>.py,并按顺序将其调用追加到pipeline.py。 - 检查是否停止 — 如果没有触发任何停止条件,则基于新的
<best>进入下一轮迭代。
最小破坏原则(循环的倾向): 优先修复而非删除。按以下顺序尝试,并采用第一个满足护栏要求且不会导致回归的策略:(a) 修复(解析/标准化/规范化值),(b) 填补(不超过 <impute_cap>,并标记为合成数据 — 在 nullable:true 列中将无法恢复的值置空属于修复,而非填补,不受上限限制),(c) 删除(删除行/列,但须遵守保留率 + 受保护下限)。删除是最后手段。只有当三种策略都确实失败时,检查才变为 residual — 例如,nullable:false 列中的格式错误值无法通过修复恢复,删除会突破保留率限制,而置空又会导致该列的 .required 检查回归。诚实地标记为 residual 才是正确做法;绝不要置空必填字段,也不要为了通过检查而编造值。
停止条件
由于检查结果是确定性的计数,“完成”是精确的 — 不使用 epsilon,也不采用平台期启发式判断。这是与 autoresearch 循环的关键区别:本循环旨在终止。 满足任一条件时停止:
- 全部通过 — 每项检查均为
pass。干净、成功的退出方式。 - 全部为 residual — 不剩下任何
open检查(每项检查均为pass或residual);剩余违规在护栏约束内确实无法修复。自然退出方式。 - 预算用尽 — 达到
<gate>/<budget>。仅作为兜底。
在将最后一项 open 检查标记为 residual(这会触发停止条件 #2)之前,请确认你确实尝试了全部三种策略(修复 → 填补 → 删除)——不要仅仅因为第一种策略导致其他检查回归或触及下限,就宣告该问题无法修复。
停止时: 将 <best> 设为 cleaned.<ext>,写入 report.md,并打印最终总结(概要 checks_passing%、通过/剩余计数,以及触发的停止条件)。
输出与格式
运行会生成 三项交付物:
cleaned.<ext>— 最佳版本(<best>的副本)。pipeline.py— 可独立运行、可重放的脚本:读取原始<artifact>→ 按顺序应用每项已接受的转换 → 写入清理后的文件。结果确定且幂等;在原始转储上重新运行后,会精确复现cleaned.<ext>。这是符合审计要求的产物。ledger.tsv+report.md— 完整审计记录,以及前后对比摘要。
<sandbox_root>/tcl/ 布局:schema.yaml(绑定项 + 契约 + 编译后的检查清单),
versions/(v0.<ext> 是原始产物的只读副本;每个候选版本都会写入一个新的 vN),
transforms/(每项已接受转换对应一个文件)、profiles/、ledger.tsv、
pipeline.py、report.md。
ledger.tsv — 以制表符分隔、仅追加,每次尝试一行,自由文本中不得包含逗号。
每行遵循 schemas/ledger.schema.json;status ∈ {keep,revert,residual,crash}:
iter transform_id target_check dimension viol_before viol_after regressions retention status rows_affected cells_affected summary
1 t01_drop_dupes rows.unique uniqueness 7 0 0 1.000 keep 7 0 remove 7 exact duplicate rows
2 t02_canon_status status.categories consistency 142 0 0 1.000 keep 0 142 rewrite variants to canonical set
3 - contact.regex validity 3 3 - 0.94 residual - - repair impossible; drop breaches retention; null regresses contact.required
profiles/profile-vN.json — 遵循 schemas/profile.schema.json(每列统计数据 + 每项检查的计数和状态 + 保留率 + 摘要标题)。精简实例:
{"version":"v2","rows":980,"cols":7,"checks_total":11,"checks_passing":9,
"checks_passing_pct":0.82,"total_violations":3,"retention":0.98,
"checks":[{"id":"status.categories","dimension":"consistency","scope":"status",
"severity":"normal","violations":0,"state":"pass"}],
"columns":[{"name":"status","dtype":"object","contract_dtype":"category",
"pct_null":0.0,"n_unique":5}],
"summary":"status canonicalized; one regex check residual on contact."}
report.md — v0 与最终版本的摘要标题 checks_passing%、包含起始和结束违规计数的完整检查清单、已确认的契约、按顺序排列的已接受转换(即处理流水线)、剩余项(及原因)、被删除或插补的内容,以及触发了哪个停止条件。
约束
- 绝不修改原始
<artifact>— 所有工作都在<sandbox_root>/tcl/中进行;v0是副本,循环只写入新的vN版本,因为原始转储是重放的基准真值。 - 绝不添加行、伪造键/索引或暗中编造值 — 允许插补,但必须标记为合成数据、记录日志,且每列最多插补
<impute_cap>条。 - 遵守护栏 — 候选版本若低于保留率下限、删除受保护的列或超过插补上限,无论通过了多少项检查,都必须回退,因为否则可以通过删除数据来操纵检查清单。
- 每项检查都是确定性的计数,而非由大模型判断;已确认的契约是“正确”的唯一依据 — 运行期间不得暗中更改契约或编译后的检查清单。
- 保留/回退完全依据检查清单逻辑 — 仅当目标检查有所改善、没有任何退化且未违反护栏时才保留。不得使用 epsilon,也不得用加权分数驱动决策。
- 每次迭代只应用一项转换,即针对一项未通过检查的纯 pandas 函数,以便将每项违规数量的变化归因到具体转换;不得盲目执行多步重写。
- 在护栏范围内无法修复的检查将标记为
residual并报告 — 绝不通过编造或清空必需数据来强行消除。 - 确保
pipeline.py忠实反映处理过程 — 仅按顺序包含已接受的转换;在原始转储上重新运行后,必须生成cleaned.<ext>。 - 只要仍有未通过的检查,就不要暂停并询问是否继续 — 设置阶段是唯一需要人工确认的环节;循环会一直运行,直至满足停止条件。不得安装环境中缺少的包,除非获得同意并使用固定版本;不得将
tcl/提交到 git。