# -*- coding: utf-8 -*-
"""
增强版 City 字段预测脚本（阶段 A）

功能：
    对 national_sites.db 中每条记录，综合以下规则预测其 City 字段：
    1. A 类：tail 以当代市名开头 → 直接取该市
    2. B 类：tail 以当代县名开头 → 取该县的父级市
    3. C 类增强：
        a. Province 是直辖市 → City = Province
        b. 去掉"地区"两字后能查到市 → 用该市
        c. 跨省大型遗产（长城/大运河/茶马古道/丝绸之路/金界壕/海上丝路）→ "跨省"
        d. 历史地名字典命中 → 使用字典映射
        e. 多地并列（、；，分隔）→ 分别匹配后分号拼接
        f. 仍失败 → 写入人工核对 CSV

输出：
    City预测_全量.csv   — 所有 5052 条的预测结果（含来源标记）
    City预测_手工核对.csv — 仅剩余匹配失败的条目

本脚本只读数据库，不做任何写入。
"""

import csv
import os
import re
import sqlite3

NS_DB = r'd:\BYP\Project\BYP\BYPsNotes\db\national_sites.db'
AD_DB = r'd:\BYP\Project\BYP\BYPsNotes\db\admin_division.db'
OUT_DIR = os.path.dirname(os.path.abspath(__file__))
OUT_ALL = os.path.join(OUT_DIR, 'City预测_全量.csv')
OUT_MANUAL = os.path.join(OUT_DIR, 'City预测_手工核对.csv')

# ---------- 省名别名 ----------
PROVINCE_ALIAS = {
    '新疆维吾尔自区': '新疆维吾尔自治区',
    '黑龙扛省': '黑龙江省',
}

PROVINCE_SHORT = {
    '内蒙古自治区': ['内蒙古'],
    '广西壮族自治区': ['广西'],
    '西藏自治区': ['西藏'],
    '宁夏回族自治区': ['宁夏'],
    '新疆维吾尔自治区': ['新疆'],
}

# ---------- 直辖市 ----------
MUNICIPALITIES = {'北京市', '上海市', '天津市', '重庆市'}

# ---------- 跨省大型遗产关键词（NameC 或 Location 包含即判跨省） ----------
CROSS_PROVINCE_KEYWORDS = ('长城', '京杭大运河', '茶马古道', '丝绸之路', '金界壕', '海上丝绸之路')

# ---------- 按 Id 特殊处理（Location 不完整的个别突陷）----------
SPECIAL_BY_ID = {
    3290: ('银川市', '水洞沟遗址在灵武市临河镇'),  # 宁夏水洞沟遗址
}

