|
|
@@ -12,8 +12,10 @@ from fuzzywuzzy import fuzz
|
|
|
class OCRResultComparator:
|
|
|
def __init__(self):
|
|
|
self.differences = []
|
|
|
- self.similarity_threshold = 85 # 相似度阈值,超过85%认为是匹配的
|
|
|
- self.max_paragraph_window = 6 # 最大合并段落数
|
|
|
+ self.similarity_threshold = 85
|
|
|
+ self.max_paragraph_window = 6
|
|
|
+ self.table_comparison_mode = 'standard' # 新增:表格比较模式
|
|
|
+ self.header_similarity_threshold = 80 # 表头相似度阈值
|
|
|
|
|
|
def normalize_text(self, text: str) -> str:
|
|
|
"""标准化文本:去除多余空格、回车等无效字符"""
|
|
|
@@ -119,11 +121,14 @@ class OCRResultComparator:
|
|
|
|
|
|
def extract_paragraphs(self, md_content: str) -> List[str]:
|
|
|
"""提取段落文本"""
|
|
|
- # 移除表格
|
|
|
- content = re.sub(r'<table>.*?</table>', '', md_content, flags=re.DOTALL)
|
|
|
- # 移除HTML标签
|
|
|
+ # 移除表格 - 修复正则表达式
|
|
|
+ # 使用 IGNORECASE 和 DOTALL 标志
|
|
|
+ content = re.sub(r'<table[^>]*>.*?</table>', '', md_content, flags=re.DOTALL | re.IGNORECASE)
|
|
|
+
|
|
|
+ # 移除其他 HTML 标签
|
|
|
content = re.sub(r'<[^>]+>', '', content)
|
|
|
- # 移除Markdown注释
|
|
|
+
|
|
|
+ # 移除 Markdown 注释
|
|
|
content = re.sub(r'<!--.*?-->', '', content, flags=re.DOTALL)
|
|
|
|
|
|
# 分割段落
|
|
|
@@ -136,7 +141,7 @@ class OCRResultComparator:
|
|
|
if normalized:
|
|
|
paragraphs.append(normalized)
|
|
|
else:
|
|
|
- print(f"跳过的内容无效或图片段落: {line[0:30]}...")
|
|
|
+ print(f"跳过的内容无效或图片段落: {line[0:30] if line else ''}...")
|
|
|
|
|
|
return paragraphs
|
|
|
|
|
|
@@ -212,38 +217,6 @@ class OCRResultComparator:
|
|
|
except ValueError:
|
|
|
return 0.0
|
|
|
|
|
|
- def normalize_text_for_comparison(self, text: str) -> str:
|
|
|
- """增强的文本标准化 - 用于语义比较"""
|
|
|
- if not text:
|
|
|
- return ""
|
|
|
-
|
|
|
- # 移除Markdown格式标记
|
|
|
- text = re.sub(r'#{1,6}\s*', '', text) # 移除标题标记
|
|
|
- text = re.sub(r'\*\*(.+?)\*\*', r'\1', text) # 移除粗体标记
|
|
|
- text = re.sub(r'\*(.+?)\*', r'\1', text) # 移除斜体标记
|
|
|
- text = re.sub(r'`(.+?)`', r'\1', text) # 移除代码标记
|
|
|
- text = re.sub(r'<!--.*?-->', '', text, flags=re.DOTALL) # 移除注释
|
|
|
-
|
|
|
- # 统一标点符号
|
|
|
- punctuation_map = {
|
|
|
- ',': ',', '。': '.', ':': ':', ';': ';',
|
|
|
- '!': '!', '?': '?', '(': '(', ')': ')',
|
|
|
- '【': '[', '】': ']', '《': '<', '》': '>',
|
|
|
- '"': '"', '"': '"', ''': "'", ''': "'",
|
|
|
- '、': ',', '…': '...'
|
|
|
- }
|
|
|
-
|
|
|
- for chinese_punct, english_punct in punctuation_map.items():
|
|
|
- text = text.replace(chinese_punct, english_punct)
|
|
|
-
|
|
|
- # 移除多余的空白字符
|
|
|
- text = re.sub(r'\s+', ' ', text.strip())
|
|
|
-
|
|
|
- # 移除标点符号周围的空格
|
|
|
- text = re.sub(r'\s*([,.():;!?])\s*', r'\1', text)
|
|
|
-
|
|
|
- return text
|
|
|
-
|
|
|
def calculate_text_similarity(self, text1: str, text2: str) -> float:
|
|
|
"""改进的相似度计算"""
|
|
|
if not text1 and not text2:
|
|
|
@@ -258,16 +231,16 @@ class OCRResultComparator:
|
|
|
# 使用多种相似度算法
|
|
|
similarity_scores = [
|
|
|
fuzz.ratio(text1, text2),
|
|
|
- fuzz.partial_ratio(text1, text2),
|
|
|
- fuzz.token_sort_ratio(text1, text2),
|
|
|
- fuzz.token_set_ratio(text1, text2)
|
|
|
+ # fuzz.partial_ratio(text1, text2),
|
|
|
+ # fuzz.token_sort_ratio(text1, text2),
|
|
|
+ # fuzz.token_set_ratio(text1, text2)
|
|
|
]
|
|
|
|
|
|
# 对于包含关系,给予更高的权重
|
|
|
- if text1 in text2 or text2 in text1:
|
|
|
- max_score = max(similarity_scores)
|
|
|
- # 提升包含关系的相似度
|
|
|
- return min(100.0, max_score + 10)
|
|
|
+ # if text1 in text2 or text2 in text1:
|
|
|
+ # max_score = max(similarity_scores)
|
|
|
+ # # 提升包含关系的相似度
|
|
|
+ # return min(100.0, max_score + 10)
|
|
|
|
|
|
return max(similarity_scores)
|
|
|
|
|
|
@@ -275,9 +248,9 @@ class OCRResultComparator:
|
|
|
"""改进的段落匹配算法 - 更好地处理段落重组"""
|
|
|
differences = []
|
|
|
|
|
|
- # 直接调用normalize_text_for_comparison进行预处理
|
|
|
- meaningful_paras1 = [self.normalize_text_for_comparison(p) for p in paras1]
|
|
|
- meaningful_paras2 = [self.normalize_text_for_comparison(p) for p in paras2]
|
|
|
+ # 直接调用进行预处理
|
|
|
+ meaningful_paras1 = paras1
|
|
|
+ meaningful_paras2 = paras2
|
|
|
|
|
|
# 使用预处理后的段落进行匹配
|
|
|
used_paras1 = set()
|
|
|
@@ -384,8 +357,441 @@ class OCRResultComparator:
|
|
|
|
|
|
return best_match
|
|
|
|
|
|
+ def detect_column_type(self, column_values: List[str]) -> str:
|
|
|
+ """检测列的数据类型"""
|
|
|
+ if not column_values:
|
|
|
+ return 'text'
|
|
|
+
|
|
|
+ # 过滤空值
|
|
|
+ non_empty_values = [v for v in column_values if v and v.strip()]
|
|
|
+ if not non_empty_values:
|
|
|
+ return 'text'
|
|
|
+
|
|
|
+ # 检测是否为日期时间
|
|
|
+ datetime_patterns = [
|
|
|
+ r'\d{4}[-/]\d{1,2}[-/]\d{1,2}', # YYYY-MM-DD
|
|
|
+ r'\d{4}[-/]\d{1,2}[-/]\d{1,2}\s*\d{1,2}:\d{1,2}:\d{1,2}', # YYYY-MM-DD HH:MM:SS
|
|
|
+ r'\d{4}年\d{1,2}月\d{1,2}日', # 中文日期
|
|
|
+ ]
|
|
|
+
|
|
|
+ datetime_count = 0
|
|
|
+ for value in non_empty_values[:5]: # 检查前5个值
|
|
|
+ for pattern in datetime_patterns:
|
|
|
+ if re.search(pattern, value):
|
|
|
+ datetime_count += 1
|
|
|
+ break
|
|
|
+
|
|
|
+ if datetime_count >= len(non_empty_values[:5]) * 0.6:
|
|
|
+ return 'datetime'
|
|
|
+
|
|
|
+ # 检测是否为数字/金额
|
|
|
+ numeric_count = 0
|
|
|
+ for value in non_empty_values[:5]:
|
|
|
+ if self.is_numeric(value):
|
|
|
+ numeric_count += 1
|
|
|
+
|
|
|
+ if numeric_count >= len(non_empty_values[:5]) * 0.6:
|
|
|
+ return 'numeric'
|
|
|
+
|
|
|
+ # 默认为文本
|
|
|
+ return 'text'
|
|
|
+
|
|
|
+ def normalize_header_text(self, text: str) -> str:
|
|
|
+ """标准化表头文本"""
|
|
|
+ # 移除括号及其内容
|
|
|
+ text = re.sub(r'[((].*?[))]', '', text)
|
|
|
+ # 统一空格
|
|
|
+ text = re.sub(r'\s+', '', text)
|
|
|
+ # 移除特殊字符
|
|
|
+ text = re.sub(r'[^\w\u4e00-\u9fff]', '', text)
|
|
|
+ return text.lower().strip()
|
|
|
+
|
|
|
+ def compare_table_headers(self, headers1: List[str], headers2: List[str]) -> Dict:
|
|
|
+ """比较表格表头"""
|
|
|
+ result = {
|
|
|
+ 'match': True,
|
|
|
+ 'differences': [],
|
|
|
+ 'column_mapping': {}, # 列映射关系
|
|
|
+ 'similarity_scores': []
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(headers1) != len(headers2):
|
|
|
+ result['match'] = False
|
|
|
+ result['differences'].append({
|
|
|
+ 'type': 'header_count',
|
|
|
+ 'description': f'表头列数不一致: {len(headers1)} vs {len(headers2)}',
|
|
|
+ 'severity': 'critical'
|
|
|
+ })
|
|
|
+ return result
|
|
|
+
|
|
|
+ # 逐列比较表头
|
|
|
+ for i, (h1, h2) in enumerate(zip(headers1, headers2)):
|
|
|
+ norm_h1 = self.normalize_header_text(h1)
|
|
|
+ norm_h2 = self.normalize_header_text(h2)
|
|
|
+
|
|
|
+ similarity = self.calculate_text_similarity(norm_h1, norm_h2)
|
|
|
+ result['similarity_scores'].append({
|
|
|
+ 'column_index': i,
|
|
|
+ 'header1': h1,
|
|
|
+ 'header2': h2,
|
|
|
+ 'similarity': similarity
|
|
|
+ })
|
|
|
+
|
|
|
+ if similarity < self.header_similarity_threshold:
|
|
|
+ result['match'] = False
|
|
|
+ result['differences'].append({
|
|
|
+ 'type': 'header_mismatch',
|
|
|
+ 'column_index': i,
|
|
|
+ 'header1': h1,
|
|
|
+ 'header2': h2,
|
|
|
+ 'similarity': similarity,
|
|
|
+ 'description': f'第{i+1}列表头不匹配: "{h1}" vs "{h2}" (相似度: {similarity:.1f}%)',
|
|
|
+ 'severity': 'critical'
|
|
|
+ })
|
|
|
+ else:
|
|
|
+ result['column_mapping'][i] = i # 建立列映射
|
|
|
+
|
|
|
+ return result
|
|
|
+
|
|
|
+ def compare_cell_value(self, value1: str, value2: str, column_type: str,
|
|
|
+ column_name: str = '') -> Dict:
|
|
|
+ """比较单元格值"""
|
|
|
+ result = {
|
|
|
+ 'match': True,
|
|
|
+ 'difference': None
|
|
|
+ }
|
|
|
+
|
|
|
+ # 标准化值
|
|
|
+ v1 = self.normalize_text(value1)
|
|
|
+ v2 = self.normalize_text(value2)
|
|
|
+
|
|
|
+ if v1 == v2:
|
|
|
+ return result
|
|
|
+
|
|
|
+ # 根据列类型采用不同的比较策略
|
|
|
+ if column_type == 'numeric':
|
|
|
+ # 数字/金额比较
|
|
|
+ if self.is_numeric(v1) and self.is_numeric(v2):
|
|
|
+ num1 = self.parse_number(v1)
|
|
|
+ num2 = self.parse_number(v2)
|
|
|
+ if abs(num1 - num2) > 0.01: # 允许0.01的误差
|
|
|
+ result['match'] = False
|
|
|
+ result['difference'] = {
|
|
|
+ 'type': 'table_amount',
|
|
|
+ 'value1': value1,
|
|
|
+ 'value2': value2,
|
|
|
+ 'diff_amount': abs(num1 - num2),
|
|
|
+ 'description': f'数字不一致: {value1} vs {value2}'
|
|
|
+ }
|
|
|
+ else:
|
|
|
+ result['match'] = False
|
|
|
+ result['difference'] = {
|
|
|
+ 'type': 'table_amount_format_error',
|
|
|
+ 'value1': value1,
|
|
|
+ 'value2': value2,
|
|
|
+ 'description': f'数字格式错误: {value1} vs {value2}'
|
|
|
+ }
|
|
|
+
|
|
|
+ elif column_type == 'datetime':
|
|
|
+ # 日期时间比较
|
|
|
+ # 提取日期时间部分进行比较
|
|
|
+ datetime1 = self.extract_datetime(v1)
|
|
|
+ datetime2 = self.extract_datetime(v2)
|
|
|
+
|
|
|
+ if datetime1 != datetime2:
|
|
|
+ result['match'] = False
|
|
|
+ result['difference'] = {
|
|
|
+ 'type': 'table_datetime_mismatch',
|
|
|
+ 'value1': value1,
|
|
|
+ 'value2': value2,
|
|
|
+ 'description': f'日期时间不一致: {value1} vs {value2}'
|
|
|
+ }
|
|
|
+
|
|
|
+ else:
|
|
|
+ # 文本比较
|
|
|
+ similarity = self.calculate_text_similarity(v1, v2)
|
|
|
+ if similarity < self.similarity_threshold:
|
|
|
+ result['match'] = False
|
|
|
+ result['difference'] = {
|
|
|
+ 'type': 'table_text_mismatch',
|
|
|
+ 'value1': value1,
|
|
|
+ 'value2': value2,
|
|
|
+ 'similarity': similarity,
|
|
|
+ 'description': f'文本不一致: {value1} vs {value2} (相似度: {similarity:.1f}%)'
|
|
|
+ }
|
|
|
+
|
|
|
+ return result
|
|
|
+
|
|
|
+ def extract_datetime(self, text: str) -> str:
|
|
|
+ """提取并标准化日期时间"""
|
|
|
+ # 尝试匹配各种日期时间格式
|
|
|
+ patterns = [
|
|
|
+ (r'(\d{4})[-/](\d{1,2})[-/](\d{1,2})\s*(\d{1,2}):(\d{1,2}):(\d{1,2})',
|
|
|
+ lambda m: f"{m.group(1)}-{m.group(2).zfill(2)}-{m.group(3).zfill(2)} {m.group(4).zfill(2)}:{m.group(5).zfill(2)}:{m.group(6).zfill(2)}"),
|
|
|
+ (r'(\d{4})[-/](\d{1,2})[-/](\d{1,2})',
|
|
|
+ lambda m: f"{m.group(1)}-{m.group(2).zfill(2)}-{m.group(3).zfill(2)}"),
|
|
|
+ (r'(\d{4})年(\d{1,2})月(\d{1,2})日',
|
|
|
+ lambda m: f"{m.group(1)}-{m.group(2).zfill(2)}-{m.group(3).zfill(2)}"),
|
|
|
+ ]
|
|
|
+
|
|
|
+ for pattern, formatter in patterns:
|
|
|
+ match = re.search(pattern, text)
|
|
|
+ if match:
|
|
|
+ return formatter(match)
|
|
|
+
|
|
|
+ return text
|
|
|
+
|
|
|
+ def detect_table_header_row(self, table: List[List[str]]) -> int:
|
|
|
+ """
|
|
|
+ 智能检测表格的表头行索引
|
|
|
+
|
|
|
+ 策略:
|
|
|
+ 1. 查找包含典型表头关键词的行(如:序号、编号、时间、日期、金额等)
|
|
|
+ 2. 检查该行后续行是否为数据行(包含数字、日期等)
|
|
|
+ 3. 返回表头行的索引,如果找不到则返回0
|
|
|
+ """
|
|
|
+ # 常见表头关键词
|
|
|
+ header_keywords = [
|
|
|
+ # 通用表头
|
|
|
+ '序号', '编号', '时间', '日期', '名称', '类型', '金额', '数量', '单价',
|
|
|
+ '备注', '说明', '状态', '类别', '方式', '账号', '单号', '订单',
|
|
|
+ # 流水表格特定
|
|
|
+ '交易单号', '交易时间', '交易类型', '收/支', '支出', '收入',
|
|
|
+ '交易方式', '交易对方', '商户单号', '付款方式', '收款方',
|
|
|
+ # 英文表头
|
|
|
+ 'no', 'id', 'time', 'date', 'name', 'type', 'amount', 'status'
|
|
|
+ ]
|
|
|
+
|
|
|
+ for row_idx, row in enumerate(table):
|
|
|
+ if not row:
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 计算该行包含表头关键词的单元格数量
|
|
|
+ keyword_count = 0
|
|
|
+ for cell in row:
|
|
|
+ cell_lower = cell.lower().strip()
|
|
|
+ for keyword in header_keywords:
|
|
|
+ if keyword in cell_lower:
|
|
|
+ keyword_count += 1
|
|
|
+ break
|
|
|
+
|
|
|
+ # 如果超过一半的单元格包含表头关键词,认为是表头行
|
|
|
+ if keyword_count >= len(row) * 0.4 and keyword_count >= 2:
|
|
|
+ # 验证:检查下一行是否像数据行
|
|
|
+ if row_idx + 1 < len(table):
|
|
|
+ next_row = table[row_idx + 1]
|
|
|
+ if self.is_data_row(next_row):
|
|
|
+ print(f" 📍 检测到表头在第 {row_idx + 1} 行")
|
|
|
+ return row_idx
|
|
|
+
|
|
|
+ # 如果没有找到明确的表头行,返回0(默认第一行)
|
|
|
+ print(f" ⚠️ 未检测到明确表头,默认使用第1行")
|
|
|
+ return 0
|
|
|
+
|
|
|
+ def is_data_row(self, row: List[str]) -> bool:
|
|
|
+ """判断是否为数据行(包含数字、日期等)"""
|
|
|
+ data_pattern_count = 0
|
|
|
+
|
|
|
+ for cell in row:
|
|
|
+ if not cell:
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 检查是否包含数字
|
|
|
+ if re.search(r'\d', cell):
|
|
|
+ data_pattern_count += 1
|
|
|
+
|
|
|
+ # 检查是否为日期时间格式
|
|
|
+ if re.search(r'\d{4}[-/年]\d{1,2}[-/月]\d{1,2}', cell):
|
|
|
+ data_pattern_count += 1
|
|
|
+
|
|
|
+ # 如果超过一半的单元格包含数据特征,认为是数据行
|
|
|
+ return data_pattern_count >= len(row) * 0.5
|
|
|
+
|
|
|
+ def compare_table_flow_list(self, table1: List[List[str]], table2: List[List[str]]) -> List[Dict]:
|
|
|
+ """专门的流水列表表格比较算法 - 支持表头不在第一行"""
|
|
|
+ differences = []
|
|
|
+
|
|
|
+ if not table1 or not table2:
|
|
|
+ return [{
|
|
|
+ 'type': 'table_empty',
|
|
|
+ 'description': '表格为空',
|
|
|
+ 'severity': 'critical'
|
|
|
+ }]
|
|
|
+
|
|
|
+ print(f"\n📋 开始流水表格对比...")
|
|
|
+
|
|
|
+ # 第一步:智能检测表头位置
|
|
|
+ header_row_idx1 = self.detect_table_header_row(table1)
|
|
|
+ header_row_idx2 = self.detect_table_header_row(table2)
|
|
|
+
|
|
|
+ if header_row_idx1 != header_row_idx2:
|
|
|
+ differences.append({
|
|
|
+ 'type': 'table_header_position', # ✅ 已经是 table_ 开头
|
|
|
+ 'position': '表头位置',
|
|
|
+ 'file1_value': f'第{header_row_idx1 + 1}行',
|
|
|
+ 'file2_value': f'第{header_row_idx2 + 1}行',
|
|
|
+ 'description': f'表头位置不一致: 文件1在第{header_row_idx1 + 1}行,文件2在第{header_row_idx2 + 1}行',
|
|
|
+ 'severity': 'high'
|
|
|
+ })
|
|
|
+
|
|
|
+ # 第二步:比对表头前的内容(按单元格比对)
|
|
|
+ if header_row_idx1 > 0 or header_row_idx2 > 0:
|
|
|
+ print(f"\n📝 对比表头前的内容...")
|
|
|
+
|
|
|
+ # 提取表头前的内容作为单独的"表格"
|
|
|
+ pre_header_table1 = table1[:header_row_idx1] if header_row_idx1 > 0 else []
|
|
|
+ pre_header_table2 = table2[:header_row_idx2] if header_row_idx2 > 0 else []
|
|
|
+
|
|
|
+ if pre_header_table1 or pre_header_table2:
|
|
|
+ # 复用compare_tables方法进行比对
|
|
|
+ pre_header_diffs = self.compare_tables(pre_header_table1, pre_header_table2)
|
|
|
+
|
|
|
+ # ✅ 修改:统一类型为 table_pre_header
|
|
|
+ for diff in pre_header_diffs:
|
|
|
+ diff['type'] = 'table_pre_header' # 改为 table_ 开头
|
|
|
+ diff['position'] = f"表头前{diff['position']}"
|
|
|
+ diff['severity'] = 'medium'
|
|
|
+ print(f" ⚠️ {diff['position']}: {diff['description']}")
|
|
|
+
|
|
|
+ differences.extend(pre_header_diffs)
|
|
|
+
|
|
|
+ # 第三步:比较表头
|
|
|
+ headers1 = table1[header_row_idx1]
|
|
|
+ headers2 = table2[header_row_idx2]
|
|
|
+
|
|
|
+ print(f"\n📋 对比表头...")
|
|
|
+ print(f" 文件1表头 (第{header_row_idx1 + 1}行): {headers1}")
|
|
|
+ print(f" 文件2表头 (第{header_row_idx2 + 1}行): {headers2}")
|
|
|
+
|
|
|
+ header_result = self.compare_table_headers(headers1, headers2)
|
|
|
+
|
|
|
+ if not header_result['match']:
|
|
|
+ print(f"\n❌ 表头不匹配,严重错误!")
|
|
|
+ for diff in header_result['differences']:
|
|
|
+ print(f" - {diff['description']}")
|
|
|
+ differences.append({
|
|
|
+ 'type': 'table_header_critical', # ✅ 已经是 table_ 开头
|
|
|
+ 'position': '表头',
|
|
|
+ 'file1_value': ', '.join(headers1),
|
|
|
+ 'file2_value': ', '.join(headers2),
|
|
|
+ 'description': diff['description'],
|
|
|
+ 'severity': 'critical'
|
|
|
+ })
|
|
|
+ return differences
|
|
|
+
|
|
|
+ print(f"✅ 表头匹配成功")
|
|
|
+
|
|
|
+ # 第四步:检测列类型
|
|
|
+ column_types = []
|
|
|
+ for col_idx in range(len(headers1)):
|
|
|
+ col_values1 = [
|
|
|
+ row[col_idx]
|
|
|
+ for row in table1[header_row_idx1 + 1:]
|
|
|
+ if col_idx < len(row)
|
|
|
+ ]
|
|
|
+ col_type = self.detect_column_type(col_values1)
|
|
|
+ column_types.append(col_type)
|
|
|
+ print(f" 列 {col_idx + 1} ({headers1[col_idx]}): {col_type}")
|
|
|
+
|
|
|
+ # 第五步:逐行比较数据
|
|
|
+ data_rows1 = table1[header_row_idx1 + 1:]
|
|
|
+ data_rows2 = table2[header_row_idx2 + 1:]
|
|
|
+
|
|
|
+ max_rows = max(len(data_rows1), len(data_rows2))
|
|
|
+
|
|
|
+ print(f"\n📊 开始逐行对比数据 (共{max_rows}行)...")
|
|
|
+
|
|
|
+ for row_idx in range(max_rows):
|
|
|
+ row1 = data_rows1[row_idx] if row_idx < len(data_rows1) else []
|
|
|
+ row2 = data_rows2[row_idx] if row_idx < len(data_rows2) else []
|
|
|
+
|
|
|
+ # 实际行号(加上表头行索引)
|
|
|
+ actual_row_num1 = header_row_idx1 + row_idx + 2
|
|
|
+ actual_row_num2 = header_row_idx2 + row_idx + 2
|
|
|
+
|
|
|
+ if not row1:
|
|
|
+ differences.append({
|
|
|
+ 'type': 'table_row_missing', # ✅ 修改:改为 table_row_missing
|
|
|
+ 'position': f'第{actual_row_num1}行',
|
|
|
+ 'file1_value': '',
|
|
|
+ 'file2_value': ', '.join(row2),
|
|
|
+ 'description': f'文件1缺少第{actual_row_num1}行',
|
|
|
+ 'severity': 'high'
|
|
|
+ })
|
|
|
+ continue
|
|
|
+
|
|
|
+ if not row2:
|
|
|
+ differences.append({
|
|
|
+ 'type': 'table_row_missing', # ✅ 修改:改为 table_row_missing
|
|
|
+ 'position': f'第{actual_row_num2}行',
|
|
|
+ 'file1_value': ', '.join(row1),
|
|
|
+ 'file2_value': '',
|
|
|
+ 'description': f'文件2缺少第{actual_row_num2}行',
|
|
|
+ 'severity': 'high'
|
|
|
+ })
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 逐列比较
|
|
|
+ max_cols = max(len(row1), len(row2))
|
|
|
+ row_has_diff = False
|
|
|
+ row_diffs = []
|
|
|
+
|
|
|
+ for col_idx in range(max_cols):
|
|
|
+ cell1 = row1[col_idx] if col_idx < len(row1) else ''
|
|
|
+ cell2 = row2[col_idx] if col_idx < len(row2) else ''
|
|
|
+
|
|
|
+ # 跳过图片内容
|
|
|
+ if "[图片内容-忽略]" in cell1 or "[图片内容-忽略]" in cell2:
|
|
|
+ continue
|
|
|
+
|
|
|
+ column_type = column_types[col_idx] if col_idx < len(column_types) else 'text'
|
|
|
+ column_name = headers1[col_idx] if col_idx < len(headers1) else f'列{col_idx + 1}'
|
|
|
+
|
|
|
+ compare_result = self.compare_cell_value(cell1, cell2, column_type, column_name)
|
|
|
+
|
|
|
+ if not compare_result['match']:
|
|
|
+ row_has_diff = True
|
|
|
+ diff_info = compare_result['difference']
|
|
|
+ row_diffs.append({
|
|
|
+ 'column_index': col_idx,
|
|
|
+ 'column_name': column_name,
|
|
|
+ 'column_type': column_type,
|
|
|
+ **diff_info
|
|
|
+ })
|
|
|
+
|
|
|
+ if row_has_diff:
|
|
|
+ # 汇总该行的所有差异
|
|
|
+ diff_columns = [f"{d['column_name']}(列{d['column_index'] + 1})" for d in row_diffs]
|
|
|
+ differences.append({
|
|
|
+ 'type': 'table_row_data', # ✅ 修改:改为 table_row_data
|
|
|
+ 'position': f'第{actual_row_num1}行',
|
|
|
+ 'row_index': row_idx + 1,
|
|
|
+ 'affected_columns': diff_columns,
|
|
|
+ 'column_differences': row_diffs,
|
|
|
+ 'file1_value': ', '.join(row1),
|
|
|
+ 'file2_value': ', '.join(row2),
|
|
|
+ 'description': f'表格第{actual_row_num1}行在以下列有差异: {", ".join(diff_columns)}',
|
|
|
+ 'severity': 'medium'
|
|
|
+ })
|
|
|
+
|
|
|
+ print(f" ⚠️ 第{actual_row_num1}行有差异:")
|
|
|
+ for diff in row_diffs:
|
|
|
+ print(f" - {diff['column_name']}: {diff['description']}")
|
|
|
+
|
|
|
+ print(f"\n✅ 流水表格对比完成,发现 {len(differences)} 个差异")
|
|
|
+
|
|
|
+ return differences
|
|
|
+
|
|
|
+ def compare_tables_with_mode(self, table1: List[List[str]], table2: List[List[str]],
|
|
|
+ mode: str = 'standard') -> List[Dict]:
|
|
|
+ """根据模式选择表格比较算法"""
|
|
|
+ if mode == 'flow_list':
|
|
|
+ return self.compare_table_flow_list(table1, table2)
|
|
|
+ else:
|
|
|
+ return self.compare_tables(table1, table2)
|
|
|
+
|
|
|
def compare_files(self, file1_path: str, file2_path: str) -> Dict:
|
|
|
- """改进的文件比较方法"""
|
|
|
+ """改进的文件比较方法 - 支持不同的表格比较模式"""
|
|
|
# 读取文件
|
|
|
with open(file1_path, 'r', encoding='utf-8') as f:
|
|
|
content1 = f.read()
|
|
|
@@ -403,9 +809,12 @@ class OCRResultComparator:
|
|
|
# 比较结果
|
|
|
all_differences = []
|
|
|
|
|
|
- # 比较表格 (保持原有逻辑)
|
|
|
+ # 比较表格 - 使用指定的比较模式
|
|
|
if tables1 and tables2:
|
|
|
- table_diffs = self.compare_tables(tables1[0], tables2[0])
|
|
|
+ table_diffs = self.compare_tables_with_mode(
|
|
|
+ tables1[0], tables2[0],
|
|
|
+ mode=self.table_comparison_mode
|
|
|
+ )
|
|
|
all_differences.extend(table_diffs)
|
|
|
elif tables1 and not tables2:
|
|
|
all_differences.append({
|
|
|
@@ -462,199 +871,6 @@ class OCRResultComparator:
|
|
|
|
|
|
return result
|
|
|
|
|
|
- def generate_unified_diff(self, paras1: List[str], paras2: List[str], file1_path: str, file2_path: str) -> Dict:
|
|
|
- """
|
|
|
- 生成类似git diff的统一差异格式,并返回结构化数据
|
|
|
- """
|
|
|
- # 直接调用normalize_text_for_comparison进行预处理
|
|
|
- file1_lines = [self.normalize_text_for_comparison(p) for p in paras1]
|
|
|
- file2_lines = [self.normalize_text_for_comparison(p) for p in paras2]
|
|
|
-
|
|
|
- # 使用unified_diff生成差异
|
|
|
- diff = difflib.unified_diff(
|
|
|
- file1_lines,
|
|
|
- file2_lines,
|
|
|
- fromfile=file1_path,
|
|
|
- tofile=file2_path,
|
|
|
- lineterm='' # 确保每行末尾不添加额外字符
|
|
|
- )
|
|
|
-
|
|
|
- # 将差异生成器转换为列表
|
|
|
- diff_output = list(diff)
|
|
|
-
|
|
|
- # 解析diff输出并生成结构化数据
|
|
|
- structured_diff = self._parse_unified_diff(diff_output, file1_lines, file2_lines, file1_path, file2_path)
|
|
|
-
|
|
|
- return structured_diff
|
|
|
-
|
|
|
- def _parse_unified_diff(self, diff_lines: List[str], file1_lines: List[str], file2_lines: List[str],
|
|
|
- file1_path: str, file2_path: str) -> Dict:
|
|
|
- """解析unified diff输出并生成结构化数据"""
|
|
|
- differences = []
|
|
|
- current_hunk = None
|
|
|
- file1_line_num = 0
|
|
|
- file2_line_num = 0
|
|
|
-
|
|
|
- for line in diff_lines:
|
|
|
- if line.startswith('---') or line.startswith('+++'):
|
|
|
- continue
|
|
|
- elif line.startswith('@@'):
|
|
|
- # 解析hunk头部,例如: @@ -1,5 +1,4 @@
|
|
|
- import re
|
|
|
- match = re.match(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@', line)
|
|
|
- if match:
|
|
|
- file1_start = int(match.group(1))
|
|
|
- file1_count = int(match.group(2)) if match.group(2) else 1
|
|
|
- file2_start = int(match.group(3))
|
|
|
- file2_count = int(match.group(4)) if match.group(4) else 1
|
|
|
-
|
|
|
- current_hunk = {
|
|
|
- 'file1_start': file1_start,
|
|
|
- 'file1_count': file1_count,
|
|
|
- 'file2_start': file2_start,
|
|
|
- 'file2_count': file2_count
|
|
|
- }
|
|
|
- file1_line_num = file1_start - 1 # 转为0基索引
|
|
|
- file2_line_num = file2_start - 1
|
|
|
- elif line.startswith(' '):
|
|
|
- # 未改变的行
|
|
|
- file1_line_num += 1
|
|
|
- file2_line_num += 1
|
|
|
- elif line.startswith('-'):
|
|
|
- # 文件1中删除的行
|
|
|
- content = line[1:] # 去掉'-'前缀
|
|
|
- differences.append({
|
|
|
- 'type': 'paragraph',
|
|
|
- 'position': f'段落{file1_line_num + 1}',
|
|
|
- 'file1_value': content,
|
|
|
- 'file2_value': "",
|
|
|
- 'description': '文件1中独有的段落',
|
|
|
- 'similarity': 0.0,
|
|
|
- 'severity': 'medium',
|
|
|
- 'line_number': file1_line_num + 1,
|
|
|
- 'change_type': 'deletion'
|
|
|
- })
|
|
|
- file1_line_num += 1
|
|
|
- elif line.startswith('+'):
|
|
|
- # 文件2中添加的行
|
|
|
- content = line[1:] # 去掉'+'前缀
|
|
|
- differences.append({
|
|
|
- 'type': 'paragraph',
|
|
|
- 'position': f'段落{file2_line_num + 1}',
|
|
|
- 'file1_value': "",
|
|
|
- 'file2_value': content,
|
|
|
- 'description': '文件2中独有的段落',
|
|
|
- 'similarity': 0.0,
|
|
|
- 'severity': 'medium',
|
|
|
- 'line_number': file2_line_num + 1,
|
|
|
- 'change_type': 'addition'
|
|
|
- })
|
|
|
- file2_line_num += 1
|
|
|
-
|
|
|
- # 计算统计信息
|
|
|
- stats = {
|
|
|
- 'total_differences': len(differences),
|
|
|
- 'table_differences': 0, # diff不包含表格差异
|
|
|
- 'paragraph_differences': len(differences),
|
|
|
- 'amount_differences': 0,
|
|
|
- 'high_severity': len([d for d in differences if d.get('severity') == 'high']),
|
|
|
- 'medium_severity': len([d for d in differences if d.get('severity') == 'medium']),
|
|
|
- 'low_severity': len([d for d in differences if d.get('severity') == 'low']),
|
|
|
- 'deletions': len([d for d in differences if d.get('change_type') == 'deletion']),
|
|
|
- 'additions': len([d for d in differences if d.get('change_type') == 'addition'])
|
|
|
- }
|
|
|
-
|
|
|
- return {
|
|
|
- 'differences': differences,
|
|
|
- 'statistics': stats,
|
|
|
- 'file1_tables': 0,
|
|
|
- 'file2_tables': 0,
|
|
|
- 'file1_paragraphs': len(file1_lines),
|
|
|
- 'file2_paragraphs': len(file2_lines),
|
|
|
- 'file1_path': file1_path,
|
|
|
- 'file2_path': file2_path,
|
|
|
- 'diff_type': 'unified_diff'
|
|
|
- }
|
|
|
-
|
|
|
- def generate_unified_diff_report(self, paras1: List[str], paras2: List[str], file1_path: str, file2_path: str, output_file: str):
|
|
|
- """生成unified diff的JSON和Markdown报告"""
|
|
|
- # 生成结构化diff数据
|
|
|
- diff_data = self.generate_unified_diff(paras1, paras2, file1_path, file2_path)
|
|
|
-
|
|
|
- # 添加时间戳
|
|
|
- import datetime
|
|
|
- diff_data['timestamp'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
|
-
|
|
|
- # 生成JSON报告
|
|
|
- json_file = f"{output_file}_unified_diff.json"
|
|
|
- with open(json_file, 'w', encoding='utf-8') as f:
|
|
|
- json.dump(diff_data, f, ensure_ascii=False, indent=2)
|
|
|
-
|
|
|
- # 生成Markdown报告
|
|
|
- md_file = f"{output_file}_unified_diff.md"
|
|
|
- self._generate_unified_diff_markdown(diff_data, md_file)
|
|
|
-
|
|
|
- print(f"📄 Unified Diff JSON报告: {json_file}")
|
|
|
- print(f"📝 Unified Diff Markdown报告: {md_file}")
|
|
|
-
|
|
|
- return diff_data
|
|
|
-
|
|
|
- def _generate_unified_diff_markdown(self, diff_data: Dict, output_file: str):
|
|
|
- """生成unified diff的Markdown报告"""
|
|
|
- with open(output_file, 'w', encoding='utf-8') as f:
|
|
|
- f.write("# OCR结果Unified Diff对比报告\n\n")
|
|
|
-
|
|
|
- # 基本信息
|
|
|
- f.write("## 基本信息\n\n")
|
|
|
- f.write(f"- **文件1**: `{diff_data['file1_path']}`\n")
|
|
|
- f.write(f"- **文件2**: `{diff_data['file2_path']}`\n")
|
|
|
- f.write(f"- **比较时间**: {diff_data.get('timestamp', 'N/A')}\n")
|
|
|
- f.write(f"- **对比方式**: Unified Diff\n\n")
|
|
|
-
|
|
|
- # 统计信息
|
|
|
- stats = diff_data['statistics']
|
|
|
- f.write("## 统计信息\n\n")
|
|
|
- f.write(f"- 总差异数量: **{stats['total_differences']}**\n")
|
|
|
- f.write(f"- 删除行数: **{stats['deletions']}**\n")
|
|
|
- f.write(f"- 添加行数: **{stats['additions']}**\n")
|
|
|
- f.write(f"- 文件1段落数: {diff_data['file1_paragraphs']}\n")
|
|
|
- f.write(f"- 文件2段落数: {diff_data['file2_paragraphs']}\n\n")
|
|
|
-
|
|
|
- # 差异详情
|
|
|
- if diff_data['differences']:
|
|
|
- f.write("## 差异详情\n\n")
|
|
|
-
|
|
|
- # 按变更类型分组
|
|
|
- deletions = [d for d in diff_data['differences'] if d['change_type'] == 'deletion']
|
|
|
- additions = [d for d in diff_data['differences'] if d['change_type'] == 'addition']
|
|
|
-
|
|
|
- if deletions:
|
|
|
- f.write(f"### 🗑️ 删除内容 ({len(deletions)}项)\n\n")
|
|
|
- for i, diff in enumerate(deletions, 1):
|
|
|
- f.write(f"**{i}. 第{diff['line_number']}行**\n")
|
|
|
- f.write(f"```\n{diff['file1_value']}\n```\n\n")
|
|
|
-
|
|
|
- if additions:
|
|
|
- f.write(f"### ➕ 新增内容 ({len(additions)}项)\n\n")
|
|
|
- for i, diff in enumerate(additions, 1):
|
|
|
- f.write(f"**{i}. 第{diff['line_number']}行**\n")
|
|
|
- f.write(f"```\n{diff['file2_value']}\n```\n\n")
|
|
|
-
|
|
|
- # 详细差异表格
|
|
|
- f.write("## 详细差异列表\n\n")
|
|
|
- f.write("| 序号 | 类型 | 行号 | 变更类型 | 内容 | 描述 |\n")
|
|
|
- f.write("| --- | --- | --- | --- | --- | --- |\n")
|
|
|
-
|
|
|
- for i, diff in enumerate(diff_data['differences'], 1):
|
|
|
- change_icon = "🗑️" if diff['change_type'] == 'deletion' else "➕"
|
|
|
- content = diff['file1_value'] if diff['change_type'] == 'deletion' else diff['file2_value']
|
|
|
- f.write(f"| {i} | {change_icon} | {diff['line_number']} | {diff['change_type']} | ")
|
|
|
- f.write(f"`{content[:50]}{'...' if len(content) > 50 else ''}` | ")
|
|
|
- f.write(f"{diff['description']} |\n")
|
|
|
- else:
|
|
|
- f.write("## 结论\n\n")
|
|
|
- f.write("🎉 **完美匹配!没有发现任何差异。**\n\n")
|
|
|
-
|
|
|
def generate_json_report(self, comparison_result: Dict, output_file: str):
|
|
|
"""生成JSON格式的比较报告"""
|
|
|
# report_data = {
|
|
|
@@ -677,7 +893,7 @@ class OCRResultComparator:
|
|
|
json.dump(comparison_result, f, ensure_ascii=False, indent=2)
|
|
|
|
|
|
def generate_markdown_report(self, comparison_result: Dict, output_file: str):
|
|
|
- """生成Markdown格式的比较报告"""
|
|
|
+ """生成Markdown格式的比较报告 - 修复类型映射"""
|
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
|
f.write("# OCR结果对比报告\n\n")
|
|
|
|
|
|
@@ -694,6 +910,9 @@ class OCRResultComparator:
|
|
|
f.write(f"- 表格差异: **{stats['table_differences']}**\n")
|
|
|
f.write(f"- 金额差异: **{stats['amount_differences']}**\n")
|
|
|
f.write(f"- 段落差异: **{stats['paragraph_differences']}**\n")
|
|
|
+ f.write(f"- 高严重度: **{stats['high_severity']}**\n") # ✅ 新增
|
|
|
+ f.write(f"- 中严重度: **{stats['medium_severity']}**\n") # ✅ 新增
|
|
|
+ f.write(f"- 低严重度: **{stats['low_severity']}**\n") # ✅ 新增
|
|
|
f.write(f"- 文件1表格数: {comparison_result['file1_tables']}\n")
|
|
|
f.write(f"- 文件2表格数: {comparison_result['file2_tables']}\n")
|
|
|
f.write(f"- 文件1段落数: {comparison_result['file1_paragraphs']}\n")
|
|
|
@@ -706,6 +925,19 @@ class OCRResultComparator:
|
|
|
else:
|
|
|
f.write("## 差异摘要\n\n")
|
|
|
|
|
|
+ # ✅ 更新类型映射
|
|
|
+ type_name_map = {
|
|
|
+ 'table_amount': '💰 表格金额差异',
|
|
|
+ 'table_text': '📝 表格文本差异',
|
|
|
+ 'table_pre_header': '📋 表头前内容差异',
|
|
|
+ 'table_header_position': '📍 表头位置差异',
|
|
|
+ 'table_header_critical': '❌ 表头严重错误',
|
|
|
+ 'table_row_missing': '🚫 表格行缺失',
|
|
|
+ 'table_row_data': '📊 表格数据差异',
|
|
|
+ 'table_structure': '🏗️ 表格结构差异',
|
|
|
+ 'paragraph': '📄 段落差异'
|
|
|
+ }
|
|
|
+
|
|
|
# 按类型分组显示差异
|
|
|
diff_by_type = {}
|
|
|
for diff in comparison_result['differences']:
|
|
|
@@ -715,12 +947,7 @@ class OCRResultComparator:
|
|
|
diff_by_type[diff_type].append(diff)
|
|
|
|
|
|
for diff_type, diffs in diff_by_type.items():
|
|
|
- type_name = {
|
|
|
- 'table_amount': '💰 表格金额差异',
|
|
|
- 'table_text': '📝 表格文本差异',
|
|
|
- 'paragraph': '📄 段落差异',
|
|
|
- 'table_structure': '🏗️ 表格结构差异'
|
|
|
- }.get(diff_type, f'❓ {diff_type}')
|
|
|
+ type_name = type_name_map.get(diff_type, f'❓ {diff_type}')
|
|
|
|
|
|
f.write(f"### {type_name} ({len(diffs)}个)\n\n")
|
|
|
|
|
|
@@ -728,42 +955,55 @@ class OCRResultComparator:
|
|
|
f.write(f"**{i}. {diff['position']}**\n")
|
|
|
f.write(f"- 文件1: `{diff['file1_value']}`\n")
|
|
|
f.write(f"- 文件2: `{diff['file2_value']}`\n")
|
|
|
- f.write(f"- 说明: {diff['description']}\n\n")
|
|
|
+ f.write(f"- 说明: {diff['description']}\n")
|
|
|
+ if 'severity' in diff:
|
|
|
+ severity_icon = {'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '🟢'}
|
|
|
+ f.write(f"- 严重度: {severity_icon.get(diff['severity'], '⚪')} {diff['severity']}\n")
|
|
|
+ f.write("\n")
|
|
|
|
|
|
# 详细差异列表
|
|
|
if comparison_result['differences']:
|
|
|
f.write("## 详细差异列表\n\n")
|
|
|
- f.write("| 序号 | 类型 | 位置 | 文件1内容 | 文件2内容 | 描述 |\n")
|
|
|
- f.write("| --- | --- | --- | --- | --- | --- |\n")
|
|
|
+ f.write("| 序号 | 类型 | 位置 | 文件1内容 | 文件2内容 | 描述 | 严重度 |\n")
|
|
|
+ f.write("| --- | --- | --- | --- | --- | --- | --- |\n")
|
|
|
|
|
|
for i, diff in enumerate(comparison_result['differences'], 1):
|
|
|
+ severity = diff.get('severity', 'N/A')
|
|
|
f.write(f"| {i} | {diff['type']} | {diff['position']} | ")
|
|
|
f.write(f"`{diff['file1_value'][:50]}{'...' if len(diff['file1_value']) > 50 else ''}` | ")
|
|
|
f.write(f"`{diff['file2_value'][:50]}{'...' if len(diff['file2_value']) > 50 else ''}` | ")
|
|
|
- f.write(f"{diff['description']} |\n")
|
|
|
-
|
|
|
+ f.write(f"{diff['description']} | {severity} |\n")
|
|
|
def compare_ocr_results(file1_path: str, file2_path: str, output_file: str = "comparison_report",
|
|
|
- output_format: str = "markdown", ignore_images: bool = True):
|
|
|
+ output_format: str = "markdown", ignore_images: bool = True,
|
|
|
+ table_mode: str = 'standard', similarity_algorithm: str = 'ratio'):
|
|
|
"""
|
|
|
比较两个OCR结果文件
|
|
|
|
|
|
Args:
|
|
|
file1_path: 第一个OCR结果文件路径
|
|
|
file2_path: 第二个OCR结果文件路径
|
|
|
- output_file: 输出文件名(不含扩展名),默认为"comparison_report"
|
|
|
- output_format: 输出格式,选项: 'json', 'markdown', 'both',默认为'markdown'
|
|
|
- ignore_images: 是否忽略图片内容,默认为True
|
|
|
-
|
|
|
- Returns:
|
|
|
- Dict: 比较结果字典
|
|
|
+ output_file: 输出文件名(不含扩展名)
|
|
|
+ output_format: 输出格式 ('json', 'markdown', 'both')
|
|
|
+ ignore_images: 是否忽略图片内容
|
|
|
+ table_mode: 表格比较模式 ('standard', 'flow_list')
|
|
|
+ similarity_algorithm: 相似度算法 ('ratio', 'partial_ratio', 'token_sort_ratio', 'token_set_ratio')
|
|
|
"""
|
|
|
comparator = OCRResultComparator()
|
|
|
+ comparator.table_comparison_mode = table_mode
|
|
|
+
|
|
|
+ # 根据参数选择相似度算法
|
|
|
+ if similarity_algorithm == 'partial_ratio':
|
|
|
+ comparator.calculate_text_similarity = lambda t1, t2: fuzz.partial_ratio(t1, t2)
|
|
|
+ elif similarity_algorithm == 'token_sort_ratio':
|
|
|
+ comparator.calculate_text_similarity = lambda t1, t2: fuzz.token_sort_ratio(t1, t2)
|
|
|
+ elif similarity_algorithm == 'token_set_ratio':
|
|
|
+ comparator.calculate_text_similarity = lambda t1, t2: fuzz.token_set_ratio(t1, t2)
|
|
|
|
|
|
print("🔍 开始对比OCR结果...")
|
|
|
print(f"📄 文件1: {file1_path}")
|
|
|
print(f"📄 文件2: {file2_path}")
|
|
|
- print(f"📁 输出格式: {output_format}")
|
|
|
- print(f"🖼️ 图片处理: {'忽略' if ignore_images else '对比'}")
|
|
|
+ print(f"📊 表格模式: {table_mode}")
|
|
|
+ print(f"🔧 相似度算法: {similarity_algorithm}")
|
|
|
|
|
|
try:
|
|
|
# 执行比较
|
|
|
@@ -823,14 +1063,17 @@ def compare_ocr_results(file1_path: str, file2_path: str, output_file: str = "co
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
parser = argparse.ArgumentParser(description='OCR结果对比工具')
|
|
|
- parser.add_argument('file1', nargs= '?', help='第一个OCR结果文件路径')
|
|
|
- parser.add_argument('file2', nargs= '?', help='第二个OCR结果文件路径')
|
|
|
- parser.add_argument('-o', '--output', default='comparison_report',
|
|
|
- help='输出文件名(不含扩展名)')
|
|
|
+ parser.add_argument('file1', nargs='?', help='第一个OCR结果文件路径')
|
|
|
+ parser.add_argument('file2', nargs='?', help='第二个OCR结果文件路径')
|
|
|
+ parser.add_argument('-o', '--output', default='comparison_report', help='输出文件名')
|
|
|
parser.add_argument('-f', '--format', choices=['json', 'markdown', 'both'],
|
|
|
- default='markdown', help='输出格式: json, markdown, 或 both')
|
|
|
- parser.add_argument('--ignore-images', action='store_true',
|
|
|
- help='忽略图片内容(默认已启用)')
|
|
|
+ default='markdown', help='输出格式')
|
|
|
+ parser.add_argument('--ignore-images', action='store_true', help='忽略图片内容')
|
|
|
+ parser.add_argument('--table-mode', choices=['standard', 'flow_list'],
|
|
|
+ default='standard', help='表格比较模式')
|
|
|
+ parser.add_argument('--similarity-algorithm',
|
|
|
+ choices=['ratio', 'partial_ratio', 'token_sort_ratio', 'token_set_ratio'],
|
|
|
+ default='ratio', help='相似度算法')
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
@@ -840,16 +1083,18 @@ if __name__ == "__main__":
|
|
|
file2_path=args.file2,
|
|
|
output_file=args.output,
|
|
|
output_format=args.format,
|
|
|
- ignore_images=args.ignore_images
|
|
|
+ ignore_images=args.ignore_images,
|
|
|
+ table_mode=args.table_mode,
|
|
|
+ similarity_algorithm=args.similarity_algorithm
|
|
|
)
|
|
|
else:
|
|
|
- # 如果sys.argv没有被传入参数,则提供默认参数用于测试
|
|
|
+ # 测试流水表格对比
|
|
|
result = compare_ocr_results(
|
|
|
- file1_path='/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/data_DotsOCR_Results/2023年度报告母公司_page_001.md',
|
|
|
- file2_path='./output/pre_validation/2023年度报告母公司_page_001.md',
|
|
|
- # output_file=f'./output/comparison_result_{time.strftime("%Y%m%d_%H%M%S")}',
|
|
|
- output_file=f'./output/pre_validation/2023年度报告母公司_page_001_comparison_result',
|
|
|
+ file1_path='/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/data_PPStructureV3_Results/A用户_单元格扫描流水_page_001.md',
|
|
|
+ file2_path='/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/mineru-vlm-2.5.3_Results/A用户_单元格扫描流水_page_001.md',
|
|
|
+ output_file=f'./output/flow_list_comparison_{time.strftime("%Y%m%d_%H%M%S")}',
|
|
|
output_format='both',
|
|
|
- ignore_images=True
|
|
|
+ ignore_images=True,
|
|
|
+ table_mode='flow_list', # 使用流水表格模式
|
|
|
+ similarity_algorithm='ratio'
|
|
|
)
|
|
|
- print("\n🎉 OCR对比完成!")
|