feat: 票据合规预审助手首版 v1.0.0(verify-invoice-default)

This commit is contained in:
chenfuqiang
2026-09-13 13:48:32 +08:00
commit ddf8044dc0
207 changed files with 31090 additions and 0 deletions

102
scripts/util/cny_amount.py Normal file
View File

@@ -0,0 +1,102 @@
"""中文大写金额与阿拉伯金额互转(发票大小写金额一致性校验用)。
支持 "壹仟陆佰伍拾元整"、"壹佰贰拾叁元肆角伍分"、"拾元整" 等常见发票大写写法;
同时兼容 "¥1,234.50" / "1234.50元" 等阿拉伯写法的清洗。
解析失败返回 None,由调用方降级为"待确认",不做硬性驳回。
"""
from __future__ import annotations
import re
from typing import Optional
_DIGITS = {
"零": 0,
"壹": 1,
"贰": 2,
"叁": 3,
"肆": 4,
"伍": 5,
"陆": 6,
"柒": 7,
"捌": 8,
"玖": 9,
}
_SMALL_UNITS = {"拾": 10, "佰": 100, "仟": 1000}
_GROUP_UNITS = {"万": 10**4, "亿": 10**8}
_ALL_KEYS = r"零壹贰叁肆伍陆柒捌玖拾佰仟万亿元圆角分整"
_TRAILING_CHARS = re.compile(r"[人民币¥¥\s,,元圆整]|人民币$")
def clean_amount_text(text: str) -> str:
"""清洗阿拉伯金额文本,返回可 float() 的字符串(失败返回原文本去掉常见缀符)。"""
cleaned = _TRAILING_CHARS.sub("", (text or "").strip())
return cleaned
def parse_cny_upper(text: str) -> Optional[float]:
"""解析中文大写金额为数值;无法解析时返回 None。"""
if not text:
return None
raw = (text or "").strip()
raw = raw.replace("人民币", "").replace("¥", "").replace("¥", "").strip()
raw = re.sub(r"[\s,,]", "", raw)
if not raw:
return None
if re.search(r"[0-9]", raw):
return None # 纯阿拉伯数字写法不属于大写金额
if not re.fullmatch(rf"[{_ALL_KEYS}]+", raw):
return None
int_part, frac_part = raw, ""
for sep in ("元", "圆"):
if sep in raw:
int_part, frac_part = raw.split(sep, 1)
break
try:
yuan = _parse_integer_section(int_part) if int_part else 0.0
except ValueError:
return None
jiao = fen = 0
m_jiao = re.search(r"([零壹贰叁肆伍陆柒捌玖])角", frac_part)
if m_jiao:
jiao = _DIGITS[m_jiao.group(1)]
m_fen = re.search(r"([零壹贰叁肆伍陆柒捌玖])分", frac_part)
if m_fen:
fen = _DIGITS[m_fen.group(1)]
return round(yuan + jiao / 10 + fen / 100, 2)
def _parse_integer_section(section: str) -> float:
"""解析整数段(元之前的部分),如 壹仟陆佰伍拾 / 拾贰万零壹佰。"""
if not section:
return 0.0
if section == "零":
return 0.0
total = 0.0
group_value = 0
number = 0
for ch in section:
if ch in _DIGITS:
number = _DIGITS[ch]
elif ch in _SMALL_UNITS:
unit = _SMALL_UNITS[ch]
group_value += (number if number else 1) * unit
number = 0
elif ch in _GROUP_UNITS:
unit = _GROUP_UNITS[ch]
group_value = (group_value + number) * unit
total += group_value
group_value = 0
number = 0
else:
raise ValueError(f"非法大写字符: {ch}")
total += group_value + number
if total <= 0:
raise ValueError("大写金额解析为 0")
return total