# ---------- 历史地名字典 (省, 原地名) -> (当代市名, 当代区县名) ----------
# 部分条目区县省略（None），表示仅到市即可
HISTORICAL_CITY = {
    # 北京（直辖市其实用规则 a 即可，这里提供更精细 City=北京市）
    # —— 直辖市由规则 a 统一处理，不必在此列举

    # 上海 / 天津 / 重庆 同上

    # 河北
    ('河北省', '定县'): ('定州市', None),
    ('河北省', '蓟县'): ('天津市', '蓟州区'),
    ('河北省', '遵化县'): ('唐山市', '遵化市'),
    ('河北省', '武安县'): ('邯郸市', '武安市'),
    ('河北省', '满城县'): ('保定市', '满城区'),
    ('河北省', '徐水县'): ('保定市', '徐水区'),
    ('河北省', '邢台县'): ('邢台市', '信都区'),
    ('河北省', '平泉县'): ('承德市', '平泉市'),
    ('河北省', '藁城市'): ('石家庄市', '藁城区'),
    ('河北省', '永年县'): ('邯郸市', '永年区'),
    ('河北省', '邯郸县'): ('邯郸市', '邯山区'),
    ('河北省', '万全县'): ('张家口市', '万全区'),

    # 山西
    ('山西省', '临汾县'): ('临汾市', '尧都区'),
    ('山西省', '朔县'): ('朔州市', '朔城区'),
    ('山西省', '离石市'): ('吕梁市', '离石区'),
    ('山西省', '潞城市'): ('长治市', '潞城区'),
    ('山西省', '太谷县'): ('晋中市', '太谷区'),
    ('山西省', '榆次市'): ('晋中市', '榆次区'),
    ('山西省', '长治县'): ('长治市', '上党区'),
    ('山西省', '屯留县'): ('长治市', '屯留区'),

    # 辽宁
    ('辽宁省', '旅大市'): ('大连市', None),
    ('辽宁省', '北镇县'): ('锦州市', '北镇市'),
    ('辽宁省', '北宁市'): ('锦州市', '北镇市'),
    ('辽宁省', '盖县'): ('营口市', '盖州市'),
    ('辽宁省', '营口县'): ('营口市', None),
    ('辽宁省', '凌源县'): ('朝阳市', '凌源市'),

    # 吉林
    ('吉林省', '辑安县'): ('通化市', '集安市'),
    ('吉林省', '集安县'): ('通化市', '集安市'),
    ('吉林省', '和龙县'): ('延边朝鲜族自治州', '和龙市'),

    # 黑龙江
    ('黑龙江省', '宁安县'): ('牡丹江市', '宁安市'),
    ('黑龙江省', '阿城县'): ('哈尔滨市', '阿城区'),
    ('黑龙江省', '阿城市'): ('哈尔滨市', '阿城区'),
    ('黑龙江省', '东宁县'): ('牡丹江市', '东宁市'),
    ('黑龙江省', '抚远县'): ('佳木斯市', '抚远市'),
    ('黑龙江省', '虎林市'): ('鸡西市', '虎林市'),
    ('黑龙江省', '孙吴县'): ('黑河市', '孙吴县'),
    ('黑龙江省', '绥滨县'): ('鹤岗市', '绥滨县'),

    # 江苏
    ('江苏省', '武进县'): ('常州市', '武进区'),
    ('江苏省', '吴县'): ('苏州市', '吴中区'),
    ('江苏省', '江宁县'): ('南京市', '江宁区'),
    ('江苏省', '吴江市'): ('苏州市', '吴江区'),
    ('江苏省', '金坛市'): ('常州市', '金坛区'),
    ('江苏省', '海安县'): ('南通市', '海安市'),
    ('江苏省', '姜堰市'): ('泰州市', '姜堰区'),
    ('江苏省', '宿豫县'): ('宿迁市', '宿豫区'),

    # 浙江
    ('浙江省', '桐乡县'): ('嘉兴市', '桐乡市'),
    ('浙江省', '鄞县'): ('宁波市', '鄞州区'),
    ('浙江省', '余姚县'): ('宁波市', '余姚市'),
    ('浙江省', '慈溪县'): ('宁波市', '慈溪市'),
    ('浙江省', '奉化市'): ('宁波市', '奉化区'),
    ('浙江省', '绍兴县'): ('绍兴市', '柯桥区'),
    ('浙江省', '临安市'): ('杭州市', '临安区'),
    ('浙江省', '龙泉县'): ('丽水市', '龙泉市'),
    ('浙江省', '余杭市'): ('杭州市', '余杭区'),
    ('浙江省', '东阳县'): ('金华市', '东阳市'),
    ('浙江省', '上虞市'): ('绍兴市', '上虞区'),

    # 安徽
    ('安徽省', '宣州市'): ('宣城市', '宣州区'),
    ('安徽省', '潜山县'): ('安庆市', '潜山市'),
    ('安徽省', '繁昌县'): ('芜湖市', '繁昌区'),
    ('安徽省', '阜南县'): ('阜阳市', '阜南县'),
    ('安徽省', '无为县'): ('芜湖市', '无为市'),

    # ===== 阶段 B 补充：从 14 条手工核对回填 =====
    ('广西壮族自治区', '三江县'): ('柳州市', '三江侗族自治县'),
    ('陕西省', '华阴县'): ('渭南市', '华阴市'),
    ('福建省', '南蒲县'): ('南平市', None),  # Location 有误，释迦文佛塔在政和县境内
    ('山东省', '长岛县'): ('烟台市', '蓬莱区'),
    ('山东省', '邹平县'): ('滨州市', '邹平市'),
    ('贵州省', '黔西县'): ('毕节市', '黔西市'),
    ('四川省', '隆昌县'): ('内江市', '隆昌市'),
    ('贵州省', '平坝县'): ('安顺市', '平坝区'),
    ('海南省', '西、南、中沙群岛办事处'): ('三沙市', None),

    # 福建
    ('福建省', '晋江县'): ('泉州市', '晋江市'),
    ('福建省', '南安县'): ('泉州市', '南安市'),
    ('福建省', '莆田县'): ('莆田市', None),
    ('福建省', '建阳市'): ('南平市', '建阳区'),
    ('福建省', '长乐市'): ('福州市', '长乐区'),
    ('福建省', '龙海市'): ('漳州市', '龙海区'),
    ('福建省', '永定县'): ('龙岩市', '永定区'),
    ('福建省', '沙县'): ('三明市', '沙县区'),

    # 江西
    ('江西省', '宁冈县'): ('吉安市', '井冈山市'),
    ('江西省', '瑞金县'): ('赣州市', '瑞金市'),
    ('江西省', '安源市'): ('萍乡市', '安源区'),
    ('江西省', '景德镇'): ('景德镇市', None),
    ('江西省', '星子县'): ('九江市', '庐山市'),
    ('江西省', '上饶县'): ('上饶市', '广信区'),
    ('江西省', '龙南县'): ('赣州市', '龙南市'),
    ('江西省', '赣县'): ('赣州市', '赣县区'),

    # 山东
    ('山东省', '肥城县'): ('泰安市', '肥城市'),
    ('山东省', '历城县'): ('济南市', '历城区'),
    ('山东省', '曲阜县'): ('济宁市', '曲阜市'),
    ('山东省', '章丘县'): ('济南市', '章丘区'),
    ('山东省', '章丘市'): ('济南市', '章丘区'),
    ('山东省', '益都县'): ('潍坊市', '青州市'),
    ('山东省', '泰安县'): ('泰安市', None),
    ('山东省', '长清县'): ('济南市', '长清区'),
    ('山东省', '蓬莱县'): ('烟台市', '蓬莱区'),
    ('山东省', '蓬莱市'): ('烟台市', '蓬莱区'),
    ('山东省', '邹县'): ('济宁市', '邹城市'),
    ('山东省', '滕县'): ('枣庄市', '滕州市'),
    ('山东省', '栖霞县'): ('烟台市', '栖霞市'),
    ('山东省', '莱芜市'): ('济南市', '莱芜区'),
    ('山东省', '掖县'): ('烟台市', '莱州市'),
    ('山东省', '平度县'): ('青岛市', '平度市'),
    ('山东省', '兖州市'): ('济宁市', '兖州区'),
    ('山东省', '茌平县'): ('聊城市', '茌平区'),
    ('山东省', '文登市'): ('威海市', '文登区'),

    # 河南
    ('河南省', '登封县'): ('郑州市', '登封市'),
    ('河南省', '巩县'): ('郑州市', '巩义市'),
    ('河南省', '新郑县'): ('郑州市', '新郑市'),
    ('河南省', '偃师县'): ('洛阳市', '偃师区'),
    ('河南省', '偃师市'): ('洛阳市', '偃师区'),
    ('河南省', '淮阳县'): ('周口市', '淮阳区'),
    ('河南省', '密县'): ('郑州市', '新密市'),
    ('河南省', '南阳县'): ('南阳市', '卧龙区'),
    ('河南省', '禹县'): ('许昌市', '禹州市'),
    ('河南省', '浙川县'): ('南阳市', '淅川县'),  # 错别字
    ('河南省', '开封县'): ('开封市', '祥符区'),

    # 湖北
    ('湖北省', '大冶县'): ('黄石市', '大冶市'),
    ('湖北省', '当阳县'): ('宜昌市', '当阳市'),
    ('湖北省', '均县'): ('十堰市', '丹江口市'),
    ('湖北省', '京山县'): ('荆门市', '京山市'),
    ('湖北省', '黄陂县'): ('武汉市', '黄陂区'),
    ('湖北省', '郧县'): ('十堰市', '郧阳区'),
    ('湖北省', '光化县'): ('襄阳市', '老河口市'),
    ('湖北省', '襄樊市'): ('襄阳市', None),
    ('湖北省', '钟祥县'): ('荆门市', '钟祥市'),

    # 湖南
    ('湖南省', '浏阳县'): ('长沙市', '浏阳市'),
    ('湖南省', '宁乡县'): ('长沙市', '宁乡市'),
    ('湖南省', '汩罗市'): ('岳阳市', '汨罗市'),  # 错别字 汩→汨
    ('湖南省', '望城县'): ('长沙市', '望城区'),
    ('湖南省', '祁阳县'): ('永州市', '祁阳市'),

    # 广东
    ('广东省', '东莞县'): ('东莞市', None),
    ('广东省', '花县'): ('广州市', '花都区'),
    ('广东省', '曲江县'): ('韶关市', '曲江区'),
    ('广东省', '南雄县'): ('韶关市', '南雄市'),
    ('广东省', '新会市'): ('江门市', '新会区'),
    ('广东省', '南海市'): ('佛山市', '南海区'),
    ('广东省', '从化市'): ('广州市', '从化区'),
    ('广东省', '潮安县'): ('潮州市', '潮安区'),

    # 广西
    ('广西壮族自治区', '桂平县'): ('贵港市', '桂平市'),
    ('广西壮族自治区', '邕宁县'): ('南宁市', '邕宁区'),

    # 海南
    ('海南省', '琼山市'): ('海口市', '琼山区'),

    # 四川
    ('四川省', '重庆市'): ('重庆市', None),  # 第一批时重庆还在四川
    ('四川省', '广元县'): ('广元市', '利州区'),
    ('四川省', '大足县'): ('重庆市', '大足区'),
    ('四川省', '绵阳县'): ('绵阳市', '涪城区'),
    ('四川省', '雅安县'): ('雅安市', '雨城区'),
    ('四川省', '灌县'): ('成都市', '都江堰市'),
    ('四川省', '巴中县'): ('巴中市', '巴州区'),
    ('四川省', '马尔康县'): ('阿坝藏族羌族自治州', '马尔康市'),
    ('四川省', '江油县'): ('绵阳市', '江油市'),
    ('四川省', '涪陵市'): ('重庆市', '涪陵区'),
    ('四川省', '邛崃县'): ('成都市', '邛崃市'),
    ('四川省', '新都县'): ('成都市', '新都区'),
    ('四川省', '新津县'): ('成都市', '新津区'),
    ('四川省', '峨眉县'): ('乐山市', '峨眉山市'),
    ('四川省', '合川市'): ('重庆市', '合川区'),
    ('四川省', '广汉县'): ('德阳市', '广汉市'),
    ('四川省', '宜宾县'): ('宜宾市', '叙州区'),
    ('四川省', '罗江县'): ('德阳市', '罗江区'),
    ('四川省', '射洪县'): ('遂宁市', '射洪市'),
    ('四川省', '巫山县'): ('重庆市', '巫山县'),
    ('四川省', '彭山县'): ('眉山市', '彭山区'),
    ('四川省', '凤翔县'): ('宝鸡市', '凤翔区'),  # 注：此条实为陕西凤翔，但四川无此县；保留作 fallback

    # 贵州
    ('贵州省', '毕节县'): ('毕节市', '七星关区'),
    ('贵州省', '盘县特区'): ('六盘水市', '盘州市'),
    ('贵州省', '兴仁县'): ('黔西南布依族苗族自治州', '兴仁市'),
    ('贵州省', '遵义县'): ('遵义市', '播州区'),
    ('贵州省', '铜仁地区'): ('铜仁市', None),

    # 云南
    ('云南省', '沧源县'): ('临沧市', '沧源佤族自治县'),
    ('云南省', '景洪县'): ('西双版纳傣族自治州', '景洪市'),
    ('云南省', '禄丰县'): ('楚雄彝族自治州', '禄丰市'),
    ('云南省', '中甸县'): ('迪庆藏族自治州', '香格里拉市'),
    ('云南省', '腾冲县'): ('保山市', '腾冲市'),
    ('云南省', '蒙自县'): ('红河哈尼族彝族自治州', '蒙自市'),
    ('云南省', '晋宁县'): ('昆明市', '晋宁区'),
    ('云南省', '丽江纳西族自治县'): ('丽江市', '玉龙纳西族自治县'),
    ('云南省', '江川县'): ('玉溪市', '江川区'),
    ('云南省', '普洱哈尼族彝族自治县'): ('普洱市', '宁洱哈尼族彝族自治县'),

    # 陕西
    ('陕西省', '延安县'): ('延安市', '宝塔区'),
    ('陕西省', '子长县'): ('延安市', '子长市'),
    ('陕西省', '彬县'): ('咸阳市', '彬州市'),
    ('陕西省', '长安县'): ('西安市', '长安区'),
    ('陕西省', '韩城县'): ('渭南市', '韩城市'),
    ('陕西省', '临潼县'): ('西安市', '临潼区'),
    ('陕西省', '兴平县'): ('咸阳市', '兴平市'),
    ('陕西省', '华县'): ('渭南市', '华州区'),
    ('陕西省', '高陵县'): ('西安市', '高陵区'),
    ('陕西省', '耀县'): ('铜川市', '耀州区'),
    ('陕西省', '户县'): ('西安市', '鄠邑区'),
    ('陕西省', '神木县'): ('榆林市', '神木市'),
    ('陕西省', '凤翔县'): ('宝鸡市', '凤翔区'),
    ('陕西省', '南郑县'): ('汉中市', '南郑区'),

    # 甘肃
    ('甘肃省', '敦煌县'): ('酒泉市', '敦煌市'),
    ('甘肃省', '安西县'): ('酒泉市', '瓜州县'),
    ('甘肃省', '武威县'): ('武威市', '凉州区'),
    ('甘肃省', '西峰市'): ('庆阳市', '西峰区'),

    # 青海
    ('青海省', '湟中县'): ('西宁市', '湟中区'),
    ('青海省', '乐都县'): ('海东市', '乐都区'),
    ('青海省', '民和县'): ('海东市', '民和回族土族自治县'),
    ('青海省', '玉树县'): ('玉树藏族自治州', '玉树市'),
    ('青海省', '同仁县'): ('黄南藏族自治州', '同仁市'),
    ('青海省', '平安县'): ('海东市', '平安区'),
    ('青海省', '海东地区'): ('海东市', None),

    # 宁夏
    ('宁夏回族自治区', '固原县'): ('固原市', '原州区'),

    # 西藏
    ('西藏自治区', '乃东县'): ('山南市', '乃东区'),
    ('西藏自治区', '日喀则县'): ('日喀则市', '桑珠孜区'),
    ('西藏自治区', '扎达县'): ('阿里地区', '札达县'),
    ('西藏自治区', '穷结县'): ('山南市', '琼结县'),
    ('西藏自治区', '昌都县'): ('昌都市', '卡若区'),
    ('西藏自治区', '昌都地区'): ('昌都市', None),
    ('西藏自治区', '山南地区'): ('山南市', None),
    ('西藏自治区', '日喀则地区'): ('日喀则市', None),
    ('西藏自治区', '林芝地区'): ('林芝市', None),
    ('西藏自治区', '那曲地区'): ('那曲市', None),

    # 新疆
    ('新疆维吾尔自治区', '库车县'): ('阿克苏地区', '库车市'),
    ('新疆维吾尔自治区', '吐鲁番县'): ('吐鲁番市', '高昌区'),
    ('新疆维吾尔自治区', '哈密地区'): ('哈密市', None),
    ('新疆维吾尔自治区', '吐鲁番地区'): ('吐鲁番市', None),
}


