excel-data-pipeline
Skill1 starsChanged 49 days agoMITView on GitHub
把真实台账里那种"表头不在第一行、有合并单元格、夹着空行、列位置还被改过"的 Excel,提取成一份可辩护的 JSON 数据集。包含探结构、列契约校验、父子合计口径、异常值与编号重复的处理。在动手做任何看板或汇报材料之前先走这一步。
In this repository3 other files it gives its agents.
- html-slide-reportSkill
- share-doc-pipelineSkill
- single-file-dashboardSkill
The file
Files
Excel 台账 → 可辩护的 JSON
源表结构、脚本产出与自检输出(示例数据):
Image: 效果示例
为什么单独有这一步
凡是要拿数据做汇报、做看板,数字能不能被追问决定了整个成果的价值。而实践中出错的地方几乎从来不是算法,是这三类:
- 看错列——台账被人删了两列,后续列整体前移,脚本还按老索引取数,取出来的东西形状对、含义完全错;
- 算错口径——父子层级里,父级数据该是"自己那一行"还是"所有子级之和",两种都常见,弄反了总量就错;
- 漏了脏数据——
#N/A、/、空白、文本格式的数字混在列里,静默变成NaN或被当成 0。
这个 skill 就是对着这三类问题来的。
第 1 步:先探结构,不要先写代码
python inspect_excel.py <工作簿路径> [输出文件]它把每个 sheet 的真实结构写进一个文本文件:
- sheet 名单与各自的尺寸;
- 逐列的列号(Excel 字母 + 0 起索引)、表头文字、若干样例值;
- 表头可能不在第 1 行时,它会跳过前几行空行再找;
- 检测到的合并单元格区域。
输出写成文件而不是打印到终端——中文在 Windows 终端下经常乱码,写文件再读比调编码省事得多。
拿到这份结构后,把列位置显式写死进提取脚本顶部的配置区。不要用"自动找表头"这类小聪明:台账的表头文字常年微调,自动匹配会在某次微调后悄悄匹配到错误的列上,静默出错。
第 2 步:列契约校验
在提取脚本开头加一道断言:读表头行,与预期的列名逐个比对,对不上就报错退出。
COL_SPEC = [
(0, '编号'), (1, '名称'), (2, '级别'), (3, '地址'),
(9, '供水量'), (10, '售水量'), (11, '漏损量'), (12, '产销差'),
]
def verify_header(ws, spec, header_row=1):
actual = [str(c.value or '').strip() for c in ws[header_row]]
bad = [(i, name, actual[i] if i < len(actual) else '空')
for i, name in spec if i >= len(actual) or actual[i] != name]
if bad:
for i, want, got in bad:
print(f' 第 {i+1} 列:期望「{want}」,实际「{got}」')
raise SystemExit('表头校验未通过——表结构可能被改过,先跑 inspect_excel.py 看清现状')这道校验的价值在生成阶段就拦住错误,而不是等材料发出去之后被人发现。一次真实的事故就是台账删掉了"上月底数""本月读数"两列,后面所有列前移两位,脚本默默取到了错误的数据,直到有人对着原始表格核对才发现。
第 3 步:数值清洗
def safe_num(v):
"""把 Excel 里各种'空'与错误值统一压成 None,其余转 float。"""
if v is None: return None
if isinstance(v, (int, float)):
if math.isnan(v) or math.isinf(v): return None
return round(float(v), 2)
if isinstance(v, str):
v = v.strip()
if v in ('', '/', '-', '—', '无'): return None
if v.startswith('#'): return None # #N/A #DIV/0! #REF!
try: return round(float(v.replace(',', '')), 2)
except ValueError: return None # 真·文本列(名称、地址)
return None要点:
None不等于 0。 用None表示"缺失",用 0 表示"确实是零"。混在一起之后,求平均、算同比都会错,而且错得看不出来。- 逗号千分位要剥掉,
"1,234.5"直接float()会炸。 - 文本列不要走这个函数——它们是合法的字符串,会被清洗成
None。
第 4 步:口径——父子合计怎么算
这是最容易错、也最容易被追问的一点。
4.1 父级 = 子级之和,不是父级自己那一行
台账里父级行往往也填了数字(有的是对的,有的是历史遗留)。要明确指定用哪种,并且写进文档:
# 明确:父级取其下所有子级之和,忽略父级行自身的数字
parent['supply'] = sum(s['supply'] for s in subs if s['supply'] is not None)一个真实的偏差案例:某台区的供水量取了它第一个子区域的值,而不是所有子区域之和,导致该台区的产销差被严重高估。这种错误从单个数字上看不出来,只有横向对比总量时才会暴露。
4.2 共用量的归属
子区域之间可能存在共用一份量的情况(一笔量同时覆盖两个子区域)。常见处理是归属到第一个子区域,其余置 None,合计时只算一次。
必须在提取脚本里显式注释这个约定,否则后面维护的人会以为是漏填,把 None 补成 0,总量就对不上了。
4.3 编号可能重复
源表里的编号列不保证唯一(不同年份、不同口径的行可能共用一个编号)。拿编号当图表标签会出现多个同名条目。
处理:按业务意义派生一个稳定且唯一的键,比如按数值降序重新编号,或直接用 编号 + 序号。不要直接拿原始编号当字典键。
第 5 步:导出前自检
提取脚本跑完时打印一份摘要,让口径错误在生成阶段就暴露:
总行数 42 | 有效数据行 38
供水量合计 1,204.5 万吨 ← 与源表合计行核对
售水量合计 903.2 万吨
漏损量合计 301.3 万吨 ← 应等于 供水 − 售水
台区数 9 | 子区域数 27
编号重复:0关键是对着源表里已有的合计行互校一遍。合计行是业务人员自己算的,与脚本独立,两者一致才说明列索引和口径都没错。
目录内容
excel-data-pipeline/
├── SKILL.md
├── scripts/
│ ├── inspect_excel.py 探清工作簿真实结构(第 1 步)
│ └── extract.py 带列契约校验的提取模板(第 2—5 步)
└── references/
└── aggregation-rules.md 层级口径与异常值的完整处理约定下游要做成单文件看板见 single-file-dashboard;要做成汇报幻灯见 html-slide-report。
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

