Files
gaokao-volunteer-system/scripts/check_crowd_db_balance.py

253 lines
6.5 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""crowd_db 均衡性检查(地域 + 专业热度)。
检查项
1. 地域均衡性省会院校占比不应过高非直辖市省份 70%
2. 专业冷热均衡热门专业占比不应过高50%冷门专业不应过低5%
用途
- 诊断数据偏差
- 为后续人工审核提供清单
"""
from __future__ import annotations
import json
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# 省会城市映射
PROVINCE_CAPITALS = {
"湖南": "长沙",
"广东": "广州",
"江苏": "南京",
"山东": "济南",
"河北": "石家庄",
"浙江": "杭州",
"福建": "福州",
"安徽": "合肥",
"河南": "郑州",
"湖北": "武汉",
"四川": "成都",
"陕西": "西安",
"山西": "太原",
"辽宁": "沈阳",
"吉林": "长春",
"黑龙江": "哈尔滨",
"江西": "南昌",
"云南": "昆明",
"贵州": "贵阳",
"甘肃": "兰州",
"青海": "西宁",
"海南": "海口",
"新疆": "乌鲁木齐",
"内蒙古": "呼和浩特",
"广西": "南宁",
"宁夏": "银川",
"西藏": "拉萨",
"北京": "北京",
"天津": "天津",
"上海": "上海",
"重庆": "重庆",
}
# 专业热度关键词
HOT_MAJORS = [
"计算机",
"软件",
"人工智能",
"电气",
"自动化",
"电子",
"通信",
"金融",
"会计",
"经济",
"临床医学",
"口腔医学",
]
COLD_MAJORS = [
"农学",
"林学",
"园艺",
"植物保护",
"动物科学",
"地质",
"测绘",
"矿业",
"冶金",
"材料",
"化工",
"纺织",
"轻工",
"档案",
"图书馆",
"民族",
"哲学",
"历史",
"考古",
]
STABLE_MAJORS = [
"机械",
"土木",
"建筑",
"环境",
"水利",
"能源",
"交通",
"物流",
"管理",
"法学",
"教育学",
"外语",
"新闻",
"体育",
"艺术",
"设计",
]
def classify_major(major: str) -> str:
"""分类专业热度"""
major = major or ""
for hot in HOT_MAJORS:
if hot in major:
return "热门"
for cold in COLD_MAJORS:
if cold in major:
return "冷门"
for stable in STABLE_MAJORS:
if stable in major:
return "稳健"
return "其他"
def detect_city_type(school_name: str, province: str) -> str:
"""从院校名称推断所在城市类型。
规则收紧
1. 只有显式命中省会城市名才算省会
2. 不再把省名/自治区名命中直接判为省会如西藏大学/宁夏大学会误判
3. 直辖市北京/上海/天津/重庆仍按直辖市主城区处理
4. 含学院/职业/师范/理工/工业/农业/医科/财经/民族 等但不含省会名地级市其他
"""
capital = PROVINCE_CAPITALS.get(province, "")
# 直辖市
if province in {"北京", "上海", "天津", "重庆"} and capital in school_name:
return "省会"
# 只有显式命中省会城市名才算省会
if capital and capital in school_name:
return "省会"
# 国家/区域级院校大多位于省会或核心城市,但不强制判省会
if any(
kw in school_name
for kw in [
"中国",
"中央",
"华中",
"华东",
"华北",
"华南",
"西南",
"西北",
"东北",
]
):
return "省会"
# 含明显高校/职业院校关键词,但未命中省会城市,默认按地级市处理
if any(
kw in school_name
for kw in [
"学院",
"职业",
"师范",
"理工",
"工业",
"农业",
"医科",
"财经",
"民族",
"警官",
"科技",
"工程",
]
):
return "地级市"
return "其他"
def main() -> int:
# 地域统计
location_stats: dict[str, Counter] = defaultdict(Counter)
# 专业热度统计
major_stats: dict[str, Counter] = defaultdict(Counter)
for path in sorted((ROOT / "data/crowd_db").glob("*.json")):
d = json.loads(path.read_text(encoding="utf-8"))
province = d.get("province", "")
if not province:
continue
schools_seen = set()
for sr in d.get("score_ranges", []):
for rec in sr.get("recommendations", []):
# 地域统计(去重院校)
school = rec.get("name", "")
if school not in schools_seen:
schools_seen.add(school)
city_type = detect_city_type(school, province)
location_stats[province][city_type] += 1
# 专业热度统计
major = rec.get("major", "")
major_cat = classify_major(major)
major_stats[province][major_cat] += 1
issues = []
# 检查地域均衡性(非直辖市省份)
municipalities = {"北京", "上海", "天津", "重庆"}
for province, stats in location_stats.items():
if province in municipalities:
continue
total = sum(stats.values())
if total > 0:
capital_pct = stats["省会"] / total
if capital_pct > 0.7:
issues.append(
f"[地域均衡] {province} 省会院校占比过高: {capital_pct:.1%} (应 ≤70%)"
)
# 检查专业冷热均衡
for province, stats in major_stats.items():
total = sum(stats.values())
if total > 0:
hot_pct = stats["热门"] / total
cold_pct = stats["冷门"] / total
if hot_pct > 0.5:
issues.append(
f"[专业均衡] {province} 热门专业占比过高: {hot_pct:.1%} (应 ≤50%)"
)
if cold_pct < 0.05:
issues.append(
f"[专业均衡] {province} 冷门专业占比过低: {cold_pct:.1%} (应 ≥5%)"
)
# 输出
if issues:
print("❌ crowd_db 均衡性检查发现问题:")
for issue in issues:
print(f" - {issue}")
return 1
print("✅ crowd_db 均衡性检查通过")
return 0
if __name__ == "__main__":
raise SystemExit(main())