def load_division(ad_db):
    """与诊断脚本相同的加载逻辑"""
    conn = sqlite3.connect(ad_db)
    conn.row_factory = sqlite3.Row
    cur = conn.cursor()

    provinces = {}
    cur.execute('SELECT code, name FROM administrative_division WHERE level = 1')
    for row in cur.fetchall():
        provinces[row['name']] = {
            'code': row['code'],
            'cities': [],
            'county_info': {},
            'counties': [],
        }

    cur.execute('SELECT code, name, parent_code FROM administrative_division WHERE level = 2')
    city_to_prov = {}
    for row in cur.fetchall():
        cur2 = conn.cursor()
        cur2.execute('SELECT name FROM administrative_division WHERE code = ?', (row['parent_code'],))
        pr = cur2.fetchone()
        if not pr or pr['name'] not in provinces:
            continue
        provinces[pr['name']]['cities'].append(row['name'])
        city_to_prov[row['code']] = pr['name']

    cur.execute('SELECT code, name, parent_code FROM administrative_division WHERE level = 3')
    for row in cur.fetchall():
        pcode = row['parent_code']
        if pcode in city_to_prov:
            prov_name = city_to_prov[pcode]
            cur2 = conn.cursor()
            cur2.execute('SELECT name FROM administrative_division WHERE code = ?', (pcode,))
            parent_name = cur2.fetchone()['name']
        else:
            cur2 = conn.cursor()
            cur2.execute('SELECT name FROM administrative_division WHERE code = ?', (pcode,))
            pr = cur2.fetchone()
            if not pr or pr['name'] not in provinces:
                continue
            prov_name = pr['name']
            parent_name = prov_name
        provinces[prov_name]['counties'].append(row['name'])
        provinces[prov_name]['county_info'][row['name']] = parent_name

    conn.close()
    for p in provinces.values():
        p['cities'].sort(key=lambda x: -len(x))
        p['counties'].sort(key=lambda x: -len(x))
    return provinces


