Files
report-admin/backend/app/engines/data_engine.py
T
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 71db82393a Initial commit
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-07-13 15:38:41 +08:00

256 lines
9.8 KiB
Python

"""
数据引擎:Excel解析 + 数据清洗 + 赋分计算
负责将原始Excel数据转换为每所学校的结构化得分数据
"""
import pandas as pd
import numpy as np
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import logging
from ..config import (
EXCEL_BASIC_INFO, EXCEL_COURSE_IMPL, EXCEL_SUBJECT_IMPL,
SCHOOL_NAME_MAP, SUBJECTS, SCHOOL_TYPE_MAP,
)
logger = logging.getLogger(__name__)
class DataEngine:
"""数据引擎:读取Excel、清洗、结构化"""
def __init__(self, data_dir: Optional[Path] = None):
self.data_dir = data_dir
self._basic_info: Optional[pd.DataFrame] = None
self._course_impl: Optional[pd.DataFrame] = None
self._subject_impl: Optional[pd.DataFrame] = None
def load_all(self) -> None:
"""加载所有Excel数据"""
logger.info("开始加载Excel数据...")
self._basic_info = self._load_basic_info()
self._course_impl = self._load_course_impl()
self._subject_impl = self._load_subject_impl()
logger.info(
f"数据加载完成: 基础信息={len(self._basic_info)}行, "
f"课程实施={len(self._course_impl)}行, "
f"学科课程={len(self._subject_impl)}行"
)
def _standardize_school_name(self, df: pd.DataFrame, col: str) -> pd.DataFrame:
"""标准化学校名称"""
df[col] = df[col].map(lambda x: SCHOOL_NAME_MAP.get(x, x))
return df
def _load_basic_info(self) -> pd.DataFrame:
"""加载基础信息表"""
df = pd.read_excel(EXCEL_BASIC_INFO)
df = self._standardize_school_name(df, "学校名称")
return df
def _load_course_impl(self) -> pd.DataFrame:
"""加载课程实施情况表"""
df = pd.read_excel(EXCEL_COURSE_IMPL)
df = self._standardize_school_name(df, "学校简称")
# 统一列名
df = df.rename(columns={"学校简称": "学校名称"})
return df
def _load_subject_impl(self) -> pd.DataFrame:
"""加载学科课程实施情况表"""
df = pd.read_excel(EXCEL_SUBJECT_IMPL)
df = self._standardize_school_name(df, "学校简称")
df = df.rename(columns={"学校简称": "学校名称"})
return df
@property
def schools(self) -> List[str]:
"""获取所有学校名称列表"""
if self._course_impl is None:
self.load_all()
return sorted(self._course_impl["学校名称"].unique().tolist())
def get_school_basic_info(self, school: str) -> Dict:
"""获取学校基本信息"""
if self._basic_info is None:
self.load_all()
df = self._basic_info[self._basic_info["学校名称"] == school]
info = SCHOOL_TYPE_MAP.get(school, {})
# 提取关键字段
def _get_field(field_name):
rows = df[df["字段名称"] == field_name]
if len(rows) > 0:
return rows.iloc[0]["字段值"]
return None
info.update({
"school_name": school,
"建校年份": _get_field("建校年份"),
"教师总数": _get_field("学校教师总数"),
"占地面积": _get_field("占地面积"),
"建筑面积": _get_field("建筑面积"),
})
return info
def get_school_course_data(self, school: str) -> pd.DataFrame:
"""获取某学校的课程实施情况数据"""
if self._course_impl is None:
self.load_all()
return self._course_impl[self._course_impl["学校名称"] == school].copy()
def get_school_subject_data(self, school: str, subject: Optional[str] = None) -> pd.DataFrame:
"""获取某学校的学科课程实施数据"""
if self._subject_impl is None:
self.load_all()
df = self._subject_impl[self._subject_impl["学校名称"] == school].copy()
if subject:
df = df[df["学科"] == subject]
return df
# ========== 课程领导力相关数据提取 ==========
def get_weekly_hours(self, school: str) -> pd.DataFrame:
"""获取学校各学科各年级各学期的周课时数据"""
df = self.get_school_course_data(school)
# 筛选周课时相关字段
hours_fields = ["学科必修课周课时", "学科选择性必修课周课时", "学科类选修课周课时"]
result = df[df["字段名称"].isin(hours_fields)].copy()
result["字段取值"] = pd.to_numeric(result["字段取值"], errors="coerce")
return result
def get_course_norms(self, school: str) -> Dict:
"""获取学校课程规范落实相关数据(建设规范、档案等)"""
df = self.get_school_course_data(school)
norm_keywords = ["建设规范文本", "档案", "已经建成并使用", "尚未建成"]
mask = df["字段名称"].apply(
lambda x: any(k in str(x) for k in norm_keywords) if pd.notna(x) else False
)
return df[mask][["字段名称", "字段取值"]].to_dict("records")
# ========== 教学变革力相关数据提取 ==========
def get_teaching_reform_data(self, school: str) -> Dict:
"""获取教学方式变革相关数据(认识程度、落实程度、实施方式)"""
df = self.get_school_subject_data(school)
reform_keywords = [
"认识程度", "落实程度", "认识", "落实",
"理解式学习", "自主性学习", "实践性学习", "跨学科学习",
"信息技术与教学融合", "信息融入教学",
]
mask = df["字段名称"].apply(
lambda x: any(k in str(x) for k in reform_keywords) if pd.notna(x) else False
)
return df[mask]
def get_homework_data(self, school: str) -> Dict:
"""获取作业设计与管理数据"""
df = self.get_school_subject_data(school)
hw_keywords = [
"作业", "实践类", "表现类", "跨学科", "团队合作",
"批改", "评价", "属性标注", "时长控制",
]
mask = df["字段名称"].apply(
lambda x: any(k in str(x) for k in hw_keywords) if pd.notna(x) else False
)
return df[mask]
# ========== 通用数据提取方法 ==========
def get_field_value(self, school: str, source: str, field_name: str,
subject: Optional[str] = None) -> Optional[str]:
"""通用字段值获取"""
if source == "basic":
df = self._basic_info[self._basic_info["学校名称"] == school]
col = "字段名称"
val_col = "字段值"
elif source == "course":
df = self.get_school_course_data(school)
col = "字段名称"
val_col = "字段取值"
elif source == "subject":
df = self.get_school_subject_data(school, subject)
col = "字段名称"
val_col = "字段取值"
else:
return None
rows = df[df[col] == field_name]
if len(rows) > 0:
return rows.iloc[0][val_col]
return None
def get_field_by_id(self, school: str, source: str, field_id: str,
subject: Optional[str] = None) -> List[Dict]:
"""通过字段ID获取数据"""
if source == "basic":
df = self._basic_info[self._basic_info["学校名称"] == school]
elif source == "course":
df = self.get_school_course_data(school)
elif source == "subject":
df = self.get_school_subject_data(school, subject)
else:
return []
rows = df[df["字段ID"] == field_id]
return rows.to_dict("records")
def build_score_matrix(self) -> pd.DataFrame:
"""
构建所有学校×所有字段的得分矩阵
这是赋分引擎的输入
"""
if self._course_impl is None:
self.load_all()
records = []
for school in self.schools:
# 课程实施表数据
course_df = self.get_school_course_data(school)
for _, row in course_df.iterrows():
records.append({
"学校": school,
"来源": "course",
"题号": row.get("题号"),
"字段ID": row.get("字段ID"),
"字段名称": row.get("字段名称"),
"字段取值": row.get("字段取值"),
"学科": row.get("学科", "不分学科"),
"年级": row.get("年级", "不分年级"),
"学期": row.get("学期", ""),
})
# 学科课程表数据
subject_df = self.get_school_subject_data(school)
for _, row in subject_df.iterrows():
records.append({
"学校": school,
"来源": "subject",
"题号": row.get("题号"),
"字段ID": row.get("字段ID"),
"字段名称": row.get("字段名称"),
"字段取值": row.get("字段取值"),
"学科": row.get("学科", "不分学科"),
"年级": "",
"学期": "",
})
matrix = pd.DataFrame(records)
logger.info(f"得分矩阵构建完成: {len(matrix)}行, {len(self.schools)}所学校")
return matrix
def summary(self) -> Dict:
"""数据摘要"""
if self._basic_info is None:
self.load_all()
return {
"schools": self.schools,
"school_count": len(self.schools),
"basic_info_rows": len(self._basic_info),
"course_impl_rows": len(self._course_impl),
"subject_impl_rows": len(self._subject_impl),
"subjects": SUBJECTS,
"school_types": {s: SCHOOL_TYPE_MAP[s]["type"] for s in self.schools},
}