"""金额工具:中文大写金额解析 + 与小写金额比对""" from __future__ import annotations import re from typing import Optional import cn2an # 小写金额正则 _NUM_PATTERN = re.compile(r"¥?\s*(\d{1,8}(?:,\d{3})*\.\d{2})") _TAX_AMOUNT_PATTERN = re.compile(r"税\s*额\s*[¥:]?\s*(\d{1,8}(?:,\d{3})*\.\d{2})") _PRETAX_PATTERN = re.compile(r"(?:不合?税价|不含税)\s*[¥:]?\s*(\d{1,8}(?:,\d{3})*\.\d{2})") _TOTAL_PATTERN = re.compile(r"价税合计[^\d]*[¥]?\s*(\d{1,8}(?:,\d{3})*\.\d{2})") def normalize_cn_amount(text: str) -> str: """中文金额归一化:圆→元、〇→零""" if not text: return "" return text.replace("圆", "元").replace("〇", "零").replace(" ", "") def extract_cn_amount(text: str) -> Optional[str]: """从原文里匹配出第一段大写金额字符串 策略(按优先级): 1. "价税合计" 后面括号内 2. 任意中括号里的中文金额 3. 含"元"或"圆"的最长中文字符串 """ if not text: return None # 1. 价税合计之后括号里 m = re.search(r"价税合计[^\((]*[\((]([零壹贰叁肆伍陆柒捌玖拾佰仟万亿圆元角分整]+)[\))]", text) if m: return m.group(1) # 2. 任意中括号里的中文金额 m = re.search(r"[\((]([零壹贰叁肆伍陆柒捌玖拾佰仟万亿圆元角分整]{3,30})[\))]", text) if m: return m.group(1) # 3. 含"元"/"圆"的中文片段 for cand in re.findall(r"[零壹贰叁肆伍陆柒捌玖拾佰仟万亿圆元角分整]{3,30}", text): if "元" in cand or "圆" in cand: return cand return None def parse_cn_amount(cn_text: str) -> Optional[float]: """把中文大写金额转 float,例如 '贰佰元整' → 200.0""" if not cn_text: return None try: s = normalize_cn_amount(cn_text) # cn2an 要求带 '元' 或 '圆' 结尾 s = s.rstrip("整") if not s.endswith("元"): s += "元" value = cn2an.cn2an(s, "smart") return float(value) except Exception: return None def amount_consistent(cn_text: Optional[str], num_amount: Optional[float]) -> Optional[bool]: """大写 vs 小写金额比对""" if cn_text is None or num_amount is None: return None cn_value = parse_cn_amount(cn_text) if cn_value is None: return None return abs(cn_value - num_amount) < 0.011 def extract_num_amount(text: str) -> Optional[float]: """从文本里提取第一个形如 1234.56 或 ¥1,234.56 的金额""" if not text: return None m = _NUM_PATTERN.search(text) if not m: return None try: return float(m.group(1).replace(",", "")) except ValueError: return None def extract_total_amount(text: str) -> Optional[float]: m = _TOTAL_PATTERN.search(text) if m: try: return float(m.group(1).replace(",", "")) except ValueError: pass return extract_num_amount(text) def extract_tax_amount(text: str) -> Optional[float]: m = _TAX_AMOUNT_PATTERN.search(text) if m: try: return float(m.group(1).replace(",", "")) except ValueError: pass return None def extract_pretax_amount(text: str) -> Optional[float]: m = _PRETAX_PATTERN.search(text) if m: try: return float(m.group(1).replace(",", "")) except ValueError: pass return None