def strip_province(location, province):
    """去掉 Location 开头的省名前缀

    兼容：
        - 当代标准名（如 '黑龙江省'）
        - PROVINCE_ALIAS 里所有映射到该省的错别字变体（如 '黑龙扛省'、'新疆维吾尔自区'）
        - 省名简写（如 '新疆'、'内蒙古'）
        - 剩余字符串开头残留的重复 '省/市/自治区' 字样自动再剪一层
    """
    loc = (location or '').strip()
    candidates = [province]
    # 反向查找：对当前省的所有错别字别名
    for alias, canonical in PROVINCE_ALIAS.items():
        if canonical == province:
            candidates.append(alias)
    candidates.extend(PROVINCE_SHORT.get(province, []))
    candidates.sort(key=lambda x: -len(x))
    for c in candidates:
        if loc.startswith(c):
            rest = loc[len(c):]
            # 容错：如 '安徽省省阜南县' 剩下 '省阜南县'，再剪一次
            if rest.startswith('省') or rest.startswith('市'):
                rest = rest[1:]
            return rest.strip()
    return loc


def match_base(tail, province, provinces):
    """基础匹配（A/B 类），返回 city 或 None"""
    if province not in provinces:
        return None, None
    p = provinces[province]
    for city in p['cities']:
        if tail.startswith(city):
            return city, 'A'
    for county in p['counties']:
        if tail.startswith(county):
            parent = p['county_info'][county]
            return parent, 'B'
    return None, None


