11 KiB
11 KiB
06 - 比对模块详细设计
1. 模块概述
比对模块(comparator/)是 COBOL 迁移验证平台 V3 的核心验证引擎,负责将 COBOL 原始输出与 Java(Spark)迁移后输出进行逐字段对比,判断迁移正确性。
模块提供五项核心能力:
- 记录对齐(aligner):按主键将 COBOL 记录集与 Java 记录集配对
- 二进制读取(cobol_binary_reader):解析 COBOL 二进制输出文件为字典
- 数据标准化(normalizer):处理 EBCDIC 编码、COMP-3 压缩十进制、日期格式
- 字段比对(field_compare):按类型(数值/日期/字符串)进行字段级比较
- 舍入检测(rounding_detect):判断数值差异是否由 COBOL ROUNDED 子句引起
2. 文件清单
| 文件 | 职责 | 依赖 |
|---|---|---|
__init__.py |
包入口,公开 API 导出 | 所有子模块 |
aligner.py |
COBOL 与 Java 记录对齐 | 无 |
cobol_binary_reader.py |
二进制 COBOL 输出解析 | data.field_tree |
field_compare.py |
字段级比较(decimal/string/date) | data.diff_result |
normalizer.py |
COMP-3/EBCDIC 解码、IR 记录构造 | 无 |
rounding_detect.py |
舍入差异检测 | decimal |
3. 记录对齐算法(aligner.py)
3.1 职责
将 COBOL 输出记录集和 Java 输出记录集按主键字段进行配对,输出对齐的记录对列表及匹配状态。
3.2 函数签名
def align_records(
cobol_records: list[dict],
java_records: list[dict],
key_field: str = "CUST-ID"
) -> list[tuple]
3.3 算法描述
- 分组:分别按
key_field对 COBOL 和 Java 记录建立{key: [records]}索引 - 合并键集:取两侧键的并集,按字符串排序
- 逐键配对:对每个键,取两侧记录列表的对应位置进行配对
- 状态标记:
| 状态 | 含义 |
|---|---|
MATCHED |
两侧均有记录,成功配对 |
MISSING_IN_SPARK |
COBOL 有记录,Java 侧缺失 |
EXTRA_IN_SPARK |
Java 有记录,COBOL 侧缺失 |
3.4 返回值
list[tuple[dict | None, dict | None, str]]
# (cobol_record, java_record, status)
3.5 设计要点
- 使用
setdefault聚合同键多记录,支持一对多场景 - 排序保证输出顺序稳定
- 键值转为字符串处理,兼容数值键和字符串键
- 空记录集直接返回空列表
4. 二进制读取器(cobol_binary_reader.py)
4.1 职责
读取 COBOL 程序生成的二进制输出文件,根据 FieldTree 定义的字段布局逐记录解析为字典。
4.2 核心类
class CobolBinaryReader:
def read(self, path: str, tree: FieldTree) -> list[dict]
def _record_size(self, tree: FieldTree) -> int
def _parse(self, record_bytes: bytes, tree: FieldTree) -> dict
def _comp3(self, raw: bytes, signed: bool, decimal: int) -> str
4.3 记录大小计算
def _record_size(self, tree):
return max((f.offset + f.length for f in tree.fields), default=0)
取所有顶层字段的最大 offset + length 作为记录大小。
4.4 字段解析策略
| USAGE 类型 | 解析方式 |
|---|---|
COMP-3 |
半字节解码 + 符号位处理 + 小数点定位 |
COMP / COMP-5 |
int.from_bytes(raw, "big", signed=...) |
| 其他(DISPLAY 等) | ASCII 解码 + 去尾空格 |
4.5 COMP-3 解码算法
输入: N 字节压缩十进制数据
1. 将每字节拆为两个半字节(高4位 + 低4位)
2. 弹出最后一个半字节作为符号位
3. 逐半字节累加为数值(权值 = 10^(len-1-i))
4. 符号位 0xD/0xB -> 取反
5. 按 decimal 定位小数点
输出: 字符串形式数值(如 "1234.56")
4.6 错误处理
- 文件为空或记录大小为 0 -> 返回空列表
- 记录不完整(字节数 < record_size)-> 跳过该记录
- COMP-3 空数据 -> 返回 "0"
- 非 ASCII 字符 ->
errors="replace"替换为?
5. 数据标准化器(normalizer.py)
5.1 职责
提供 COBOL 数据到中间表示(IR)的标准化转换,包括 EBCDIC 解码、COMP-3 解码、日期格式统一、IR 记录构造。
5.2 核心类
class Normalizer:
def normalize_encoding(self, raw: bytes, encoding: str) -> str
def normalize_comp3(self, raw: bytes) -> str
def normalize_date(self, s: str) -> str
def to_ir_record(self, name, hex_, val, enc, ft, length=0, scale=0, signed=False) -> IRRecord
def to_null_ir(self, name: str, side: str = "java") -> IRRecord
5.3 EBCDIC 编码转换
内置 EBCDIC 037 代码页映射表(EBCDIC_037),覆盖:
- 空格、标点、运算符
- 小写 a-z(0x81-0xA9)
- 大写 A-Z(0xC1-0xE9)
- 数字 0-9(0xF0-0xF9)
不可映射字符处理:
- 可打印 ASCII(32-126)-> 保留原字符
- 其他 -> 替换为
?
5.4 COMP-3 标准化
与 CobolBinaryReader._comp3 算法一致,但返回整数字符串(无小数点处理),适用于无需小数定位的场景。
5.5 日期标准化
- 8 位纯数字字符串(如
20260822)-> 格式化为YYYY-MM-DD - 其他格式 -> 原样返回
5.6 中间表示(IR)模型
@dataclass
class CobolIRField:
raw_hex: str # 原始十六进制
decoded_value: str # 解码后值
encoding: str # 编码类型(EBCDIC/ASCII)
field_type: str # 字段类型
length: int # 字节长度
scale: int # 小数位数
signed: bool # 是否带符号
@dataclass
class JavaIRField:
raw_value: str # 原始值
decoded_value: str # 解码后值
field_type: str # 字段类型
nullable: bool # 是否可空
@dataclass
class IRRecord:
field_name: str
cobol: CobolIRField | None
java: JavaIRField | None
5.7 空值 IR 构造
to_null_ir 为缺失侧构造空 IR 记录(JavaIRField("", "", "null", True)),用于只有一侧有数据的场景。
6. 字段比对算法(field_compare.py)
6.1 职责
对单个字段的 COBOL 值和 Java 值进行类型感知的比较,返回结构化的比对结果。
6.2 函数签名
def compare_field(
name: str,
c: str, # COBOL 值
j: str, # Java 值
field_type: str = "decimal", # 字段类型
tolerance: float = 0.01 # 容忍度
) -> FieldResult
6.3 比对策略
| 字段类型 | 比对方式 | 说明 |
|---|---|---|
decimal / numeric |
数值差 <= tolerance -> TOLERATED | 使用 Decimal 精确计算 |
date |
8 位数字 -> YYYY-MM-DD 格式化后比较 |
统一格式消除表示差异 |
string |
strip 后直接比较 | 去除首尾空白 |
| 其他 | 字符串直接比较 | 兜底策略 |
6.4 状态判定
| 状态 | 条件 |
|---|---|
PASS |
完全一致 |
TOLERATED |
数值差在容忍度范围内 |
MISMATCH |
不一致 |
NOT_SET |
两侧均为空/None |
6.5 数值解析(_num)
def _num(v) -> Decimal | None
None/"None"->None- 空字符串 ->
Decimal("0") - 含
\x00-> 去除后解析 - 非数字字符串 ->
None
使用 Python Decimal 进行精确十进制运算,避免浮点精度问题。
6.6 默认容忍度
DEFAULT_TOLERANCE = 0.01
可通过 Config.tolerance 全局配置调整。
7. 舍入检测(rounding_detect.py)
7.1 职责
判断两个数值之间的差异是否由 COBOL ROUNDED 子句引起,识别舍入模式并给出置信度。
7.2 函数签名
def detect_rounding(c: str, j: str) -> RoundingResult
7.3 RoundingResult 数据类
@dataclass
class RoundingResult:
mode: str = "EXACT" # 舍入模式
confidence: float = 1.0 # 置信度(0-1)
suggestion: str = "" # 建议文本
7.4 检测算法
1. 解析 COBOL 值 (cv) 和 Java 值 (jv) 为 Decimal
2. 任一解析失败 -> UNKNOWN (confidence=0)
3. cv == jv -> EXACT (confidence=1.0)
4. 计算绝对差 diff = |cv - jv|
5. 判定模式:
- diff < 2 -> TRUNCATE (confidence=0.6)
- diff < 100 -> ROUNDING (confidence=0.4)
- diff >= 100 -> SIGNIFICANT (confidence=0.9)
7.5 舍入模式说明
| 模式 | 含义 | 置信度 | 建议 |
|---|---|---|---|
EXACT |
完全一致 | 1.0 | 无 |
TRUNCATE |
截断差异(diff < 2) | 0.6 | 可能是 COBOL 截断行为 |
ROUNDING |
舍入差异(diff < 100) | 0.4 | 可能存在舍入方式差异 |
SIGNIFICANT |
显著差异(diff >= 100) | 0.9 | 需要关注的较大差异 |
7.6 数值解析(_d)
def _d(v) -> Decimal | None
- 使用
Decimal(str(v).strip())解析 - 异常时返回
None
8. 接口定义
8.1 模块公开 API
# comparator/__init__.py
align_records(cobol_records, java_records, key_field) -> list[tuple]
compare_field(name, c, j, field_type, tolerance) -> FieldResult
CobolBinaryReader # class
Normalizer # class
detect_rounding(c, j) -> RoundingResult
8.2 典型调用流程
cobol_binary_reader.read(path, field_tree) -> list[dict]
|
v
align_records(cobol_records, java_records, key_field) -> list[tuple]
|
v (对每个 MATCHED 对)
compare_field(name, cobol_val, java_val, field_type, tolerance) -> FieldResult
|
v (可选)
detect_rounding(cobol_val, java_val) -> RoundingResult
8.3 数据模型依赖
| 模型 | 定义位置 | 用途 |
|---|---|---|
FieldTree / Field |
data.field_tree |
字段布局定义 |
FieldResult |
data.diff_result |
字段比对结果 |
IRRecord / CobolIRField / JavaIRField |
comparator.normalizer |
中间表示 |
RoundingResult |
comparator.rounding_detect |
舍入检测结果 |
9. 错误处理
9.1 错误策略
模块采用严格解析 + 宽容比对策略:
| 异常场景 | 处理方式 | 影响 |
|---|---|---|
| 二进制文件为空 | 返回空列表 | 无记录可比 |
| 记录不完整 | 跳过该记录 | 部分数据丢失 |
| COMP-3 空数据 | 返回 "0" | 默认零值 |
| EBCDIC 未知字符 | 替换为 ? |
可能导致 MISMATCH |
| 数值解析失败 | 返回 None |
NOT_SET 状态 |
| 两侧均无值 | NOT_SET | 不计入匹配统计 |
9.2 已知局限
aligner.py仅支持单一主键,不支持复合主键CobolBinaryReader假定所有记录等长,不支持变长记录Normalizer.normalize_comp3不处理小数位(与CobolBinaryReader._comp3重复实现)detect_rounding的阈值(2/100)为经验值,未基于统计分析field_compare的字符串比较未处理 EBCDIC 与 ASCII 的编码差异