feat: 票据合规预审助手首版 v1.0.0(verify-invoice-default)
This commit is contained in:
102
scripts/util/cny_amount.py
Normal file
102
scripts/util/cny_amount.py
Normal 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
|
||||
Reference in New Issue
Block a user