def predict_one(name_c, province_raw, location, provinces, record_id=None):
    """预测单条记录的 City，返回 (city, source)"""
    province = PROVINCE_ALIAS.get(province_raw, province_raw)

    # 规则 0: 按 Id 特殊绑定（最高优先级）
    if record_id is not None and record_id in SPECIAL_BY_ID:
        city, _note = SPECIAL_BY_ID[record_id]
        return city, 'special_byid'

    # 规则 c: 跨省大型遗产（优先判定，NameC 中含关键词）
    for kw in CROSS_PROVINCE_KEYWORDS:
        if kw in (name_c or ''):
            return '跨省', 'c_跨省遗产'

    # 新疆生产建设兵团
    if province_raw == '新疆生产建设兵团':
        return '新疆生产建设兵团', 'special_兵团'

    # 规则 a: 直辖市
    if province in MUNICIPALITIES:
        # 需要先检查是否命中 tail 的具体区；若命中区，City 仍为 Province（直辖市下就是市自己）
        return province, 'a_直辖市'

    tail = strip_province(location or '', province)
    if not tail:
        # 省+空，只能当作省会级 → 失败
        return None, 'fail_空Location'

    # A/B 类基础匹配
    city, cat = match_base(tail, province, provinces)
    if city:
        return city, cat

    # 规则 d: 历史地名字典
    # 按长度倒序在 tail 开头尝试字典 key
    keys_for_prov = sorted(
        [k for k in HISTORICAL_CITY if k[0] == province],
        key=lambda x: -len(x[1])
    )
    for (_, old) in keys_for_prov:
        if tail.startswith(old):
            city, _county = HISTORICAL_CITY[(province, old)]
            return city, 'd_历史字典'

    # 规则 b'（增强）：去掉 "XX地区" 前缀后再查
    m = re.match(r'([\u4e00-\u9fa5]{2,6}?)地区(.+)', tail)
    if m:
        rest = m.group(2)
        city2, cat2 = match_base(rest, province, provinces)
        if city2:
            return city2, 'b_去地区'

    # 规则 e: 多地并列（用 、；,；分隔符切分后递归）
    if re.search(r'[、；;,]', tail):
        parts = re.split(r'[、；;,]', tail)
        cities = []
        for part in parts:
            part = part.strip()
            if not part:
                continue
            # 每个 part 当作完整的一条来匹配（仅基础+字典）
            c, _ = match_base(part, province, provinces)
            if not c:
                for (_, old) in keys_for_prov:
                    if part.startswith(old):
                        c = HISTORICAL_CITY[(province, old)][0]
                        break
            if c and c not in cities:
                cities.append(c)
        if cities:
            return '；'.join(cities), 'e_多地并列'

    return None, 'fail_未命中'


