Files
gaokao-volunteer-system/data/crowd_db/loader.py
Hermes 552526a3f1 feat(crowd_db): T1.1 数据加载器 + 目录完善
- data/crowd_db/README.md 补全字段说明、文件命名、数据来源
- data/crowd_db/hunan.json 已有 T2.1 整理的 8 个分数段 58 条数据
- 新增 loader.py: CrowdDBLoader + CrowdRecommendation
  - 省份→文件名映射(湖南→hunan.json) + 多种命名兜底
  - load_province / find_recommendations / find_recommendation_by_school
  - risk_level 派生属性(frequency>=4=high)
- tests/test_loader.py 5 测试全通过

Plan: docs/plans/T1-1-crowd-db-setup.md DoD 达成
2026-06-12 10:29:53 +08:00

172 lines
4.9 KiB
Python
Raw 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.
"""
大厂AI推荐数据库加载器
用于反扎堆检测功能加载和查询大厂AI的高频推荐院校。
"""
import json
import os
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any
@dataclass
class CrowdRecommendation:
"""扎堆推荐数据"""
name: str # 院校名称
major: str # 专业
frequency: int # 推荐频次0-4
platforms: List[str] # 推荐平台列表
predicted_increase: int # 预测分数上涨
alternatives: List[Dict[str, Any]] = field(default_factory=list)
@property
def risk_level(self) -> str:
"""根据频次计算风险等级"""
if self.frequency >= 4:
return "high"
elif self.frequency >= 2:
return "medium"
else:
return "low"
class CrowdDBLoader:
"""
大厂AI推荐数据库加载器
数据存储在 data/crowd_db/{province}.json 文件中
"""
# 数据目录路径(相对项目根目录)
# __file__ = <root>/data/crowd_db/loader.py
# dirname x3 → project root, then join data/crowd_db
DATA_DIR = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
"data",
"crowd_db",
)
# 省份名 → JSON 文件名 映射(拼音风格)
PROVINCE_FILE_MAP = {
"湖南": "hunan",
"浙江": "zhejiang",
"湖北": "hubei",
"广东": "guangdong",
"北京": "beijing",
"上海": "shanghai",
"江苏": "jiangsu",
"四川": "sichuan",
"山东": "shandong",
"河南": "henan",
"全国": "national",
}
def __init__(self, data_dir: Optional[str] = None):
"""初始化加载器
Args:
data_dir: 数据目录路径,默认使用 DATA_DIR
"""
self.data_dir = data_dir or self.DATA_DIR
self._cache: Dict[str, dict] = {}
def load_province(self, province: str) -> Optional[dict]:
"""加载指定省份的推荐数据
Args:
province: 省份名称(如"湖南"
Returns:
省份数据字典,未找到返回 None
"""
if province in self._cache:
return self._cache[province]
# 解析文件名:先查映射,缺失时尝试 province.json / {province}.json 两种命名
candidates = []
slug = self.PROVINCE_FILE_MAP.get(province)
if slug:
candidates.append(f"{slug}.json")
candidates.append(f"{province}.json")
for filename in candidates:
file_path = os.path.join(self.data_dir, filename)
if not os.path.exists(file_path):
continue
try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
self._cache[province] = data
return data
except (json.JSONDecodeError, IOError):
continue
return None
def find_recommendations(self, province: str, score: int) -> List[Dict[str, Any]]:
"""查询指定分数段内的所有推荐
Args:
province: 省份名称
score: 用户分数
Returns:
推荐列表
"""
data = self.load_province(province)
if not data:
return []
results = []
for score_range in data.get("score_ranges", []):
min_score, max_score = score_range["range"]
if min_score <= score <= max_score:
results.extend(score_range.get("recommendations", []))
return results
def find_recommendation_by_school(
self, province: str, school_name: str
) -> Optional[Dict[str, Any]]:
"""按院校名查询推荐信息
Args:
province: 省份名称
school_name: 院校名称(支持模糊匹配)
Returns:
推荐信息,未找到返回 None
"""
data = self.load_province(province)
if not data:
return None
for score_range in data.get("score_ranges", []):
for rec in score_range.get("recommendations", []):
if school_name in rec["name"] or rec["name"] in school_name:
return rec
return None
# 命令行测试
if __name__ == "__main__":
loader = CrowdDBLoader()
# 测试加载湖南数据
data = loader.load_province("湖南")
if data:
print(f"✅ 加载湖南数据: {len(data.get('score_ranges', []))} 个分数段")
else:
print("❌ 加载湖南数据失败")
# 测试分数查询
recs = loader.find_recommendations("湖南", score=575)
print(f"📊 575分在湖南的扎堆院校: {len(recs)}")
for rec in recs:
print(
f" - {rec['name']} {rec['major']} (频次:{rec['frequency']}, +{rec['predicted_increase']}分)"
)