# Machine Code Parser 分析报告 ## 文件概况 - **文件**: `src/ocr/machine_code_parser.py` - **总行数**: 919 行 - **代码行**: 607 行 (66%) - **方法数**: 14 个 - **正则表达式使用**: 47 次 ## 代码结构 ### 类结构 ``` MachineCodeResult (数据类) ├── to_dict() └── get_region_bbox() MachineCodeParser (主解析器) ├── __init__() ├── parse() - 主入口 ├── _find_tokens_with_values() ├── _find_machine_code_line_tokens() ├── _parse_standard_payment_line_with_tokens() ├── _parse_standard_payment_line() - 142行 ⚠️ ├── _extract_ocr() - 50行 ├── _extract_bankgiro() - 58行 ├── _extract_plusgiro() - 30行 ├── _extract_amount() - 68行 ├── _calculate_confidence() └── cross_validate() ``` ## 发现的问题 ### 1. ⚠️ `_parse_standard_payment_line` 方法过长 (142行) **位置**: 442-582 行 **问题**: - 包含嵌套函数 `normalize_account_spaces` 和 `format_account` - 多个正则匹配分支 - 逻辑复杂,难以测试和维护 **建议**: 可以拆分为独立方法: - `_normalize_account_spaces(line)` - `_format_account(account_digits, context)` - `_match_primary_pattern(line)` - `_match_fallback_patterns(line)` ### 2. 🔁 4个 `_extract_*` 方法有重复模式 所有 extract 方法都遵循相同模式: ```python def _extract_XXX(self, tokens): candidates = [] for token in tokens: text = token.text.strip() matches = self.XXX_PATTERN.findall(text) for match in matches: # 验证逻辑 # 上下文检测 candidates.append((normalized, context_score, token)) if not candidates: return None candidates.sort(key=lambda x: (x[1], 1), reverse=True) return candidates[0][0] ``` **重复的逻辑**: - Token 迭代 - 模式匹配 - 候选收集 - 上下文评分 - 排序和选择最佳匹配 **建议**: 可以提取基础提取器类或通用方法来减少重复。 ### 3. ✅ 上下文检测重复 上下文检测代码在多个地方重复: ```python # _extract_bankgiro 中 context_text = ' '.join(t.text.lower() for t in tokens) is_bankgiro_context = ( 'bankgiro' in context_text or 'bg:' in context_text or 'bg ' in context_text ) # _extract_plusgiro 中 context_text = ' '.join(t.text.lower() for t in tokens) is_plusgiro_context = ( 'plusgiro' in context_text or 'postgiro' in context_text or 'pg:' in context_text or 'pg ' in context_text ) # _parse_standard_payment_line 中 context = (context_line or raw_line).lower() is_plusgiro_context = ( ('plusgiro' in context or 'postgiro' in context or 'plusgirokonto' in context) and 'bankgiro' not in context ) ``` **建议**: 提取为独立方法: - `_detect_account_context(tokens) -> dict[str, bool]` ## 重构建议 ### 方案 A: 轻度重构(推荐)✅ **目标**: 提取重复的上下文检测逻辑,不改变主要结构 **步骤**: 1. 提取 `_detect_account_context(tokens)` 方法 2. 提取 `_normalize_account_spaces(line)` 为独立方法 3. 提取 `_format_account(digits, context)` 为独立方法 **影响**: - 减少 ~50-80 行重复代码 - 提高可测试性 - 低风险,易于验证 **预期结果**: 919 行 → ~850 行 (↓7%) ### 方案 B: 中度重构 **目标**: 创建通用的字段提取框架 **步骤**: 1. 创建 `_generic_extract(pattern, normalizer, context_checker)` 2. 重构所有 `_extract_*` 方法使用通用框架 3. 拆分 `_parse_standard_payment_line` 为多个小方法 **影响**: - 减少 ~150-200 行代码 - 显著提高可维护性 - 中等风险,需要全面测试 **预期结果**: 919 行 → ~720 行 (↓22%) ### 方案 C: 深度重构(不推荐) **目标**: 完全重新设计为策略模式 **风险**: - 高风险,可能引入 bugs - 需要大量测试 - 可能破坏现有集成 ## 推荐方案 ### ✅ 采用方案 A(轻度重构) **理由**: 1. **代码已经工作良好**: 没有明显的 bug 或性能问题 2. **低风险**: 只提取重复逻辑,不改变核心算法 3. **性价比高**: 小改动带来明显的代码质量提升 4. **易于验证**: 现有测试应该能覆盖 ### 重构步骤 ```python # 1. 提取上下文检测 def _detect_account_context(self, tokens: list[TextToken]) -> dict[str, bool]: """检测上下文中的账户类型关键词""" context_text = ' '.join(t.text.lower() for t in tokens) return { 'bankgiro': any(kw in context_text for kw in ['bankgiro', 'bg:', 'bg ']), 'plusgiro': any(kw in context_text for kw in ['plusgiro', 'postgiro', 'plusgirokonto', 'pg:', 'pg ']), } # 2. 提取空格标准化 def _normalize_account_spaces(self, line: str) -> str: """移除账户号码中的空格""" # (现有 line 460-481 的代码) # 3. 提取账户格式化 def _format_account( self, account_digits: str, is_plusgiro_context: bool ) -> tuple[str, str]: """格式化账户并确定类型""" # (现有 line 485-523 的代码) ``` ## 对比:field_extractor vs machine_code_parser | 特征 | field_extractor | machine_code_parser | |------|-----------------|---------------------| | 用途 | 值提取 | 机器码解析 | | 重复代码 | ~400行normalize方法 | ~80行上下文检测 | | 重构价值 | ❌ 不同用途,不应统一 | ✅ 可提取共享逻辑 | | 风险 | 高(会破坏功能) | 低(只是代码组织) | ## 决策 ### ✅ 建议重构 machine_code_parser.py **与 field_extractor 的不同**: - field_extractor: 重复的方法有**不同的用途**(提取 vs 变体生成) - machine_code_parser: 重复的代码有**相同的用途**(都是上下文检测) **预期收益**: - 减少 ~70 行重复代码 - 提高可测试性(可以单独测试上下文检测) - 更清晰的代码组织 - **低风险**,易于验证 ## 下一步 1. ✅ 备份原文件 2. ✅ 提取 `_detect_account_context` 方法 3. ✅ 提取 `_normalize_account_spaces` 方法 4. ✅ 提取 `_format_account` 方法 5. ✅ 更新所有调用点 6. ✅ 运行测试验证 7. ✅ 检查代码覆盖率 --- **状态**: 📋 分析完成,建议轻度重构 **风险评估**: 🟢 低风险 **预期收益**: 919行 → ~850行 (↓7%)