def main():
    print(f'加载行政区划库：{AD_DB}')
    provinces = load_division(AD_DB)

    print(f'扫描国保库：{NS_DB}')
    conn = sqlite3.connect(NS_DB)
    conn.row_factory = sqlite3.Row
    cur = conn.cursor()
    cur.execute('''
        SELECT Id, NameC, Province, Location, Serial
        FROM NationalHistoricalandCulturalSites
        ORDER BY Id ASC
    ''')
    records = [dict(r) for r in cur.fetchall()]
    conn.close()
    print(f'  共 {len(records)} 条\n')

    source_counter = {}
    all_rows = []
    manual_rows = []

    for r in records:
        city, source = predict_one(r['NameC'], r['Province'], r['Location'], provinces, record_id=r['Id'])
        source_counter[source] = source_counter.get(source, 0) + 1
        row = {
            'Id': r['Id'],
            'Serial': r['Serial'],
            'Province': r['Province'],
            'NameC': r['NameC'],
            'Location': r['Location'],
            'City': city or '',
            '来源': source,
        }
        all_rows.append(row)
        if not city:
            manual_rows.append(row)

    # 写 CSV（utf-8-sig 便于 Excel 直接打开）
    fieldnames = ['Id', 'Serial', 'Province', 'NameC', 'Location', 'City', '来源']
    with open(OUT_ALL, 'w', encoding='utf-8-sig', newline='') as f:
        w = csv.DictWriter(f, fieldnames=fieldnames)
        w.writeheader()
        w.writerows(all_rows)
    if manual_rows:
        with open(OUT_MANUAL, 'w', encoding='utf-8-sig', newline='') as f:
            w = csv.DictWriter(f, fieldnames=fieldnames)
            w.writeheader()
            w.writerows(manual_rows)

    # 终端汇总
    total = len(records)
    print('=' * 60)
    print('来源分布')
    print('=' * 60)
    order = ['A', 'B', 'a_直辖市', 'b_去地区', 'c_跨省遗产',
             'd_历史字典', 'e_多地并列', 'special_兵团', 'special_byid',
             'fail_空Location', 'fail_未命中']
    for s in order:
        if s in source_counter:
            c = source_counter[s]
            print(f'  {s:<18} {c:>5}  ({c/total*100:>5.1f}%)')
    other = {k: v for k, v in source_counter.items() if k not in order}
    for k, v in other.items():
        print(f'  {k:<18} {v:>5}  ({v/total*100:>5.1f}%)')

    success = sum(v for k, v in source_counter.items() if not k.startswith('fail'))
    fail = total - success
    print(f'\n  成功预测：{success} / {total}  ({success/total*100:.1f}%)')
    print(f'  仍需人工：{fail}')

    print(f'\n全量预测写入：{OUT_ALL}')
    if manual_rows:
        print(f'手工核对写入：{OUT_MANUAL}（{len(manual_rows)} 条）')
    print('\n完成。')


if __name__ == '__main__':
    main()
