Files
cobol-java-v3/docs/detailed-design/06-comparator.md
T

11 KiB
Raw Blame History

06 - 比对模块详细设计

1. 模块概述

比对模块(comparator/)是 COBOL 迁移验证平台 V3 的核心验证引擎,负责将 COBOL 原始输出与 Java(Spark)迁移后输出进行逐字段对比,判断迁移正确性。

模块提供五项核心能力:

  1. 记录对齐aligner):按主键将 COBOL 记录集与 Java 记录集配对
  2. 二进制读取cobol_binary_reader):解析 COBOL 二进制输出文件为字典
  3. 数据标准化normalizer):处理 EBCDIC 编码、COMP-3 压缩十进制、日期格式
  4. 字段比对field_compare):按类型(数值/日期/字符串)进行字段级比较
  5. 舍入检测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 算法描述

  1. 分组:分别按 key_field 对 COBOL 和 Java 记录建立 {key: [records]} 索引
  2. 合并键集:取两侧键的并集,按字符串排序
  3. 逐键配对:对每个键,取两侧记录列表的对应位置进行配对
  4. 状态标记
状态 含义
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-z0x81-0xA9
  • 大写 A-Z0xC1-0xE9
  • 数字 0-90xF0-0xF9

不可映射字符处理:

  • 可打印 ASCII32-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 已知局限

  1. aligner.py 仅支持单一主键,不支持复合主键
  2. CobolBinaryReader 假定所有记录等长,不支持变长记录
  3. Normalizer.normalize_comp3 不处理小数位(与 CobolBinaryReader._comp3 重复实现)
  4. detect_rounding 的阈值(2/100)为经验值,未基于统计分析
  5. field_compare 的字符串比较未处理 EBCDIC 与 ASCII 的编码差异