副本编号 LY-20261007-AC63(水印指纹已嵌入) 含 README、.gitignore;排除 .DS_Store / __pycache__ / .pytest_cache
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
import json
|
|
|
|
@dataclass(frozen=True)
|
|
class Location:
|
|
name: str
|
|
lon: float # 东经为正
|
|
lat: float
|
|
tz: str # IANA,如 "Asia/Shanghai"
|
|
|
|
class LocationError(Exception):
|
|
"""出生地无法解析 —— 必须向上抛,不得降级。"""
|
|
|
|
_CACHE: dict[str, Location] | None = None
|
|
_ALIASES: dict[str, str] | None = None
|
|
|
|
# 省级行政区简称/全称前缀,用于「新疆乌鲁木齐市」这类输入的兜底剥离
|
|
_PROVINCE_NAMES = (
|
|
"北京", "天津", "上海", "重庆",
|
|
"河北", "山西", "内蒙古", "辽宁", "吉林", "黑龙江",
|
|
"江苏", "浙江", "安徽", "福建", "江西", "山东",
|
|
"河南", "湖北", "湖南", "广东", "广西", "海南",
|
|
"四川", "贵州", "云南", "西藏", "陕西", "甘肃",
|
|
"青海", "宁夏", "新疆", "香港", "澳门", "台湾",
|
|
)
|
|
|
|
# 行政通名,长的必须排在短的前面(replace 顺序敏感)
|
|
_ADMIN_WORDS = (
|
|
"壮族自治区", "回族自治区", "维吾尔自治区", "自治区",
|
|
"特别行政区", "省", "市",
|
|
)
|
|
|
|
def _load() -> dict[str, Location]:
|
|
global _CACHE, _ALIASES
|
|
if _CACHE is None:
|
|
raw = json.loads(
|
|
(Path(__file__).parents[2] / "data" / "cities.json")
|
|
.read_text(encoding="utf-8")
|
|
)
|
|
_ALIASES = dict(raw.get("_aliases", {}))
|
|
_CACHE = {
|
|
k: Location(k, v["lon"], v["lat"], v["tz"])
|
|
for k, v in raw.items()
|
|
if not k.startswith("_") # 跳过 _meta / _aliases
|
|
}
|
|
return _CACHE
|
|
|
|
def _alias(key: str) -> str | None:
|
|
# 别名表:乌市、蓉城、羊城……(放在 cities.json 的 _aliases 段)
|
|
_load()
|
|
assert _ALIASES is not None
|
|
return _ALIASES.get(key)
|
|
|
|
def _strip_admin(text: str) -> str:
|
|
"""去掉「省/市/自治区/壮族自治区/回族自治区/维吾尔自治区/特别行政区」等字样。"""
|
|
for word in _ADMIN_WORDS:
|
|
text = text.replace(word, "")
|
|
return text
|
|
|
|
def resolve_location(text: str) -> Location:
|
|
"""
|
|
容错匹配:全称 → 去后缀(市/省/县/区)→ 别名 → 去行政通名(含省名前缀)。
|
|
命中即返回;未命中抛 LocationError(由 Agent 转成追问)。
|
|
"""
|
|
table = _load()
|
|
key = text.strip()
|
|
|
|
candidates: list[str | None] = [key, key.rstrip("市省县区"), _alias(key)]
|
|
|
|
stripped = _strip_admin(key)
|
|
candidates += [stripped, _alias(stripped)]
|
|
for province in _PROVINCE_NAMES:
|
|
if stripped != province and stripped.startswith(province):
|
|
rest = stripped[len(province):]
|
|
candidates += [rest, _alias(rest)]
|
|
|
|
for candidate in candidates:
|
|
if candidate and candidate in table:
|
|
return table[candidate]
|
|
raise LocationError(
|
|
f"未能解析出生地:{text!r}。请用户补充到地级市一级,"
|
|
f"如「四川省成都市」「新疆乌鲁木齐市」。"
|
|
)
|