# AI 数据清洗：虚构源数据与处理依据包

OpenMax 编辑教学资料 · 第 1 版 · 2026 年 9 月 4 日。
这不是客户数据、产品导出，也不授权修改真实记录。

## 来源清单与字段字典

来源编号为 CLEAN-070-RAW-v1，共八条物理行。预期粒度是每个作业一条观测，但导出清单明确指出 R3 是 R2 的重复投递，不是第二次事件或修订。保留完整原始数据。候选版本 CLEAN-070-CAND-v1 不使用 R3，但保留它与 R2 的对应关系。

以下业务字段的原始类型均为字符串。反引号表示原样字符串；两个双引号表示空字符串，而不是包含引号的值。状态 Done 两侧的空格是原始字符。source_row 标识收到的物理行，不是作业编号。

| source_row | job_id | region | quantity | status | completed_date |
|---|---|---|---|---|---|
| R1 | `0001` | `NA` | `10` | `done` | `2026-09-01` |
| R2 | `0002` | `EU` | `20` | ` Done ` | `2026-09-01` |
| R3 | `0002` | `EU` | `20` | ` Done ` | `2026-09-01` |
| R4 | `0003` | `EU` | `1,200` | `done` | `2026-09-01` |
| R5 | `0004` | `EU` | `""` | `done` | `2026-09-01` |
| R6 | `0005` | `EU` | `5` | `BETA` | `2026-09-01` |
| R7 | `""` | `EU` | `7` | `done` | `2026-09-01` |
| R8 | `0006` | `EU` | `30` | `done` | `03/04/2026` |

- job_id：恰好四个 ASCII 数字，按文本保存，前导零有意义。空值无效。按已说明的投递规则排除后，非空编号须在指定粒度内唯一。
- region：NA 与 EU 是允许的两个虚构地区标签；NA 不是缺失值，不代表真实国家或客户身份。
- quantity：以单件为单位的非负整数。允许 ASCII 数字；分组整数采用美式逗号，首组一到三位，后续每组三位。空字符串表示未知，不是零。没有提供其他单位或区域格式换算。
- status：只允许将 done 与原始变体（Done 两侧各一个空格）映射为 done。BETA 没有批准的映射。这个有限对应表不授权全局规范任意字符串。
- completed_date：有效日历日期，格式为 YYYY-MM-DD。没有提供斜杠日期的解释。本例只用日期，不需要时区换算；报表期间为 2026 年 9 月。禁止为了改变是否纳入而选择某种斜杠解释。
- 候选记录必须同时具有有效编号、地区、数量、批准的 done 状态和期间内有效日期。缺失或未解决条件导致暂缓。八行案例不包含负责人参考表；下方空键测试使用另一组独立输入。

## 规则与逐行决定

规则集为 CLEAN-070-RULES-v1，依据是这份虚构字典和来源清单，而不是模型建议。D1 识别重复投递；I1 保留并验证编号；R1 检查地区；Q1 解析数量或暂缓；S1 应用状态映射；T1 检查日期和期间；G1 汇总符合条件的要求。

| 行 | 原值到候选处理 | 状态 | 规则与理由 |
|---|---|---|---|
| R1 | 0001 和 NA 不变；数量字符串 10 转为整数 10 | 符合条件 | I1/R1/Q1/S1/T1 通过，不替换缺失标记 |
| R2 | 带首尾空格的 Done 转为 done；20 转为整数 | 符合条件 | S1 明确允许；其他检查通过 |
| R3 | 完整原始内容保留，关联到留存的 R2 | 重复投递排除 | D1 清单明确指示，不是任意保留第一行 |
| R4 | 数量 1,200 转为整数 1200，原字符串保留 | 符合条件 | Q1 已提供分组规则，其他检查通过 |
| R5 | 空数量对应未知标准化数量 | 暂缓 | Q1 必需测量缺失，向来源索取更正 |
| R6 | 原始 BETA 保留，不分配标准状态 | 暂缓 | S1 无映射，索取类别定义 |
| R7 | 空编号保留，不虚构或分配默认编号 | 暂缓 | I1 无效键，索取有效源编号 |
| R8 | 原样保留 03/04/2026，标准日期不可用 | 暂缓 | T1 格式未知，索取明确源日期 |

暂缓记录中其他可解释字段可以有派生值，但这不让整条记录符合条件。暂缓不等于删除；每一种处理都保留源字符串与源行引用。

## 对账与使用边界

- 原始物理行：8 = 1 条投递排除 + 3 条符合条件 + 4 条暂缓。
- 排除投递后：7 条观测，其中一条没有作业编号；不能称为七个有效且唯一的编号。
- 符合条件：R1、R2、R4；数量为 10 + 20 + 1200 = 1230 件。
- 暂缓：R5、R6、R7、R8；已知数量为 5 + 7 + 30 = 42 件，另有一项数量未知。
- 原始已知值小计为 1292；减去重复投递的 20 后，1272 = 1230 + 42。完整总量仍然未知。
- 排除投递后的符合条件比例：3/7 ≈ 42.86%；收到的物理行比例：3/8 = 37.5%。两者均不是 AI 准确率。

候选集未获准用于完整的九月数量报表。有限使用三条记录子集，需要负责人明确接受部分总体并说明排除与暂缓。没有真实负责人通过本工作表授予这种权限。真实发布前须记录接收人、用途、候选版本、决定时间和原始数据引用。

## 两项独立的库行为测试

本地执行版本为 pandas 3.0.1；2026 年 9 月 4 日核对的在线参考页标示 3.0.5。测试不涉及 OpenMax，也不代表所有导入与连接实现。

```python
from io import StringIO
import pandas as pd

sample = 'job_id,region,quantity\n0001,NA,10\n0002,EU,\n'
default = pd.read_csv(StringIO(sample))
preserved = pd.read_csv(StringIO(sample), dtype=str, keep_default_na=False)
assert default.loc[0, 'job_id'] == 1
assert pd.isna(default.loc[0, 'region'])
assert preserved.loc[0, 'job_id'] == '0001'
assert preserved.loc[0, 'region'] == 'NA'
assert preserved.loc[1, 'quantity'] == ''

left = pd.DataFrame({'key': ['0001', None], 'units': [10, 7]})
right = pd.DataFrame({'key': ['0001', None], 'owner': ['Team1', 'Unassigned']})
joined = left.merge(right, on='key', validate='many_to_one', indicator=True)
assert joined.loc[joined['key'].isna(), 'owner'].tolist() == ['Unassigned']
valid_left = left[left['key'].notna() & left['key'].ne('')]
valid_right = right[right['key'].notna() & right['key'].ne('')]
candidate = valid_left.merge(valid_right, on='key', validate='many_to_one')
assert len(candidate) == 1
assert len(left) - len(valid_left) == 1
```

这组设置展示行为边界，不是万能配置。保留全部字符串也会让空数量继续是字符串；后续必须按字段规则判断含义。将无效键排除在连接输入之外，不等于允许把原记录从对账中抹去。

来源：[pandas read_csv](https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html)；[pandas merge](https://pandas.pydata.org/docs/reference/api/pandas.merge.html)。
