Matcher Module - 字段匹配模块
将标准化后的字段值与PDF文档中的tokens进行匹配,返回字段在文档中的位置(bbox),用于生成YOLO训练标注。
📁 模块结构
src/matcher/
├── __init__.py # 导出主要接口
├── field_matcher.py # 主类 (205行, 从876行简化)
├── models.py # 数据模型
├── token_index.py # 空间索引
├── context.py # 上下文关键词
├── utils.py # 工具函数
└── strategies/ # 匹配策略
├── __init__.py
├── base.py # 基础策略类
├── exact_matcher.py # 精确匹配
├── concatenated_matcher.py # 多token拼接匹配
├── substring_matcher.py # 子串匹配
├── fuzzy_matcher.py # 模糊匹配 (金额)
└── flexible_date_matcher.py # 灵活日期匹配
🎯 核心功能
FieldMatcher - 字段匹配器
主类,协调各个匹配策略:
from src.matcher import FieldMatcher
matcher = FieldMatcher(
context_radius=200.0, # 上下文关键词搜索半径(像素)
min_score_threshold=0.5 # 最低匹配分数
)
# 匹配字段
matches = matcher.find_matches(
tokens=tokens, # PDF提取的tokens
field_name="InvoiceNumber", # 字段名
normalized_values=["100017500321", "INV-100017500321"], # 标准化变体
page_no=0 # 页码
)
# matches: List[Match]
for match in matches:
print(f"Field: {match.field}")
print(f"Value: {match.value}")
print(f"BBox: {match.bbox}")
print(f"Score: {match.score}")
print(f"Context: {match.context_keywords}")
5种匹配策略
1. ExactMatcher - 精确匹配
from src.matcher.strategies import ExactMatcher
matcher = ExactMatcher(context_radius=200.0)
matches = matcher.find_matches(tokens, "100017500321", "InvoiceNumber")
匹配规则:
- 完全匹配: score = 1.0
- 大小写不敏感: score = 0.95
- 纯数字匹配: score = 0.9
- 上下文关键词加分: +0.1/keyword (最多+0.25)
2. ConcatenatedMatcher - 拼接匹配
from src.matcher.strategies import ConcatenatedMatcher
matcher = ConcatenatedMatcher()
matches = matcher.find_matches(tokens, "100017500321", "InvoiceNumber")
用于处理OCR将单个值拆成多个token的情况。
3. SubstringMatcher - 子串匹配
from src.matcher.strategies import SubstringMatcher
matcher = SubstringMatcher()
matches = matcher.find_matches(tokens, "2026-01-09", "InvoiceDate")
匹配嵌入在长文本中的字段值:
"Fakturadatum: 2026-01-09"匹配"2026-01-09""Fakturanummer: 2465027205"匹配"2465027205"
4. FuzzyMatcher - 模糊匹配
from src.matcher.strategies import FuzzyMatcher
matcher = FuzzyMatcher()
matches = matcher.find_matches(tokens, "1234.56", "Amount")
用于金额字段,允许小数点差异 (±0.01)。
5. FlexibleDateMatcher - 灵活日期匹配
from src.matcher.strategies import FlexibleDateMatcher
matcher = FlexibleDateMatcher()
matches = matcher.find_matches(tokens, "2025-01-15", "InvoiceDate")
当精确匹配失败时使用:
- 同年月: score = 0.7-0.8
- 7天内: score = 0.75+
- 3天内: score = 0.8+
- 14天内: score = 0.6
- 30天内: score = 0.55
数据模型
Match - 匹配结果
from src.matcher.models import Match
match = Match(
field="InvoiceNumber",
value="100017500321",
bbox=(100.0, 200.0, 300.0, 220.0),
page_no=0,
score=0.95,
matched_text="100017500321",
context_keywords=["fakturanr"]
)
# 转换为YOLO格式
yolo_annotation = match.to_yolo_format(
image_width=1200,
image_height=1600,
class_id=0
)
# "0 0.166667 0.131250 0.166667 0.012500"
TokenIndex - 空间索引
from src.matcher.token_index import TokenIndex
# 构建索引
index = TokenIndex(tokens, grid_size=100.0)
# 快速查找附近tokens (O(1)平均复杂度)
nearby = index.find_nearby(token, radius=200.0)
# 获取缓存的中心坐标
center = index.get_center(token)
# 获取缓存的小写文本
text_lower = index.get_text_lower(token)
上下文关键词
from src.matcher.context import CONTEXT_KEYWORDS, find_context_keywords
# 查看字段的上下文关键词
keywords = CONTEXT_KEYWORDS["InvoiceNumber"]
# ['fakturanr', 'fakturanummer', 'invoice', 'inv.nr', ...]
# 查找附近的关键词
found_keywords, boost_score = find_context_keywords(
tokens=tokens,
target_token=token,
field_name="InvoiceNumber",
context_radius=200.0,
token_index=index # 可选,提供则使用O(1)查找
)
支持的字段:
- InvoiceNumber
- InvoiceDate
- InvoiceDueDate
- OCR
- Bankgiro
- Plusgiro
- Amount
- supplier_organisation_number
- supplier_accounts
工具函数
from src.matcher.utils import (
normalize_dashes,
parse_amount,
tokens_on_same_line,
bbox_overlap,
DATE_PATTERN,
WHITESPACE_PATTERN,
NON_DIGIT_PATTERN,
DASH_PATTERN,
)
# 标准化各种破折号
text = normalize_dashes("123–456") # "123-456"
# 解析瑞典金额格式
amount = parse_amount("1 234,56 kr") # 1234.56
amount = parse_amount("239 00") # 239.00 (öre格式)
# 检查tokens是否在同一行
same_line = tokens_on_same_line(token1, token2)
# 计算bbox重叠度 (IoU)
overlap = bbox_overlap(bbox1, bbox2) # 0.0 - 1.0
🧪 测试
# 在WSL中运行
conda activate invoice-py311
# 运行所有matcher测试
pytest tests/matcher/ -v
# 运行特定策略测试
pytest tests/matcher/strategies/test_exact_matcher.py -v
# 查看覆盖率
pytest tests/matcher/ --cov=src/matcher --cov-report=html
测试覆盖:
- ✅ 77个测试全部通过
- ✅ TokenIndex 空间索引
- ✅ 5种匹配策略
- ✅ 上下文关键词
- ✅ 工具函数
- ✅ 去重逻辑
📊 重构成果
| 指标 | 重构前 | 重构后 | 改进 |
|---|---|---|---|
| field_matcher.py | 876行 | 205行 | ↓ 76% |
| 模块数 | 1 | 11 | 更清晰 |
| 最大文件大小 | 876行 | 154行 | 更易读 |
| 测试通过率 | - | 100% | ✅ |
🚀 使用示例
完整流程
from src.matcher import FieldMatcher, find_field_matches
# 1. 提取PDF tokens (使用PDF模块)
from src.pdf import PDFExtractor
extractor = PDFExtractor("invoice.pdf")
tokens = extractor.extract_tokens()
# 2. 准备字段值 (从CSV或数据库)
field_values = {
"InvoiceNumber": "100017500321",
"InvoiceDate": "2026-01-09",
"Amount": "1234.56",
}
# 3. 查找所有字段匹配
results = find_field_matches(tokens, field_values, page_no=0)
# 4. 使用结果
for field_name, matches in results.items():
if matches:
best_match = matches[0] # 已按score降序排列
print(f"{field_name}: {best_match.value} @ {best_match.bbox}")
print(f" Score: {best_match.score:.2f}")
print(f" Context: {best_match.context_keywords}")
添加自定义策略
from src.matcher.strategies.base import BaseMatchStrategy
from src.matcher.models import Match
class CustomMatcher(BaseMatchStrategy):
"""自定义匹配策略"""
def find_matches(self, tokens, value, field_name, token_index=None):
matches = []
# 实现你的匹配逻辑
for token in tokens:
if self._custom_match_logic(token.text, value):
match = Match(
field=field_name,
value=value,
bbox=token.bbox,
page_no=token.page_no,
score=0.85,
matched_text=token.text,
context_keywords=[]
)
matches.append(match)
return matches
def _custom_match_logic(self, token_text, value):
# 你的匹配逻辑
return True
# 在FieldMatcher中使用
from src.matcher import FieldMatcher
matcher = FieldMatcher()
matcher.custom_matcher = CustomMatcher()
🔧 维护指南
添加新的上下文关键词
CONTEXT_KEYWORDS = {
'InvoiceNumber': ['fakturanr', 'fakturanummer', 'invoice', '新关键词'],
# ...
}
调整匹配分数
编辑对应的策略文件:
- exact_matcher.py - 精确匹配分数
- fuzzy_matcher.py - 模糊匹配容差
- flexible_date_matcher.py - 日期距离分数
性能优化
- TokenIndex网格大小: 默认100px,可根据实际文档调整
- 上下文半径: 默认200px,可根据扫描DPI调整
- 去重网格: 默认50px,影响bbox重叠检测性能
📚 相关文档
- PDF模块文档 - Token提取
- Normalize模块文档 - 字段值标准化
- YOLO模块文档 - 标注生成
✅ 总结
这个模块化的matcher系统提供:
- 清晰的职责分离: 每个策略专注一个匹配方法
- 易于测试: 独立测试每个组件
- 高性能: O(1)空间索引,智能去重
- 可扩展: 轻松添加新策略
- 完整测试: 77个测试100%通过