- 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 达成
172 lines
4.9 KiB
Python
172 lines
4.9 KiB
Python
"""
|
||
大厂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']}分)"
|
||
)
|