fix: 科目名称提取v2 + 摘要修复
1. 摘要:排除期号文本(J22年第9期/22年第9期等)
2. 科目补位v2:
- 先提取清洗后名称再评分(而非评分原始OCR文本)
- 评分窗口±30px~+500px(下方优先,允许同行偏移)
- 始终运行补位,与内联名称比分(内联基线10分)
- y距惩罚加强至1/10(防跨行误抓)
- 排除非科目文本(房号模式\d-\d{3,4}、未到账等)
- 最小名称长度3(排除假台等噪声)
3. 之前code_map去重保留(Bug 1)
This commit is contained in:
parent
f1041972a7
commit
d2257b0fce
118
core/parser.py
118
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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue