|
|
@@ -0,0 +1,104 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+"""生成 AI 数据清洗 E2E 验收用的 xlsx/csv 样本。
|
|
|
+
|
|
|
+依赖:openpyxl
|
|
|
+用法:python generate_fixtures.py # 输出到同目录 fixtures/
|
|
|
+"""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import csv
|
|
|
+import os
|
|
|
+
|
|
|
+from openpyxl import Workbook
|
|
|
+from openpyxl.styles import Font
|
|
|
+
|
|
|
+OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures")
|
|
|
+os.makedirs(OUT, exist_ok=True)
|
|
|
+
|
|
|
+HEADERS = ["姓名", "身份证号", "手机号", "银行卡号", "交易日期", "交易时间", "金额", "数量", "备注"]
|
|
|
+
|
|
|
+ROWS = [
|
|
|
+ ["张三", "11010519491231002X", "13800000000", "6222020200001234",
|
|
|
+ "2026-01-05", "2026-01-05 10:30:00", "1,234.50", "3", "正常"],
|
|
|
+ ["李四", "110105491231002", "+86 139-0000-1111", "6222 0202 0000 1235",
|
|
|
+ "2026/1/6", "2026-01-06T11:00:00", "-100.00", "2", "负数金额"],
|
|
|
+ ["王五", "12345", "123", "6.22202E+15",
|
|
|
+ "2026年1月7日", "10:30", "(200.50)", "1.5", "坏证件/电话/卡/纯时间"],
|
|
|
+ ["赵六", "110105194912310021", "13800002222", "6222020200001236",
|
|
|
+ "2026-02-30", "25:00", "约100元", "abc", "坏日期/时间/金额/数量"],
|
|
|
+ ["钱七", "11010519491231002X", "13800003333", "6222020200001237",
|
|
|
+ "45000", "1704067200", "¥2,300.5", "10", "序列号/epoch"],
|
|
|
+ ["孙八", "11010519491231002X", "13800004444", "6222020200001238",
|
|
|
+ "20260108", "2026-01-08 09:00:00", "100元", "8", "紧凑日期/带元"],
|
|
|
+ [],
|
|
|
+ ["周九", "11010519491231002X", "13800005555", "6222020200001239",
|
|
|
+ "2026-01-09", "2026-01-09 12:00:00", "300.00", "5", "空行后的行"],
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+def save_xlsx(name: str, sheet_name: str, rows, headers=None, title=None, start_row=1,
|
|
|
+ positions=None):
|
|
|
+ wb = Workbook()
|
|
|
+ ws = wb.active
|
|
|
+ ws.title = sheet_name
|
|
|
+ row = start_row
|
|
|
+ if title:
|
|
|
+ ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(headers or HEADERS))
|
|
|
+ ws.cell(row=1, column=1, value=title).font = Font(bold=True)
|
|
|
+ row = 3
|
|
|
+ for c, h in enumerate(headers or HEADERS, 1):
|
|
|
+ ws.cell(row=row, column=c, value=h).font = Font(bold=True)
|
|
|
+ for i, values in enumerate(rows):
|
|
|
+ target = positions[i] if positions else row + 1 + i
|
|
|
+ for c, v in enumerate(values, 1):
|
|
|
+ ws.cell(row=target, column=c, value=v)
|
|
|
+ wb.save(os.path.join(OUT, name))
|
|
|
+
|
|
|
+
|
|
|
+def save_csv(name: str, rows):
|
|
|
+ with open(os.path.join(OUT, name), "w", newline="", encoding="utf-8") as f:
|
|
|
+ w = csv.writer(f)
|
|
|
+ w.writerow(HEADERS)
|
|
|
+ for values in rows:
|
|
|
+ w.writerow(values if values else [""] * len(HEADERS))
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ # 01:自建表类型推断(标题行 + 空行 + 表头 + 空行隔断)
|
|
|
+ save_xlsx("01_self_built_types.xlsx", "经营者信息", ROWS, title="经营者信息表")
|
|
|
+ # 02:与 01 同表头但倒序(验证复用必须同序)
|
|
|
+ save_xlsx("02_reordered_headers.xlsx", "乱序经营者",
|
|
|
+ [list(reversed(r)) for r in ROWS[:3]], headers=list(reversed(HEADERS)))
|
|
|
+ # 03:两列标题 + 空行 + 表头 + 空行隔断,验证表头定位与 row_no 缺口
|
|
|
+ save_xlsx("03_messy_title_blank.xlsx", "人员交易", [
|
|
|
+ ["张三", "1,000.00", "2026-01-01"],
|
|
|
+ ["李四", "2,000.00", "2026-01-02"],
|
|
|
+ ["王五", "3,000.00", "2026-01-03"],
|
|
|
+ ["赵六", "坏金额", "坏日期"],
|
|
|
+ ], headers=["姓名", "金额", "日期"], title="2026年人员交易明细",
|
|
|
+ positions=[4, 5, 7, 8])
|
|
|
+ # 04:CSV 版脏数据(验证 CSV 不再 Skip;可选列脏值只置空)
|
|
|
+ save_csv("04_csv_types.csv", ROWS)
|
|
|
+ # 05:同表头 20 行,1 行身份证非法(配合把 ai_clean_field.required 临时置 1,
|
|
|
+ # 验证必填列坏值整行拒绝 + 失败明细)
|
|
|
+ rows05 = []
|
|
|
+ for i in range(1, 21):
|
|
|
+ bad = i == 1
|
|
|
+ rows05.append([
|
|
|
+ "坏证件行" if bad else f"正常{i}",
|
|
|
+ "12345" if bad else "11010519491231002X",
|
|
|
+ "13800000000", "6222020200001234", "2026-01-05",
|
|
|
+ "2026-01-05 10:30:00", "100.00", "1", "ok",
|
|
|
+ ])
|
|
|
+ save_xlsx("05_required_bad.xlsx", "经营者信息", rows05)
|
|
|
+ # 06:干净 CSV(验证 CSV 正常入库)
|
|
|
+ save_csv("06_csv_clean.csv", [
|
|
|
+ [f"CSV正常{i}", "11010519491231002X", "13800000000", "6222020200001234",
|
|
|
+ "2026-01-05", "2026-01-05 10:30:00", "100.00", "1", "ok"]
|
|
|
+ for i in range(1, 8)
|
|
|
+ ])
|
|
|
+ print("fixtures written to", OUT)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|