Files
moxiaonian d9da4301cd 公众授权版:normal · 1000 次 · 有效期至 2026-12-31
副本编号 LY-20261007-AC63(水印指纹已嵌入)
含 README、.gitignore;排除 .DS_Store / __pycache__ / .pytest_cache
2026-10-07 17:29:52 +08:00

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"如「四川省成都市」「新疆乌鲁木齐市」。"
)