127 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Pure helpers for splitting and normalizing raw supplier cells."""
from __future__ import annotations
import re
from dataclasses import dataclass
_SEPARATOR_RE = re.compile(r"[//、,,;;|\\\n]+")
_LABEL_RE = re.compile(r"(?P<label>[\u4e00-\u9fffA-Za-z0-9]+)\s*[::]")
@dataclass(frozen=True)
class SupplierMention:
name: str
role: str | None
material_type: str | None
source_text: str
method: str
confidence: float
def compact_text(value: str) -> str:
return re.sub(r"[\s()()【】\[\]{}]+", "", value.casefold()).strip()
def normalize_name(value: str) -> str:
value = re.sub(r"\s+", "", value).strip(" ::、,,;;/\\")
for suffix in ("有限责任公司", "有限公司", "公司"):
if value.endswith(suffix) and len(value) > len(suffix) + 1:
value = value[: -len(suffix)]
break
if value.endswith("市") and len(value) > 2:
# Keep city in canonical display names; this is only a normalized key.
pass
return value
def normalized_key(value: str) -> str:
return compact_text(normalize_name(value))
def supplier_match_key(value: str) -> str:
key = normalized_key(value)
return re.sub(r"(佛山|广州|深圳|东莞|中山|珠海)市", r"\1", key)
def resolve_canonical_name(name: str, purchase_order_names: list[str]) -> tuple[str, bool]:
"""Resolve a short/raw name to one unambiguous procurement full name."""
key = supplier_match_key(name)
if not key:
return name, False
exact = [item for item in purchase_order_names if supplier_match_key(item) == key]
if len(exact) == 1:
return exact[0], True
contains = [item for item in purchase_order_names if key in supplier_match_key(item)]
if len(contains) == 1:
return contains[0], True
return name, False
def infer_location(value: str) -> tuple[str | None, str | None]:
"""Infer only explicit locations present in a supplier name."""
compact = compact_text(value)
city_map = {
"佛山": ("佛山", "广东"),
"广州": ("广州", "广东"),
"深圳": ("深圳", "广东"),
"东莞": ("东莞", "广东"),
"中山": ("中山", "广东"),
"珠海": ("珠海", "广东"),
}
for token, location in city_map.items():
if token in compact:
return location
return None, None
def split_supplier_cell(value: str | None, *, material_type: str | None = None) -> list[SupplierMention]:
if not value or value.strip() in {"/", "-", "—", "无", "未知"}:
return []
text = value.strip()
mentions: list[SupplierMention] = []
current_role: str | None = None
current_material = material_type
for chunk in _SEPARATOR_RE.split(text):
chunk = chunk.strip()
if not chunk:
continue
bare_label = chunk.rstrip(" ::")
if bare_label in {"内料", "包材", "包装", "瓶子", "泵头", "盖子", "软管", "彩盒", "物料", "加工厂", "供应商"}:
current_role = "加工厂" if "工厂" in bare_label else "供应商"
if bare_label not in {"加工厂", "供应商"}:
current_material = bare_label
continue
if chunk.endswith((":", ":")) and bare_label:
current_role = "加工厂" if "工厂" in bare_label else "供应商"
material_label = re.sub(r"(?:加工厂|工厂|供应商)$", "", bare_label)
if material_label:
current_material = material_label
continue
labels = list(_LABEL_RE.finditer(chunk))
if labels:
label = labels[-1].group("label")
current_role = "加工厂" if "工厂" in label else "供应商"
material_label = re.sub(r"(?:加工厂|工厂|供应商)$", "", label)
if material_label:
current_material = material_label
chunk = chunk[labels[-1].end() :].strip()
# A comma can separate material labels from the actual name.
if not chunk or chunk in {"/", "-", "—"}:
continue
name = normalize_name(chunk)
if not name:
continue
mentions.append(
SupplierMention(
name=name,
role=current_role,
material_type=current_material,
source_text=chunk,
method="delimiter" if len(_SEPARATOR_RE.findall(text)) else "raw",
confidence=0.95 if len(_SEPARATOR_RE.findall(text)) else 0.75,
)
)
return mentions