From d2257b0fce7816c0adaa7eeb00cde00068d16abb Mon Sep 17 00:00:00 2001 From: xiaoxue_admin <9283713@qq.com> Date: Tue, 23 Jun 2026 00:10:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=A7=91=E7=9B=AE=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E6=8F=90=E5=8F=96v2=20+=20=E6=91=98=E8=A6=81=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 摘要:排除期号文本(J22年第9期/22年第9期等) 2. 科目补位v2: - 先提取清洗后名称再评分(而非评分原始OCR文本) - 评分窗口±30px~+500px(下方优先,允许同行偏移) - 始终运行补位,与内联名称比分(内联基线10分) - y距惩罚加强至1/10(防跨行误抓) - 排除非科目文本(房号模式\d-\d{3,4}、未到账等) - 最小名称长度3(排除假台等噪声) 3. 之前code_map去重保留(Bug 1) --- core/parser.py | 118 +++++++++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 42 deletions(-) diff --git a/core/parser.py b/core/parser.py index 4010a32..d9aee50 100644 --- a/core/parser.py +++ b/core/parser.py @@ -240,54 +240,88 @@ class K3VoucherParser: name_candidates.sort(key=lambda x: x[1]) for code in codes: + # 对所有代码行(即使已有内联名称)运行补位评分 + candidates = [] + + # 基线:内联名称(如已存在,分数加成) if code['name'] and len(code['name']) >= 2: - continue # 已有名称,跳过 - # 评分制选取最佳名称补位:优先含全角横杠的科目名称文本 - scored = [] + candidates.append({ + 'name': code['name'], + 'is_inline': True, + 'y': code['y_pos'], + 'x': code['x_pos'], + 'raw': '', + }) + for t, cy, cx in name_candidates: - if cy <= code['y_pos']: - continue # 只找下方的 + # 只找下方 ±30px(允许同行偏移,但不找上方无关文本) + if cy < code['y_pos'] - 30: + continue + if cy > code['y_pos'] + 500: + continue if abs(cx - code['x_pos']) > 600: continue - dist = cy - code['y_pos'] - if dist >= 600: + + # 先提取清洗后的名称 + n = t.lstrip('--—= ') + n = re.sub(r'[\d.\-—=-]+$', '', n).strip() + n = n.lstrip('-—-=') + if '\\' in n: + n = n.split('\\', 1)[0].strip() + else: + idx = max(n.rfind('-'), n.rfind('-'), n.rfind('—')) + if idx >= 0: + n = n[idx+1:].strip() + if not n or len(n) < 3: + continue + # 排除明显的非科目名称文本 + if any(k in t for k in ['未到账', '过账', 'Manager', '出纳', '制单', '审核', '经办']): + continue + # 排除房号/楼栋模式文本(如 "假日里C1二单元假日重C1-2-0201") + if re.search(r'\d-\d{3,4}', t): continue - # 评分:高的优先 - score = 0 - if '-' in t or '-' in t: - score += 10 # 含横杠(全角/ASCII)→ 科目名称信号 - if re.search(r'\d{10,}', t): - score -= 30 # 银行账号(10位以上数字)→ 强烈惩罚 - if len(t) >= 4: - score += 3 # 长文本更可能是有效科目名 - # 同列优先(科目名称通常在代码同一列下方) - x_dist = abs(cx - code['x_pos']) - if x_dist < 50: - score += 25 - elif x_dist < 150: - score += 15 - elif x_dist < 300: - score += 5 - score -= dist / 30 # 近的优先(tiebreaker) - scored.append((score, t, cy, cx)) + candidates.append({ + 'name': n, + 'is_inline': False, + 'y': cy, + 'x': cx, + 'raw': t, + }) - if not scored: + if not candidates: continue - scored.sort(key=lambda x: -x[0]) - t, cy, cx = scored[0][1], scored[0][2], scored[0][3] - # 按用户规则提取名称(先清洗尾部全角横杠,防止取到最后无内容) - n = t.lstrip('--—= ') - n = re.sub(r'[\d.\-—=-]+$', '', n).strip() - n = n.lstrip('-—-=') - if '\\' in n: - n = n.split('\\', 1)[0].strip() - else: - idx = max(n.rfind('-'), n.rfind('-'), n.rfind('—')) - if idx >= 0: - n = n[idx+1:].strip() - if n and len(n) >= 2: - code['name'] = n + + # 评分:基于清洗后的名称,而非原始 OCR 文本 + for cand in candidates: + score = 0 + if cand['is_inline']: + score = 10 # 内联名称基线分(有实际来源,更高可信度) + else: + if '-' in cand['raw'] or '-' in cand['raw']: + score += 10 # 含横杠信号 + if len(cand['name']) >= 4: + score += 3 # 长名称更可信 + x_dist = abs(cand['x'] - code['x_pos']) + if x_dist < 50: + score += 25 + elif x_dist < 150: + score += 15 + elif x_dist < 300: + score += 5 + if re.search(r'\d{10,}', cand['raw']): + score -= 15 # 原始文本含长数字串则轻度惩罚 + # 非科目名称特征文本 + if any(k in cand['raw'] for k in ['未到账', '过账', 'Manager']): + score -= 20 + score -= abs(cand['y'] - code['y_pos']) / 10 # 近的优先(y差10px扣1分,防止跨行误抓) + cand['score'] = score + + candidates.sort(key=lambda c: -c['score']) + best = candidates[0] + + if not best['is_inline']: + code['name'] = best['name'] return codes @@ -384,9 +418,9 @@ class K3VoucherParser: # 按 y 排序,取最靠上且有意义的候选 candidates.sort(key=lambda x: x[1]) - # 过滤掉日期/期号类文本 + # 过滤掉日期/期号类文本(如"J22年第9期"、"022年第9期") real_summaries = [t for t, y in candidates - if not re.match(r'\d{3}年第\d+期', t) + if not re.match(r'.*\d{1,4}年第?\d{1,2}期', t) and not re.match(r'[::]\d+', t) and '日期' not in t and len(t) >= 6]