Initial commit
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
commit
71db82393a
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
"""
|
||||
数据引擎: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},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,777 @@
|
||||
"""
|
||||
报告渲染引擎:将数据+LLM文字+模板组装成最终HTML报告
|
||||
"""
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
import numpy as np
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from ..config import (
|
||||
TEMPLATES_DIR, DIMENSION_FRAMEWORK, LEVEL_DESCRIPTIONS,
|
||||
OUTPUT_DIR, SCHOOL_TYPE_MAP,
|
||||
)
|
||||
|
||||
|
||||
class ReportRenderer:
|
||||
"""HTML报告渲染引擎"""
|
||||
|
||||
def __init__(self):
|
||||
self.env = Environment(
|
||||
loader=FileSystemLoader(str(TEMPLATES_DIR)),
|
||||
autoescape=False, # 允许HTML直接渲染
|
||||
)
|
||||
|
||||
def render(self, report_data: Dict, llm_sections: Dict[str, str]) -> str:
|
||||
"""
|
||||
渲染完整HTML报告
|
||||
|
||||
report_data: stats_engine.compute_school_report_data() 的输出
|
||||
llm_sections: llm_engine.generate_report_segments() 的输出
|
||||
"""
|
||||
template = self.env.get_template("base.html")
|
||||
|
||||
# 准备模板数据
|
||||
school = report_data["school"]
|
||||
context = {
|
||||
"school": school,
|
||||
"school_info": report_data["school_info"],
|
||||
"overall": self._to_namespace(report_data["overall"]),
|
||||
"dimensions": {
|
||||
name: self._to_namespace(data)
|
||||
for name, data in report_data["dimensions"].items()
|
||||
},
|
||||
"sub_dimensions": {
|
||||
name: self._to_namespace(data)
|
||||
for name, data in report_data["sub_dimensions"].items()
|
||||
},
|
||||
"framework": {
|
||||
name: self._to_namespace(info)
|
||||
for name, info in DIMENSION_FRAMEWORK.items()
|
||||
},
|
||||
"llm_sections": llm_sections,
|
||||
"level_descriptions": LEVEL_DESCRIPTIONS,
|
||||
"generation_date": datetime.now().strftime("%Y年%m月%d日"),
|
||||
|
||||
# ECharts数据 — 传原始dict,由模板的tojson过滤器序列化一次
|
||||
"radar_data": self._build_radar_data(report_data),
|
||||
"sub_dim_chart_data": self._build_sub_dim_charts(report_data),
|
||||
"score_compare_data": self._build_score_compare(report_data),
|
||||
"cluster_radar_data": self._build_cluster_radar(report_data),
|
||||
"correlation_data": self._build_correlation_heatmap(report_data),
|
||||
"level_dist_data": self._build_level_distribution(report_data),
|
||||
"school_ranking_data": self._build_school_ranking(report_data),
|
||||
# 新增图表数据
|
||||
"cluster_type_dist_data": self._build_cluster_type_distribution(report_data),
|
||||
"cluster_line_compare_data": self._build_cluster_line_compare(report_data),
|
||||
"dim_scatter_data": self._build_dim_scatter_charts(report_data),
|
||||
"dim_score_bar_data": self._build_dim_score_bars(report_data),
|
||||
"dim_sub_radar_data": self._build_dim_sub_radar_charts(report_data),
|
||||
# 创新图表
|
||||
"profile_card_data": self._build_profile_card(report_data),
|
||||
"quadrant_data": self._build_quadrant_chart(report_data),
|
||||
"thermometer_data": self._build_thermometer_data(report_data),
|
||||
"waterfall_data": self._build_waterfall_chart(report_data),
|
||||
}
|
||||
|
||||
return template.render(**context)
|
||||
|
||||
def render_to_file(self, report_data: Dict, llm_sections: Dict[str, str],
|
||||
output_path: Path = None) -> Path:
|
||||
"""渲染并保存到文件"""
|
||||
html = self.render(report_data, llm_sections)
|
||||
school = report_data["school"]
|
||||
|
||||
if output_path is None:
|
||||
output_dir = OUTPUT_DIR
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = output_dir / f"{school}_报告.html"
|
||||
|
||||
output_path.write_text(html, encoding="utf-8")
|
||||
return output_path
|
||||
|
||||
def _build_radar_data(self, report_data: Dict) -> Dict:
|
||||
"""构建雷达图数据"""
|
||||
school = report_data["school"]
|
||||
dims = list(report_data["dimensions"].keys())
|
||||
school_values = [report_data["dimensions"][d]["score"] for d in dims]
|
||||
avg_values = [report_data["dimensions"][d]["district_avg"] for d in dims]
|
||||
|
||||
return {
|
||||
"dimensions": dims,
|
||||
"legend": [school, "区均值"],
|
||||
"series": [
|
||||
{"name": school, "values": school_values},
|
||||
{"name": "区均值", "values": avg_values},
|
||||
],
|
||||
}
|
||||
|
||||
def _build_sub_dim_charts(self, report_data: Dict) -> Dict:
|
||||
"""构建各维度的子维度柱状图数据"""
|
||||
charts = {}
|
||||
part_names = {
|
||||
"课程领导力": "part3", "教学变革力": "part4", "学生发展指导力": "part5",
|
||||
"教师发展支持力": "part6", "教育质量评估力": "part7",
|
||||
"教育条件保障力": "part8", "数字化赋能力": "part9",
|
||||
}
|
||||
|
||||
for dim_name, info in DIMENSION_FRAMEWORK.items():
|
||||
part_id = part_names[dim_name]
|
||||
sub_dims = info["sub_dimensions"]
|
||||
|
||||
categories = []
|
||||
school_values = []
|
||||
avg_values = []
|
||||
|
||||
for sd in sub_dims:
|
||||
sd_data = report_data["sub_dimensions"].get(sd, {})
|
||||
if sd_data:
|
||||
categories.append(sd)
|
||||
school_values.append(round(sd_data["score"], 2))
|
||||
avg_values.append(round(sd_data["district_avg"], 2))
|
||||
|
||||
charts[part_id] = {
|
||||
"categories": categories,
|
||||
"school_values": school_values,
|
||||
"avg_values": avg_values,
|
||||
}
|
||||
|
||||
return charts
|
||||
|
||||
def _build_score_compare(self, report_data: Dict) -> Dict:
|
||||
"""构建得分对比横向条形图数据:本校 vs 区均值 vs 同类学校均值"""
|
||||
school = report_data["school"]
|
||||
dims = list(report_data["dimensions"].keys())
|
||||
return {
|
||||
"categories": dims,
|
||||
"school_values": [round(report_data["dimensions"][d]["score"], 2) for d in dims],
|
||||
"district_avg": [round(report_data["dimensions"][d]["district_avg"], 2) for d in dims],
|
||||
"same_type_avg": [round(report_data["dimensions"][d]["same_type_avg"], 2) for d in dims],
|
||||
"school_name": school,
|
||||
}
|
||||
|
||||
def _build_cluster_radar(self, report_data: Dict) -> Dict:
|
||||
"""构建聚类类型特征对比雷达图(较好类 vs 待提升类)"""
|
||||
all_dim_scores = report_data.get("all_schools_dim_scores", {})
|
||||
if not all_dim_scores:
|
||||
return {}
|
||||
|
||||
# 从 overall cluster info 中提取各学校的聚类标签
|
||||
school = report_data["school"]
|
||||
dims = list(report_data["dimensions"].keys())
|
||||
|
||||
# 计算各学校的总体得分来判断聚类
|
||||
school_totals = {}
|
||||
for s in list(list(all_dim_scores.values())[0].keys()):
|
||||
total = 0
|
||||
for d in dims:
|
||||
total += all_dim_scores.get(d, {}).get(s, 50)
|
||||
school_totals[s] = total / len(dims)
|
||||
|
||||
# 二分聚类(简单按总分中位数分)
|
||||
median_score = sorted(school_totals.values())[len(school_totals) // 2]
|
||||
good_schools = [s for s, v in school_totals.items() if v >= median_score]
|
||||
weak_schools = [s for s, v in school_totals.items() if v < median_score]
|
||||
|
||||
good_avgs = []
|
||||
weak_avgs = []
|
||||
for d in dims:
|
||||
dim_data = all_dim_scores.get(d, {})
|
||||
good_avgs.append(round(sum(dim_data.get(s, 50) for s in good_schools) / max(len(good_schools), 1), 2))
|
||||
weak_avgs.append(round(sum(dim_data.get(s, 50) for s in weak_schools) / max(len(weak_schools), 1), 2))
|
||||
|
||||
return {
|
||||
"dimensions": dims,
|
||||
"legend": ["课程实施较好类", "课程实施待提升类", school],
|
||||
"series": [
|
||||
{"name": "课程实施较好类", "values": good_avgs},
|
||||
{"name": "课程实施待提升类", "values": weak_avgs},
|
||||
{"name": school, "values": [round(report_data["dimensions"][d]["score"], 2) for d in dims]},
|
||||
],
|
||||
"good_count": len(good_schools),
|
||||
"weak_count": len(weak_schools),
|
||||
}
|
||||
|
||||
def _build_correlation_heatmap(self, report_data: Dict) -> Dict:
|
||||
"""构建维度间相关性热力图"""
|
||||
correlation = report_data.get("correlation", {})
|
||||
if not correlation:
|
||||
return {}
|
||||
|
||||
dims = list(correlation.keys())
|
||||
# 构建二维数组 [x_index, y_index, value]
|
||||
data = []
|
||||
for i, d1 in enumerate(dims):
|
||||
for j, d2 in enumerate(dims):
|
||||
val = correlation.get(d1, {}).get(d2, 0)
|
||||
data.append([i, j, round(val, 3) if val is not None else 0])
|
||||
|
||||
# 短名
|
||||
short_names = [d.replace("力", "").replace("教育", "") for d in dims]
|
||||
|
||||
return {
|
||||
"dimensions": dims,
|
||||
"short_names": short_names,
|
||||
"data": data,
|
||||
}
|
||||
|
||||
def _build_level_distribution(self, report_data: Dict) -> Dict:
|
||||
"""构建各三级维度水平分布堆叠条形图"""
|
||||
charts = {}
|
||||
part_names = {
|
||||
"课程领导力": "part3", "教学变革力": "part4", "学生发展指导力": "part5",
|
||||
"教师发展支持力": "part6", "教育质量评估力": "part7",
|
||||
"教育条件保障力": "part8", "数字化赋能力": "part9",
|
||||
}
|
||||
|
||||
for dim_name, info in DIMENSION_FRAMEWORK.items():
|
||||
part_id = part_names[dim_name]
|
||||
sub_dims = info["sub_dimensions"]
|
||||
|
||||
categories = []
|
||||
level1_pcts = []
|
||||
level2_pcts = []
|
||||
level3_pcts = []
|
||||
level4_pcts = []
|
||||
school_levels = []
|
||||
|
||||
for sd in sub_dims:
|
||||
sd_data = report_data["sub_dimensions"].get(sd, {})
|
||||
if not sd_data:
|
||||
continue
|
||||
dist = sd_data.get("level_distribution", {})
|
||||
total = sum(dist.values())
|
||||
if total == 0:
|
||||
continue
|
||||
|
||||
categories.append(sd)
|
||||
level1_pcts.append(round(dist.get("水平1", 0) / total * 100, 1))
|
||||
level2_pcts.append(round(dist.get("水平2", 0) / total * 100, 1))
|
||||
level3_pcts.append(round(dist.get("水平3", 0) / total * 100, 1))
|
||||
level4_pcts.append(round(dist.get("水平4", 0) / total * 100, 1))
|
||||
school_levels.append(sd_data.get("level", 0))
|
||||
|
||||
charts[part_id] = {
|
||||
"categories": categories,
|
||||
"level1": level1_pcts,
|
||||
"level2": level2_pcts,
|
||||
"level3": level3_pcts,
|
||||
"level4": level4_pcts,
|
||||
"school_levels": school_levels,
|
||||
}
|
||||
|
||||
return charts
|
||||
|
||||
def _build_school_ranking(self, report_data: Dict) -> Dict:
|
||||
"""构建区内各校维度排名对比图"""
|
||||
school = report_data["school"]
|
||||
all_dim_scores = report_data.get("all_schools_dim_scores", {})
|
||||
dims = list(report_data["dimensions"].keys())
|
||||
|
||||
if not all_dim_scores:
|
||||
return {}
|
||||
|
||||
# 获取所有学校名
|
||||
first_dim = list(all_dim_scores.values())[0] if all_dim_scores else {}
|
||||
all_schools = list(first_dim.keys())
|
||||
|
||||
# 计算每校总体得分并排序
|
||||
school_totals = {}
|
||||
for s in all_schools:
|
||||
total = sum(all_dim_scores.get(d, {}).get(s, 50) for d in dims)
|
||||
school_totals[s] = round(total / len(dims), 2)
|
||||
|
||||
sorted_schools = sorted(school_totals.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
return {
|
||||
"schools": [s[0] for s in sorted_schools],
|
||||
"total_scores": [s[1] for s in sorted_schools],
|
||||
"current_school": school,
|
||||
"dimensions": dims,
|
||||
"dim_scores": {
|
||||
d: [round(all_dim_scores.get(d, {}).get(s[0], 50), 2) for s in sorted_schools]
|
||||
for d in dims
|
||||
},
|
||||
}
|
||||
|
||||
def _build_cluster_type_distribution(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
构建课程实施类型分布饼/条形图数据(如参考报告图2-2)
|
||||
展示 较好类 vs 待提升类 在区内各校的分布
|
||||
"""
|
||||
all_dim_scores = report_data.get("all_schools_dim_scores", {})
|
||||
if not all_dim_scores:
|
||||
return {}
|
||||
|
||||
school = report_data["school"]
|
||||
dims = list(report_data["dimensions"].keys())
|
||||
|
||||
# 计算各学校总体得分并二分聚类
|
||||
school_totals = {}
|
||||
first_dim_data = list(all_dim_scores.values())[0] if all_dim_scores else {}
|
||||
all_schools = list(first_dim_data.keys())
|
||||
|
||||
for s in all_schools:
|
||||
total = sum(all_dim_scores.get(d, {}).get(s, 50) for d in dims)
|
||||
school_totals[s] = total / len(dims)
|
||||
|
||||
median_score = sorted(school_totals.values())[len(school_totals) // 2]
|
||||
good_schools = [s for s, v in school_totals.items() if v >= median_score]
|
||||
weak_schools = [s for s, v in school_totals.items() if v < median_score]
|
||||
|
||||
school_cluster = "较好" if school in good_schools else "待提升"
|
||||
|
||||
return {
|
||||
"good_count": len(good_schools),
|
||||
"weak_count": len(weak_schools),
|
||||
"total": len(all_schools),
|
||||
"school_cluster": school_cluster,
|
||||
"school_name": school,
|
||||
"good_schools": good_schools,
|
||||
"weak_schools": weak_schools,
|
||||
}
|
||||
|
||||
def _build_cluster_line_compare(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
构建两类学校特征折线对比图(如参考报告图2-3)
|
||||
两条折线:较好类 vs 待提升类在7个维度上的得分
|
||||
"""
|
||||
all_dim_scores = report_data.get("all_schools_dim_scores", {})
|
||||
if not all_dim_scores:
|
||||
return {}
|
||||
|
||||
school = report_data["school"]
|
||||
dims = list(report_data["dimensions"].keys())
|
||||
|
||||
# 计算聚类
|
||||
first_dim_data = list(all_dim_scores.values())[0] if all_dim_scores else {}
|
||||
all_schools = list(first_dim_data.keys())
|
||||
|
||||
school_totals = {}
|
||||
for s in all_schools:
|
||||
total = sum(all_dim_scores.get(d, {}).get(s, 50) for d in dims)
|
||||
school_totals[s] = total / len(dims)
|
||||
|
||||
median_score = sorted(school_totals.values())[len(school_totals) // 2]
|
||||
good_schools = [s for s, v in school_totals.items() if v >= median_score]
|
||||
weak_schools = [s for s, v in school_totals.items() if v < median_score]
|
||||
|
||||
good_avgs = []
|
||||
weak_avgs = []
|
||||
for d in dims:
|
||||
dim_data = all_dim_scores.get(d, {})
|
||||
good_avgs.append(round(sum(dim_data.get(s, 50) for s in good_schools) / max(len(good_schools), 1), 2))
|
||||
weak_avgs.append(round(sum(dim_data.get(s, 50) for s in weak_schools) / max(len(weak_schools), 1), 2))
|
||||
|
||||
return {
|
||||
"dimensions": dims,
|
||||
"good_values": good_avgs,
|
||||
"weak_values": weak_avgs,
|
||||
"good_count": len(good_schools),
|
||||
"weak_count": len(weak_schools),
|
||||
}
|
||||
|
||||
def _build_dim_scatter_charts(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
构建各二级维度的聚类散点图数据(2D / 3D)
|
||||
参考报告中每个维度都有一个散点图显示所有学校的聚类分布
|
||||
对于有2个子维度的 → 2D散点图
|
||||
对于有3个子维度的 → 3D散点图
|
||||
对于有4个子维度的 → 取前2个主成分的2D散点图
|
||||
"""
|
||||
all_sub_scores = report_data.get("all_schools_sub_scores", {})
|
||||
if not all_sub_scores:
|
||||
return {}
|
||||
|
||||
school = report_data["school"]
|
||||
charts = {}
|
||||
part_names = {
|
||||
"课程领导力": "part3", "教学变革力": "part4", "学生发展指导力": "part5",
|
||||
"教师发展支持力": "part6", "教育质量评估力": "part7",
|
||||
"教育条件保障力": "part8", "数字化赋能力": "part9",
|
||||
}
|
||||
|
||||
first_sub = list(all_sub_scores.values())[0] if all_sub_scores else {}
|
||||
all_schools = list(first_sub.keys())
|
||||
|
||||
for dim_name, info in DIMENSION_FRAMEWORK.items():
|
||||
part_id = part_names[dim_name]
|
||||
sub_dims = info["sub_dimensions"]
|
||||
n_subs = len(sub_dims)
|
||||
|
||||
# 获取各学校在这些子维度上的得分
|
||||
school_scores = {}
|
||||
for s in all_schools:
|
||||
scores = []
|
||||
for sd in sub_dims:
|
||||
val = all_sub_scores.get(sd, {}).get(s, 50)
|
||||
scores.append(round(float(val), 2))
|
||||
school_scores[s] = scores
|
||||
|
||||
# 简单二分聚类
|
||||
totals = {s: sum(v) / len(v) for s, v in school_scores.items()}
|
||||
med = sorted(totals.values())[len(totals) // 2]
|
||||
clusters = {s: 0 if totals[s] >= med else 1 for s in all_schools}
|
||||
|
||||
if n_subs == 2:
|
||||
# 2D散点图
|
||||
data_good = []
|
||||
data_weak = []
|
||||
school_point = None
|
||||
|
||||
for s in all_schools:
|
||||
point = school_scores[s]
|
||||
if s == school:
|
||||
school_point = point
|
||||
elif clusters[s] == 0:
|
||||
data_good.append(point)
|
||||
else:
|
||||
data_weak.append(point)
|
||||
|
||||
charts[part_id] = {
|
||||
"type": "2d",
|
||||
"axes": sub_dims,
|
||||
"good_data": data_good,
|
||||
"weak_data": data_weak,
|
||||
"school_point": school_point,
|
||||
"school_name": school,
|
||||
"dim_name": dim_name,
|
||||
}
|
||||
|
||||
elif n_subs == 3:
|
||||
# 3D散点图
|
||||
data_good = []
|
||||
data_weak = []
|
||||
school_point = None
|
||||
|
||||
for s in all_schools:
|
||||
point = school_scores[s]
|
||||
if s == school:
|
||||
school_point = point
|
||||
elif clusters[s] == 0:
|
||||
data_good.append(point)
|
||||
else:
|
||||
data_weak.append(point)
|
||||
|
||||
charts[part_id] = {
|
||||
"type": "3d",
|
||||
"axes": sub_dims,
|
||||
"good_data": data_good,
|
||||
"weak_data": data_weak,
|
||||
"school_point": school_point,
|
||||
"school_name": school,
|
||||
"dim_name": dim_name,
|
||||
}
|
||||
|
||||
elif n_subs >= 4:
|
||||
# 取前两个子维度做2D散点
|
||||
axes = sub_dims[:2]
|
||||
data_good = []
|
||||
data_weak = []
|
||||
school_point = None
|
||||
|
||||
for s in all_schools:
|
||||
point = school_scores[s][:2]
|
||||
if s == school:
|
||||
school_point = point
|
||||
elif clusters[s] == 0:
|
||||
data_good.append(point)
|
||||
else:
|
||||
data_weak.append(point)
|
||||
|
||||
charts[part_id] = {
|
||||
"type": "2d",
|
||||
"axes": axes,
|
||||
"good_data": data_good,
|
||||
"weak_data": data_weak,
|
||||
"school_point": school_point,
|
||||
"school_name": school,
|
||||
"dim_name": dim_name,
|
||||
}
|
||||
|
||||
return charts
|
||||
|
||||
def _build_dim_score_bars(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
构建各维度独立得分柱状图数据(如参考报告图3-1、图4-1等)
|
||||
展示本校 vs 区均值 vs 同类学校均值 的对比
|
||||
"""
|
||||
school = report_data["school"]
|
||||
charts = {}
|
||||
part_names = {
|
||||
"课程领导力": "part3", "教学变革力": "part4", "学生发展指导力": "part5",
|
||||
"教师发展支持力": "part6", "教育质量评估力": "part7",
|
||||
"教育条件保障力": "part8", "数字化赋能力": "part9",
|
||||
}
|
||||
|
||||
for dim_name, dim_data in report_data["dimensions"].items():
|
||||
part_id = part_names.get(dim_name, "")
|
||||
if not part_id:
|
||||
continue
|
||||
|
||||
charts[part_id] = {
|
||||
"dim_name": dim_name,
|
||||
"school_name": school,
|
||||
"school_score": round(dim_data["score"], 2),
|
||||
"district_avg": round(dim_data["district_avg"], 2),
|
||||
"same_type_avg": round(dim_data["same_type_avg"], 2),
|
||||
}
|
||||
|
||||
return charts
|
||||
|
||||
def _build_dim_sub_radar_charts(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
构建各二级维度的子维度雷达图(如参考报告图3-2)
|
||||
多条线对比: 本校 vs 区均值 vs 同类学校均值
|
||||
"""
|
||||
school = report_data["school"]
|
||||
all_sub_scores = report_data.get("all_schools_sub_scores", {})
|
||||
charts = {}
|
||||
part_names = {
|
||||
"课程领导力": "part3", "教学变革力": "part4", "学生发展指导力": "part5",
|
||||
"教师发展支持力": "part6", "教育质量评估力": "part7",
|
||||
"教育条件保障力": "part8", "数字化赋能力": "part9",
|
||||
}
|
||||
|
||||
# Get same-type schools
|
||||
school_info = report_data.get("school_info", {})
|
||||
school_type = school_info.get("type", "")
|
||||
all_schools = list(list(all_sub_scores.values())[0].keys()) if all_sub_scores else []
|
||||
same_type_schools = [s for s in all_schools if SCHOOL_TYPE_MAP.get(s, {}).get("type") == school_type]
|
||||
|
||||
for dim_name, info in DIMENSION_FRAMEWORK.items():
|
||||
part_id = part_names.get(dim_name, "")
|
||||
if not part_id:
|
||||
continue
|
||||
|
||||
sub_dims = info["sub_dimensions"]
|
||||
school_values = []
|
||||
district_avg = []
|
||||
same_type_avg = []
|
||||
|
||||
for sd in sub_dims:
|
||||
sd_data = report_data["sub_dimensions"].get(sd, {})
|
||||
if sd_data:
|
||||
school_values.append(round(sd_data["score"], 2))
|
||||
district_avg.append(round(sd_data["district_avg"], 2))
|
||||
# 计算同类学校均值
|
||||
if same_type_schools and all_sub_scores:
|
||||
st_vals = [all_sub_scores.get(sd, {}).get(s, 50) for s in same_type_schools]
|
||||
same_type_avg.append(round(sum(float(v) for v in st_vals) / len(st_vals), 2))
|
||||
else:
|
||||
same_type_avg.append(district_avg[-1])
|
||||
|
||||
if len(sub_dims) >= 3:
|
||||
charts[part_id] = {
|
||||
"type": "radar",
|
||||
"sub_dims": sub_dims,
|
||||
"school_name": school,
|
||||
"school_values": school_values,
|
||||
"district_avg": district_avg,
|
||||
"same_type_avg": same_type_avg,
|
||||
}
|
||||
else:
|
||||
charts[part_id] = {
|
||||
"type": "bar",
|
||||
"sub_dims": sub_dims,
|
||||
"school_name": school,
|
||||
"school_values": school_values,
|
||||
"district_avg": district_avg,
|
||||
"same_type_avg": same_type_avg,
|
||||
}
|
||||
|
||||
return charts
|
||||
|
||||
# ========== 创新图表数据构建 ==========
|
||||
|
||||
def _build_profile_card(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
A. 学校画像卡(综合仪表盘)
|
||||
- 总体得分环形仪表盘
|
||||
- 7个维度的红绿灯状态(基于各维度下子维度的最低水平)
|
||||
- 20个三级维度的水平分布概览
|
||||
"""
|
||||
school = report_data["school"]
|
||||
overall = report_data["overall"]
|
||||
|
||||
# 维度红绿灯:每个二级维度取其子维度的最低水平作为"短板"指示
|
||||
dim_signals = []
|
||||
for dim_name, dim_data in report_data["dimensions"].items():
|
||||
sub_dims = DIMENSION_FRAMEWORK[dim_name]["sub_dimensions"]
|
||||
levels = []
|
||||
for sd in sub_dims:
|
||||
sd_data = report_data["sub_dimensions"].get(sd, {})
|
||||
if sd_data:
|
||||
levels.append(sd_data.get("level", 0))
|
||||
min_level = min(levels) if levels else 0
|
||||
avg_level = round(sum(levels) / len(levels), 1) if levels else 0
|
||||
dim_signals.append({
|
||||
"name": dim_name,
|
||||
"score": round(dim_data["score"], 2),
|
||||
"min_level": min_level,
|
||||
"avg_level": avg_level,
|
||||
"rank": dim_data.get("rank_in_district", 0),
|
||||
})
|
||||
|
||||
# 20个三级维度的水平分布统计
|
||||
level_counts = {1: 0, 2: 0, 3: 0, 4: 0}
|
||||
for sd_name, sd_data in report_data["sub_dimensions"].items():
|
||||
lv = sd_data.get("level", 0)
|
||||
if lv in level_counts:
|
||||
level_counts[lv] += 1
|
||||
|
||||
return {
|
||||
"school_name": school,
|
||||
"school_type": report_data["school_info"].get("type", ""),
|
||||
"total_score": overall["score"],
|
||||
"district_avg": overall["district_avg"],
|
||||
"rank": overall["rank_in_district"],
|
||||
"total_schools": overall["total_schools"],
|
||||
"cluster": overall.get("cluster", ""),
|
||||
"dim_signals": dim_signals,
|
||||
"level_counts": level_counts,
|
||||
"total_sub_dims": sum(level_counts.values()),
|
||||
}
|
||||
|
||||
def _build_quadrant_chart(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
B. 优势-短板象限图(Gap Analysis)
|
||||
X轴 = 得分, Y轴 = 与区均值的差值
|
||||
四象限:右上=核心优势, 左下=急需改进, 右下=隐性风险, 左上=潜力项
|
||||
"""
|
||||
school = report_data["school"]
|
||||
items = []
|
||||
|
||||
for sd_name, sd_data in report_data["sub_dimensions"].items():
|
||||
parent_dim = sd_data.get("parent_dimension", "")
|
||||
items.append({
|
||||
"name": sd_name,
|
||||
"parent": parent_dim,
|
||||
"score": round(sd_data["score"], 2),
|
||||
"diff": round(sd_data["diff_district"], 2),
|
||||
"level": sd_data.get("level", 0),
|
||||
})
|
||||
|
||||
return {
|
||||
"school_name": school,
|
||||
"items": items,
|
||||
"x_center": 50, # 区均值(标准化后均值=50)
|
||||
"y_center": 0, # 差值=0 的参照线
|
||||
}
|
||||
|
||||
def _build_thermometer_data(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
C. 维度温度计条形图数据
|
||||
为每个三级维度构建横向温度计数据:
|
||||
- 得分范围20-80
|
||||
- 水平分界线
|
||||
- 本校位置、区均值位置、同类学校均值位置
|
||||
"""
|
||||
school = report_data["school"]
|
||||
school_type = report_data["school_info"].get("type", "")
|
||||
all_sub_scores = report_data.get("all_schools_sub_scores", {})
|
||||
same_type_schools = [s for s in SCHOOL_TYPE_MAP
|
||||
if SCHOOL_TYPE_MAP[s].get("type") == school_type]
|
||||
|
||||
thermometers = {}
|
||||
for sd_name, sd_data in report_data["sub_dimensions"].items():
|
||||
# 同类学校均值
|
||||
if same_type_schools and sd_name in all_sub_scores:
|
||||
st_vals = [float(all_sub_scores[sd_name].get(s, 50)) for s in same_type_schools]
|
||||
same_type_avg = round(sum(st_vals) / len(st_vals), 2)
|
||||
else:
|
||||
same_type_avg = round(sd_data["district_avg"], 2)
|
||||
|
||||
# 水平阈值
|
||||
from ..config import LEVEL_THRESHOLDS
|
||||
thresholds = LEVEL_THRESHOLDS.get(sd_name, {})
|
||||
|
||||
thermometers[sd_name] = {
|
||||
"score": round(sd_data["score"], 2),
|
||||
"district_avg": round(sd_data["district_avg"], 2),
|
||||
"same_type_avg": same_type_avg,
|
||||
"level": sd_data.get("level", 0),
|
||||
"thresholds": {
|
||||
"level4": thresholds.get("level4", 57),
|
||||
"level3": thresholds.get("level3", 50),
|
||||
"level2": thresholds.get("level2", 43),
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
"school_name": school,
|
||||
"data": thermometers,
|
||||
}
|
||||
|
||||
def _build_waterfall_chart(self, report_data: Dict) -> Dict:
|
||||
"""
|
||||
D. 进步空间瀑布图
|
||||
展示:如果每个低于水平3的子维度提升到水平3的阈值,总分增加多少
|
||||
让校长看到"改哪几个点收益最大"
|
||||
"""
|
||||
from ..config import LEVEL_THRESHOLDS
|
||||
|
||||
school = report_data["school"]
|
||||
current_total = report_data["overall"]["score"]
|
||||
|
||||
# 找出所有低于水平3的子维度
|
||||
improvement_items = []
|
||||
for sd_name, sd_data in report_data["sub_dimensions"].items():
|
||||
level = sd_data.get("level", 0)
|
||||
if level < 3:
|
||||
current_score = sd_data["score"]
|
||||
# 提升到水平3的阈值
|
||||
target_score = LEVEL_THRESHOLDS.get(sd_name, {}).get("level3", 50)
|
||||
gap = round(target_score - current_score, 2)
|
||||
if gap > 0:
|
||||
improvement_items.append({
|
||||
"name": sd_name,
|
||||
"parent": sd_data.get("parent_dimension", ""),
|
||||
"current_score": round(current_score, 2),
|
||||
"target_score": round(target_score, 2),
|
||||
"gap": gap,
|
||||
"current_level": level,
|
||||
})
|
||||
|
||||
# 按收益从大到小排序
|
||||
improvement_items.sort(key=lambda x: x["gap"], reverse=True)
|
||||
|
||||
# 估算总分提升(简化:假设20个子维度等权重影响总分)
|
||||
total_sub_dims = len(report_data["sub_dimensions"])
|
||||
cumulative = current_total
|
||||
waterfall_steps = [{"name": "当前总分", "value": round(current_total, 2), "type": "current"}]
|
||||
|
||||
for item in improvement_items:
|
||||
# 粗略估算:子维度提升gap分 → 总分提升 gap / total_sub_dims * 权重
|
||||
# 实际PCA权重不同,此处用等权近似
|
||||
estimated_gain = round(item["gap"] / total_sub_dims, 2)
|
||||
cumulative += estimated_gain
|
||||
waterfall_steps.append({
|
||||
"name": item["name"],
|
||||
"value": round(estimated_gain, 2),
|
||||
"type": "gain",
|
||||
"detail": f"从水平{item['current_level']}→水平3 (+{item['gap']}分)",
|
||||
})
|
||||
|
||||
waterfall_steps.append({"name": "潜在总分", "value": round(cumulative, 2), "type": "potential"})
|
||||
|
||||
return {
|
||||
"school_name": school,
|
||||
"current_total": round(current_total, 2),
|
||||
"potential_total": round(cumulative, 2),
|
||||
"total_gain": round(cumulative - current_total, 2),
|
||||
"steps": waterfall_steps,
|
||||
"improvements": improvement_items,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _to_namespace(d: Dict) -> Dict:
|
||||
"""将dict转为可用点号访问的对象(Jinja2兼容)"""
|
||||
class Namespace(dict):
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return None
|
||||
return Namespace(d) if isinstance(d, dict) else d
|
||||
@@ -0,0 +1,850 @@
|
||||
"""
|
||||
赋分引擎:将原始题目回答按赋分规则转换为分数
|
||||
基于赋分整理表的规则,实现各类赋分函数
|
||||
"""
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import logging
|
||||
import re
|
||||
|
||||
from ..config import DIMENSION_FRAMEWORK, SUBJECTS
|
||||
from .data_engine import DataEngine
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScoringEngine:
|
||||
"""
|
||||
赋分引擎
|
||||
|
||||
赋分逻辑基于"高中课程实施监测指标、题目、赋分整理0127.xlsx"
|
||||
核心策略:按维度分组,从原始数据中提取相关字段,按规则赋分,
|
||||
然后将赋分结果交给统计引擎做PCA合成
|
||||
"""
|
||||
|
||||
def __init__(self, data_engine: DataEngine):
|
||||
self.data_engine = data_engine
|
||||
|
||||
def score_all_schools(self) -> Dict[str, Dict[str, List[float]]]:
|
||||
"""
|
||||
对所有学校的所有维度进行赋分
|
||||
|
||||
返回: {
|
||||
school_name: {
|
||||
sub_dimension_name: [score1, score2, ...]
|
||||
}
|
||||
}
|
||||
"""
|
||||
result = {}
|
||||
for school in self.data_engine.schools:
|
||||
logger.info(f"正在对 {school} 进行赋分...")
|
||||
result[school] = self._score_school(school)
|
||||
return result
|
||||
|
||||
def _score_school(self, school: str) -> Dict[str, List[float]]:
|
||||
"""对单个学校进行全维度赋分"""
|
||||
scores = {}
|
||||
|
||||
# 课程领导力
|
||||
scores["国家标准遵循"] = self._score_national_standard(school)
|
||||
scores["课程结构建设"] = self._score_course_structure(school)
|
||||
scores["课程规范落实"] = self._score_course_norms(school)
|
||||
|
||||
# 教学变革力
|
||||
scores["教学方式变革"] = self._score_teaching_reform(school)
|
||||
scores["作业设计与管理变革"] = self._score_homework_reform(school)
|
||||
|
||||
# 学生发展指导力
|
||||
scores["学科发展的个性化辅导"] = self._score_personalized_tutoring(school)
|
||||
scores["学生生涯发展指导"] = self._score_career_guidance(school)
|
||||
|
||||
# 教师发展支持力
|
||||
scores["培训支持"] = self._score_training_support(school)
|
||||
scores["教研支持"] = self._score_research_support(school)
|
||||
scores["项目支持"] = self._score_project_support(school)
|
||||
|
||||
# 教育质量评估力
|
||||
scores["科学评价观"] = self._score_scientific_evaluation(school)
|
||||
scores["学业质量评估"] = self._score_academic_evaluation(school)
|
||||
scores["综合素质评估"] = self._score_comprehensive_evaluation(school)
|
||||
scores["实践活动评估"] = self._score_practice_evaluation(school)
|
||||
|
||||
# 教育条件保障力
|
||||
scores["区域推进"] = self._score_regional_promotion(school)
|
||||
scores["环境支持"] = self._score_environment_support(school)
|
||||
scores["资源支持"] = self._score_resource_support(school)
|
||||
|
||||
# 数字化赋能力
|
||||
scores["教学方式创新"] = self._score_digital_teaching(school)
|
||||
scores["评价精准化与个性化"] = self._score_digital_evaluation(school)
|
||||
scores["课程迭代优化"] = self._score_digital_curriculum(school)
|
||||
|
||||
return scores
|
||||
|
||||
# ========== 课程领导力 ==========
|
||||
|
||||
def _score_national_standard(self, school: str) -> List[float]:
|
||||
"""
|
||||
国家标准遵循:开足开齐国家课程
|
||||
赋分:必修课程学分低于标准=0,高于标准=1,与标准一致=2
|
||||
选必/选修课程:低于标准=0,达到标准=1
|
||||
"""
|
||||
scores = []
|
||||
hours_df = self.data_engine.get_weekly_hours(school)
|
||||
if len(hours_df) == 0:
|
||||
return [1.0] # 默认中等
|
||||
|
||||
# 按课程类型汇总
|
||||
for course_type, field_name in [
|
||||
("必修", "学科必修课周课时"),
|
||||
("选必", "学科选择性必修课周课时"),
|
||||
("选修", "学科类选修课周课时"),
|
||||
]:
|
||||
type_df = hours_df[hours_df["字段名称"] == field_name]
|
||||
if len(type_df) == 0:
|
||||
scores.append(0.5)
|
||||
continue
|
||||
|
||||
# 按学科汇总总课时
|
||||
total = type_df.groupby("学科")["字段取值"].sum()
|
||||
has_courses = (total > 0).sum()
|
||||
total_hours = total.sum()
|
||||
|
||||
if course_type == "必修":
|
||||
# 必修课:与标准一致=2,高于=1,低于=0
|
||||
# 简化处理:有多少学科开了课
|
||||
exam_subjects = ["语文", "数学", "英语", "物理", "化学", "生物学",
|
||||
"历史", "地理", "思想政治"]
|
||||
opened = sum(1 for s in exam_subjects if s in total.index and total.get(s, 0) > 0)
|
||||
if opened >= len(exam_subjects):
|
||||
scores.append(2.0)
|
||||
elif opened >= 6:
|
||||
scores.append(1.0)
|
||||
else:
|
||||
scores.append(0.0)
|
||||
else:
|
||||
# 选必/选修:达到标准=1,低于=0
|
||||
if total_hours > 0:
|
||||
scores.append(1.0)
|
||||
else:
|
||||
scores.append(0.0)
|
||||
|
||||
return scores if scores else [1.0]
|
||||
|
||||
def _score_course_structure(self, school: str) -> List[float]:
|
||||
"""
|
||||
课程结构建设:学科类课程结构、校本特色课程结构、综合实践活动与劳动
|
||||
赋分:按照离差百分比和填报数值
|
||||
"""
|
||||
scores = []
|
||||
hours_df = self.data_engine.get_weekly_hours(school)
|
||||
|
||||
if len(hours_df) > 0:
|
||||
# 1. 学科类课程结构:各学科三类课程的离差
|
||||
by_subject = hours_df.groupby(["学科", "字段名称"])["字段取值"].sum().unstack(fill_value=0)
|
||||
if len(by_subject) > 0:
|
||||
total_per_subject = by_subject.sum(axis=1)
|
||||
overall_total = total_per_subject.sum()
|
||||
if overall_total > 0:
|
||||
proportions = total_per_subject / overall_total
|
||||
mean_prop = proportions.mean()
|
||||
deviation = np.abs(proportions - mean_prop).sum()
|
||||
# 离差越小越好,标准化到0-3分
|
||||
structure_score = max(0, 3 - deviation * 10)
|
||||
scores.append(structure_score)
|
||||
else:
|
||||
scores.append(1.0)
|
||||
else:
|
||||
scores.append(1.0)
|
||||
|
||||
# 2. 校本特色课程:跨学科选修课门数、综合主题选修课门数
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
for field in ["跨学科选修课门数", "综合主题选修课门数", "综合实践选修课门数"]:
|
||||
vals = course_df[course_df["字段名称"] == field]["字段取值"]
|
||||
if len(vals) > 0:
|
||||
try:
|
||||
v = float(vals.iloc[0])
|
||||
scores.append(min(v / 5, 3.0)) # 归一化
|
||||
except (ValueError, TypeError):
|
||||
scores.append(0.5)
|
||||
|
||||
# 3. 综合实践活动与劳动
|
||||
for field in ["三年应完成的研究性学习数量", "三年社会考察个数", "三年志愿服务时长"]:
|
||||
vals = course_df[course_df["字段名称"] == field]["字段取值"]
|
||||
if len(vals) > 0:
|
||||
try:
|
||||
v = float(vals.iloc[0])
|
||||
scores.append(min(v / 10, 3.0))
|
||||
except (ValueError, TypeError):
|
||||
scores.append(0.5)
|
||||
|
||||
return scores if scores else [1.0]
|
||||
|
||||
def _score_course_norms(self, school: str) -> List[float]:
|
||||
"""
|
||||
课程规范落实:建设规范文本 + 档案规范
|
||||
赋分:有=2,无=0
|
||||
"""
|
||||
scores = []
|
||||
norms = self.data_engine.get_course_norms(school)
|
||||
|
||||
norm_score = 0
|
||||
archive_score = 0
|
||||
norm_count = 0
|
||||
archive_count = 0
|
||||
|
||||
for n in norms:
|
||||
name = str(n.get("字段名称", ""))
|
||||
val = str(n.get("字段取值", ""))
|
||||
|
||||
if "建设规范文本" in name:
|
||||
norm_count += 1
|
||||
if val and val not in ["0", "nan", "None", ""]:
|
||||
norm_score += 2
|
||||
elif "档案" in name:
|
||||
archive_count += 1
|
||||
if "已经建成并使用" in name and val == "1":
|
||||
archive_score += 2
|
||||
elif "已经建成" in name and val == "1":
|
||||
archive_score += 1
|
||||
|
||||
if norm_count > 0:
|
||||
scores.append(norm_score / norm_count * 2)
|
||||
if archive_count > 0:
|
||||
scores.append(archive_score / archive_count * 2)
|
||||
|
||||
return scores if scores else [1.0]
|
||||
|
||||
# ========== 教学变革力 ==========
|
||||
|
||||
def _score_teaching_reform(self, school: str) -> List[float]:
|
||||
"""
|
||||
教学方式变革:认识程度 + 落实程度 + 实施方式
|
||||
赋分:量表题4/3/2/1分;多选每项1分加总
|
||||
"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
sub_scores = []
|
||||
|
||||
# 认识程度类题目
|
||||
for keyword in ["认识程度", "认识"]:
|
||||
rows = sub_df[sub_df["字段名称"].str.contains(keyword, na=False)]
|
||||
for _, row in rows.iterrows():
|
||||
s = self._score_likert(row["字段取值"], reverse=False)
|
||||
if s is not None:
|
||||
sub_scores.append(s)
|
||||
|
||||
# 落实程度类题目
|
||||
for keyword in ["落实程度", "落实"]:
|
||||
rows = sub_df[sub_df["字段名称"].str.contains(keyword, na=False)]
|
||||
for _, row in rows.iterrows():
|
||||
s = self._score_likert(row["字段取值"], reverse=False)
|
||||
if s is not None:
|
||||
sub_scores.append(s)
|
||||
|
||||
# 实施方式(多选,体现形式类)
|
||||
form_rows = sub_df[sub_df["字段名称"].str.contains("体现形式", na=False)]
|
||||
if len(form_rows) > 0:
|
||||
form_count = (form_rows["字段取值"].astype(str) == "1").sum()
|
||||
sub_scores.append(min(form_count, 6))
|
||||
|
||||
if sub_scores:
|
||||
scores.append(np.mean(sub_scores))
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
def _score_homework_reform(self, school: str) -> List[float]:
|
||||
"""
|
||||
作业设计与管理变革:作业设计 + 作业管理
|
||||
"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
sub_scores = []
|
||||
|
||||
# 作业设计:实践类/表现类/跨学科/团队合作作业是否布置
|
||||
for hw_type in ["实践类作业_有布置", "表现类作业_有布置", "跨学科作业_有布置", "团队合作类作业_有布置"]:
|
||||
rows = sub_df[sub_df["字段名称"] == hw_type]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
sub_scores.append(1.0)
|
||||
else:
|
||||
sub_scores.append(0.0)
|
||||
|
||||
# 作业属性标注(多选)
|
||||
attr_rows = sub_df[sub_df["字段名称"].str.contains("作业属性标注", na=False)]
|
||||
if len(attr_rows) > 0:
|
||||
attr_count = (attr_rows["字段取值"].astype(str) == "1").sum()
|
||||
sub_scores.append(min(attr_count, 5))
|
||||
|
||||
# 作业管理:时长控制、批改、评价
|
||||
for field, score_map in [
|
||||
("回家作业时长控制_学校控制", 3),
|
||||
("回家作业时长控制_教研组负责", 2),
|
||||
("作业批改范围_全部批改", 3),
|
||||
("作业批改范围_部分练习", 1),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
sub_scores.append(score_map)
|
||||
|
||||
if sub_scores:
|
||||
scores.append(np.mean(sub_scores))
|
||||
|
||||
return scores if scores else [1.5]
|
||||
|
||||
# ========== 学生发展指导力 ==========
|
||||
|
||||
def _score_personalized_tutoring(self, school: str) -> List[float]:
|
||||
"""个性化辅导"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
|
||||
# 辅导时长
|
||||
for field, val in [
|
||||
("个别辅导时长_每周>2h", 4),
|
||||
("个别辅导时长_每周1~2h", 3),
|
||||
("个别辅导时长_每周<1h", 2),
|
||||
("个别辅导时长_几乎无", 1),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 辅导方式
|
||||
for field, val in [
|
||||
("个别辅导实施方式_分散辅导", 3),
|
||||
("个别辅导实施方式_分组统一辅导", 2),
|
||||
("个别辅导实施方式_班级统一辅导", 1),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
def _score_career_guidance(self, school: str) -> List[float]:
|
||||
"""生涯发展指导"""
|
||||
scores = []
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
|
||||
# 生涯指导实施方式
|
||||
for field, val in [
|
||||
("生涯指导实施方式_专设课程", 3),
|
||||
("生涯指导实施方式_社会考察和志愿服务", 2),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
|
||||
# 覆盖率
|
||||
rows = course_df[course_df["字段名称"] == "完成生涯指导的学生占比_90%+"]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(4)
|
||||
else:
|
||||
scores.append(2)
|
||||
|
||||
# 教师构成
|
||||
for field, val in [
|
||||
("生涯指导教师构成_本校和外聘结合", 4),
|
||||
("生涯指导教师构成_本校为主", 3),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 资源支持
|
||||
for resource in ["生涯指导的校外资源支持程度", "生涯指导的校内资源支持程度"]:
|
||||
rows = course_df[course_df["字段名称"] == resource]
|
||||
if len(rows) > 0:
|
||||
s = self._score_resource_level(rows.iloc[0]["字段取值"])
|
||||
if s is not None:
|
||||
scores.append(s)
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
# ========== 教师发展支持力 ==========
|
||||
|
||||
def _score_training_support(self, school: str) -> List[float]:
|
||||
"""培训支持"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
|
||||
# 提供外校培训的教师人数
|
||||
rows = sub_df[sub_df["字段名称"] == "提供外校培训的教师人数"]
|
||||
if len(rows) > 0:
|
||||
try:
|
||||
v = float(rows.iloc[0]["字段取值"])
|
||||
scores.append(min(v, 5))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# 区域培训指导人数
|
||||
rows = sub_df[sub_df["字段名称"] == "区域培训指导人数"]
|
||||
if len(rows) > 0:
|
||||
try:
|
||||
v = float(rows.iloc[0]["字段取值"])
|
||||
scores.append(min(v, 5))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return scores if scores else [1.0]
|
||||
|
||||
def _score_research_support(self, school: str) -> List[float]:
|
||||
"""教研支持"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
|
||||
# 教研活动次数
|
||||
rows = sub_df[sub_df["字段名称"] == "学科教研组每学期活动次数"]
|
||||
if len(rows) > 0:
|
||||
try:
|
||||
v = float(rows.iloc[0]["字段取值"])
|
||||
scores.append(min(v / 3, 4)) # 归一化
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# 教研活动时长
|
||||
rows = sub_df[sub_df["字段名称"] == "学科教研组平均每次活动时长"]
|
||||
if len(rows) > 0:
|
||||
try:
|
||||
v = float(rows.iloc[0]["字段取值"])
|
||||
scores.append(min(v / 30, 4)) # 30分钟为基准
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# 教研计划
|
||||
rows = sub_df[sub_df["字段名称"] == "学科教研工作计划_有"]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(2)
|
||||
|
||||
# 校级展示
|
||||
for field in ["学科教研组校级展示_有", "学科教研组区域展示_有", "学科教研组成果发表_有"]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(2)
|
||||
|
||||
return scores if scores else [1.0]
|
||||
|
||||
def _score_project_support(self, school: str) -> List[float]:
|
||||
"""项目支持"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
|
||||
# 学科层面的项目
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
for field in ["学科承担的市级教改项目个数", "学科承担的区级教改项目个数", "学科承担的校级教改项目个数"]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0:
|
||||
try:
|
||||
v = float(rows.iloc[0]["字段取值"])
|
||||
scores.append(min(v, 5))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# 学校层面的项目
|
||||
for field in ["学校负责的市级教改项目个数", "学校参与的市级教改项目个数",
|
||||
"学校负责的区级教改项目个数", "学校参与的区级教改项目个数",
|
||||
"校级教改项目个数"]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0:
|
||||
try:
|
||||
v = float(rows.iloc[0]["字段取值"])
|
||||
scores.append(min(v, 5))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return scores if scores else [0.5]
|
||||
|
||||
# ========== 教育质量评估力 ==========
|
||||
|
||||
def _score_scientific_evaluation(self, school: str) -> List[float]:
|
||||
"""科学评价观"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
|
||||
# 作业评价关注点(多选,关注素养发展的项目越多越好)
|
||||
eval_items = sub_df[sub_df["字段名称"].str.contains("作业评价关注点|课堂表现评价关注点|学科实践活动评价关注点", na=False)]
|
||||
if len(eval_items) > 0:
|
||||
focus_count = (eval_items["字段取值"].astype(str) == "1").sum()
|
||||
scores.append(min(focus_count / 3, 4))
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
def _score_academic_evaluation(self, school: str) -> List[float]:
|
||||
"""学业质量评估"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
|
||||
# 评价工具建成
|
||||
for field, val in [
|
||||
("作业评价工具_已经建成并使用", 2),
|
||||
("课堂表现评价工具_已经建成并使用", 2),
|
||||
("作业评价工具_已经建成尚未使用", 1),
|
||||
("作业评价工具_尚未建成和使用", 0),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 考试分析
|
||||
for field, val in [
|
||||
("学期考试分析_执行分析并存档", 3),
|
||||
("学期考试分析_执行分析,不要求存档", 2),
|
||||
("学期考试分析_教师自己决定", 1),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 表现性评价应用
|
||||
for field, val in [
|
||||
("表现性评价应用程度_经常", 4),
|
||||
("表现性评价应用程度_有时", 3),
|
||||
("表现性评价应用程度_总是", 4),
|
||||
("表现性评价应用程度_从不", 1),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
def _score_comprehensive_evaluation(self, school: str) -> List[float]:
|
||||
"""综合素质评估"""
|
||||
scores = []
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
|
||||
# 综评评价体系
|
||||
for field, val in [
|
||||
("综评评价体系_已经建成并使用", 3),
|
||||
("综评评价体系_已经建成尚未使用", 2),
|
||||
("综评评价体系_未建成", 1),
|
||||
("综评评价体系_不准备建设", 0),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 综评信息化
|
||||
for field, val in [
|
||||
("综评信息化实现_自建平台支持", 2),
|
||||
("综评信息化实现_借助第三方平台支持", 1),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 综评结果使用(多选)
|
||||
result_uses = course_df[course_df["字段名称"].str.contains("综评结果使用|综评应用", na=False)]
|
||||
if len(result_uses) > 0:
|
||||
use_count = (result_uses["字段取值"].astype(str) == "1").sum()
|
||||
scores.append(min(use_count, 6))
|
||||
|
||||
return scores if scores else [1.0]
|
||||
|
||||
def _score_practice_evaluation(self, school: str) -> List[float]:
|
||||
"""实践活动评估"""
|
||||
scores = []
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
|
||||
# 研究性学习评价
|
||||
for field, val in [
|
||||
("研究性学习评价工具_已经建成并使用", 2),
|
||||
("研究性学习评价工具_尚未建成和使用", 0),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 社会考察评价
|
||||
for field, val in [
|
||||
("社会考察评价工具_已经建成并使用", 2),
|
||||
("社会考察评价工具_尚未建成和使用", 0),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 学科实践活动评价
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
for subject in SUBJECTS[:5]: # 抽样几个学科
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
for field, val in [
|
||||
("学科实践活动工具_已经建成并使用", 2),
|
||||
("学科实践活动工具_已经建成尚未使用", 1),
|
||||
("学科实践活动工具_尚未建成和使用", 0),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
return scores if scores else [1.0]
|
||||
|
||||
# ========== 教育条件保障力 ==========
|
||||
|
||||
def _score_regional_promotion(self, school: str) -> List[float]:
|
||||
"""区域推进"""
|
||||
scores = []
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
|
||||
# 工作会频率
|
||||
for field, val in [
|
||||
("区域工作会参与_一月4次以上", 7),
|
||||
("区域工作会参与_一月1次", 5),
|
||||
("区域工作会参与_二月1次", 3),
|
||||
("区域工作会参与_三月1次", 1),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 区域推进措施(多选)
|
||||
measures = course_df[course_df["字段名称"].str.contains("区域推进措施", na=False)]
|
||||
if len(measures) > 0:
|
||||
measure_count = (measures["字段取值"].astype(str) == "1").sum()
|
||||
scores.append(min(measure_count, 5))
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
def _score_environment_support(self, school: str) -> List[float]:
|
||||
"""环境支持:硬件+信息化"""
|
||||
scores = []
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
|
||||
# 场馆供给
|
||||
for field, val in [
|
||||
("场馆供给_能满足需要", 3),
|
||||
("场馆供给_基本满足需要", 2),
|
||||
("场馆供给_难以满足需要", 1),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 专用教室供给
|
||||
for field, val in [
|
||||
("专用教室供给_能满足需要", 3),
|
||||
("专用教室供给_基本满足需要", 2),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 信息化平台功能(多选)
|
||||
info_funcs = course_df[course_df["字段名称"].str.contains("信息化平台功能", na=False)]
|
||||
if len(info_funcs) > 0:
|
||||
func_count = (info_funcs["字段取值"].astype(str) == "1").sum()
|
||||
scores.append(min(func_count / 3, 5))
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
def _score_resource_support(self, school: str) -> List[float]:
|
||||
"""资源支持:校内资源 + 校外资源 + 师资配置"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
|
||||
# 校内资源支持程度
|
||||
for field in ["必修课校内资源支持程度", "选择性必修课校内资源支持程度"]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0:
|
||||
s = self._score_resource_level(rows.iloc[0]["字段取值"])
|
||||
if s is not None:
|
||||
scores.append(s)
|
||||
|
||||
# 校外资源支持程度
|
||||
for field in ["必修课校外资源支持程度", "选择性必修课校外资源支持程度"]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0:
|
||||
s = self._score_resource_level(rows.iloc[0]["字段取值"])
|
||||
if s is not None:
|
||||
scores.append(s)
|
||||
|
||||
# 师资:教研组总人数、高级教师比例等
|
||||
rows = sub_df[sub_df["字段名称"] == "学科教研组总人数"]
|
||||
if len(rows) > 0:
|
||||
try:
|
||||
total = float(rows.iloc[0]["字段取值"])
|
||||
scores.append(min(total / 3, 4))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
# ========== 数字化赋能力 ==========
|
||||
|
||||
def _score_digital_teaching(self, school: str) -> List[float]:
|
||||
"""教学方式创新"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
|
||||
# 信息技术融合认识
|
||||
for field in ["信息技术与教学融合的认识_所有人可做到"]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(4)
|
||||
break
|
||||
else:
|
||||
rows = sub_df[sub_df["字段名称"] == "信息技术与教学融合的认识_个别人可做到"]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(1)
|
||||
|
||||
# 信息化终端使用比例
|
||||
for field, val in [
|
||||
("信息化终端使用比例_80%+", 4),
|
||||
("信息化终端使用比例_60~79%", 3),
|
||||
("信息化终端使用比例_30~59%", 2),
|
||||
("信息化终端使用比例_30%-", 1),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
return scores if scores else [2.0]
|
||||
|
||||
def _score_digital_evaluation(self, school: str) -> List[float]:
|
||||
"""评价精准化与个性化"""
|
||||
scores = []
|
||||
subject_df = self.data_engine.get_school_subject_data(school)
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
|
||||
# 学业评价信息化
|
||||
for subject in SUBJECTS:
|
||||
sub_df = subject_df[subject_df["学科"] == subject]
|
||||
for field, val in [
|
||||
("学业评价信息化实现_自建平台支持", 3),
|
||||
("学业评价信息化实现_借助第三方平台支持", 2),
|
||||
("学业评价信息化实现_没有平台支持", 0),
|
||||
]:
|
||||
rows = sub_df[sub_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 学校层面的信息化支持
|
||||
for field, val in [
|
||||
("学校信息系统对选课支持程度", None),
|
||||
("学校信息系统对排课支持程度", None),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0:
|
||||
s = self._score_resource_level(rows.iloc[0]["字段取值"])
|
||||
if s is not None:
|
||||
scores.append(s)
|
||||
|
||||
return scores if scores else [1.5]
|
||||
|
||||
def _score_digital_curriculum(self, school: str) -> List[float]:
|
||||
"""课程迭代优化"""
|
||||
scores = []
|
||||
course_df = self.data_engine.get_school_course_data(school)
|
||||
|
||||
# 数据连通
|
||||
for field, val in [
|
||||
("数据连通_有数据能互通", 3),
|
||||
("数据连通_有数据不互通", 1),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
break
|
||||
|
||||
# 管理业务信息化
|
||||
for field, val in [
|
||||
("管理业务的信息化应用_绝大部分", 4),
|
||||
("教学业务的信息化应用_绝大部分", 4),
|
||||
]:
|
||||
rows = course_df[course_df["字段名称"] == field]
|
||||
if len(rows) > 0 and str(rows.iloc[0]["字段取值"]) == "1":
|
||||
scores.append(val)
|
||||
|
||||
# 已完成网络课程数
|
||||
rows = course_df[course_df["字段名称"] == "已完成的网络课程门数"]
|
||||
if len(rows) > 0:
|
||||
try:
|
||||
v = float(rows.iloc[0]["字段取值"])
|
||||
scores.append(min(v / 3, 4))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return scores if scores else [1.5]
|
||||
|
||||
# ========== 辅助赋分函数 ==========
|
||||
|
||||
@staticmethod
|
||||
def _score_likert(value, scale: int = 4, reverse: bool = False) -> Optional[float]:
|
||||
"""
|
||||
量表题赋分
|
||||
value格式可能是 "1" 或 "2(有一些支持)" 等
|
||||
"""
|
||||
try:
|
||||
val_str = str(value).strip()
|
||||
# 提取数字部分
|
||||
match = re.match(r'^(\d+)', val_str)
|
||||
if match:
|
||||
v = int(match.group(1))
|
||||
if reverse:
|
||||
return float(scale + 1 - v)
|
||||
return float(v)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _score_resource_level(value) -> Optional[float]:
|
||||
"""资源支持程度赋分:几乎没有=1, 有一些=2, 有足够=3"""
|
||||
val_str = str(value).strip()
|
||||
match = re.match(r'^(\d+)', val_str)
|
||||
if match:
|
||||
v = int(match.group(1))
|
||||
return float(v)
|
||||
if "足够" in val_str or "3" in val_str:
|
||||
return 3.0
|
||||
elif "一些" in val_str or "2" in val_str:
|
||||
return 2.0
|
||||
elif "没有" in val_str or "1" in val_str:
|
||||
return 1.0
|
||||
return None
|
||||
@@ -0,0 +1,363 @@
|
||||
"""
|
||||
统计引擎:PCA合成 + 标准化 + 水平判定 + 聚类 + T检验 + 相关性
|
||||
将赋分后的原始数据合成为维度得分,并进行统计分析
|
||||
"""
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from sklearn.decomposition import PCA
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.cluster import KMeans
|
||||
from scipy import stats
|
||||
import logging
|
||||
|
||||
from ..config import (
|
||||
PCA_MEAN, PCA_STD, LEVEL_THRESHOLDS, LEVEL_DESCRIPTIONS,
|
||||
DIMENSION_FRAMEWORK, SUBJECTS, SCHOOL_TYPE_MAP,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StatsEngine:
|
||||
"""统计引擎"""
|
||||
|
||||
def __init__(self):
|
||||
self._dimension_scores: Optional[pd.DataFrame] = None
|
||||
self._sub_dimension_scores: Optional[pd.DataFrame] = None
|
||||
self._subject_dimension_scores: Optional[pd.DataFrame] = None
|
||||
|
||||
def standardize_scores(self, raw_scores: np.ndarray) -> np.ndarray:
|
||||
"""标准化到均值50标准差10"""
|
||||
if len(raw_scores) < 2:
|
||||
return np.full_like(raw_scores, PCA_MEAN, dtype=float)
|
||||
mean = np.nanmean(raw_scores)
|
||||
std = np.nanstd(raw_scores, ddof=1)
|
||||
if std == 0 or np.isnan(std):
|
||||
return np.full_like(raw_scores, PCA_MEAN, dtype=float)
|
||||
return (raw_scores - mean) / std * PCA_STD + PCA_MEAN
|
||||
|
||||
def pca_compose(self, data_matrix: pd.DataFrame) -> np.ndarray:
|
||||
"""
|
||||
PCA合成:将多个变量合成为一个主成分分数
|
||||
data_matrix: 行=学校, 列=变量
|
||||
返回:合成后的分数(已标准化到50/10)
|
||||
"""
|
||||
# 处理缺失值:均值填充
|
||||
filled = data_matrix.fillna(data_matrix.mean())
|
||||
if filled.shape[1] == 0:
|
||||
return np.full(filled.shape[0], PCA_MEAN)
|
||||
|
||||
if filled.shape[1] == 1:
|
||||
# 只有一个变量,直接标准化
|
||||
return self.standardize_scores(filled.iloc[:, 0].values)
|
||||
|
||||
# 标准化
|
||||
scaler = StandardScaler()
|
||||
scaled = scaler.fit_transform(filled)
|
||||
|
||||
# PCA取第一主成分
|
||||
n_components = min(1, filled.shape[1], filled.shape[0])
|
||||
pca = PCA(n_components=n_components)
|
||||
scores = pca.fit_transform(scaled)[:, 0]
|
||||
|
||||
# 如果载荷为负(方向反转),翻转
|
||||
loadings = pca.components_[0]
|
||||
if np.sum(loadings) < 0:
|
||||
scores = -scores
|
||||
|
||||
# 标准化到50/10
|
||||
return self.standardize_scores(scores)
|
||||
|
||||
def determine_level(self, score: float, dimension: str) -> int:
|
||||
"""根据分数和维度确定水平(1-4)"""
|
||||
thresholds = LEVEL_THRESHOLDS.get(dimension, {})
|
||||
if not thresholds:
|
||||
# 默认阈值
|
||||
if score > 55:
|
||||
return 4
|
||||
elif score > 50:
|
||||
return 3
|
||||
elif score > 45:
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
|
||||
if score > thresholds["level4"]:
|
||||
return 4
|
||||
elif score > thresholds["level3"]:
|
||||
return 3
|
||||
elif score > thresholds["level2"]:
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
|
||||
def get_level_description(self, dimension: str, level: int) -> str:
|
||||
"""获取水平的质性描述"""
|
||||
descriptions = LEVEL_DESCRIPTIONS.get(dimension, {})
|
||||
return descriptions.get(level, f"水平{level}")
|
||||
|
||||
def compute_dimension_scores(self, school_raw_scores: Dict[str, Dict[str, List[float]]]) -> pd.DataFrame:
|
||||
"""
|
||||
计算所有学校在各三级维度上的得分
|
||||
|
||||
school_raw_scores: {
|
||||
school_name: {
|
||||
sub_dimension_name: [score1, score2, ...] # 该维度下各题目的赋分
|
||||
}
|
||||
}
|
||||
|
||||
返回 DataFrame: 行=学校, 列=三级维度, 值=标准化得分
|
||||
"""
|
||||
schools = list(school_raw_scores.keys())
|
||||
all_sub_dims = []
|
||||
for dim, info in DIMENSION_FRAMEWORK.items():
|
||||
all_sub_dims.extend(info["sub_dimensions"])
|
||||
|
||||
# 构建原始矩阵
|
||||
raw_matrix = {}
|
||||
for sub_dim in all_sub_dims:
|
||||
values = []
|
||||
for school in schools:
|
||||
scores = school_raw_scores.get(school, {}).get(sub_dim, [])
|
||||
values.append(np.nanmean(scores) if scores else np.nan)
|
||||
raw_matrix[sub_dim] = values
|
||||
|
||||
raw_df = pd.DataFrame(raw_matrix, index=schools)
|
||||
|
||||
# PCA合成并标准化各维度
|
||||
result = pd.DataFrame(index=schools)
|
||||
for sub_dim in all_sub_dims:
|
||||
if sub_dim in raw_df.columns:
|
||||
result[sub_dim] = self.standardize_scores(raw_df[sub_dim].values)
|
||||
else:
|
||||
result[sub_dim] = PCA_MEAN
|
||||
|
||||
self._sub_dimension_scores = result
|
||||
return result
|
||||
|
||||
def compute_dimension_aggregates(self, sub_scores: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
从三级维度分数聚合到二级维度(均值)
|
||||
|
||||
sub_scores: 行=学校, 列=三级维度
|
||||
返回: 行=学校, 列=二级维度
|
||||
"""
|
||||
result = pd.DataFrame(index=sub_scores.index)
|
||||
for dim, info in DIMENSION_FRAMEWORK.items():
|
||||
sub_dims = [s for s in info["sub_dimensions"] if s in sub_scores.columns]
|
||||
if sub_dims:
|
||||
result[dim] = sub_scores[sub_dims].mean(axis=1)
|
||||
else:
|
||||
result[dim] = PCA_MEAN
|
||||
|
||||
# 总体得分(七维度均值)
|
||||
result["总体得分"] = result[list(DIMENSION_FRAMEWORK.keys())].mean(axis=1)
|
||||
self._dimension_scores = result
|
||||
return result
|
||||
|
||||
def compute_levels(self, sub_scores: pd.DataFrame) -> pd.DataFrame:
|
||||
"""计算各学校各维度的水平等级"""
|
||||
levels = pd.DataFrame(index=sub_scores.index)
|
||||
for col in sub_scores.columns:
|
||||
levels[col] = sub_scores[col].apply(
|
||||
lambda x: self.determine_level(x, col)
|
||||
)
|
||||
return levels
|
||||
|
||||
def cluster_analysis(self, scores: pd.DataFrame, n_clusters: int = 2) -> Dict:
|
||||
"""
|
||||
聚类分析
|
||||
|
||||
scores: 行=学校, 列=维度
|
||||
返回: 聚类标签和各类特征
|
||||
"""
|
||||
# 标准化
|
||||
scaler = StandardScaler()
|
||||
scaled = scaler.fit_transform(scores.fillna(PCA_MEAN))
|
||||
|
||||
# KMeans聚类
|
||||
n_clusters = min(n_clusters, len(scores))
|
||||
if n_clusters < 2:
|
||||
return {"labels": [0] * len(scores), "centers": scores.values.tolist()}
|
||||
|
||||
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
|
||||
labels = kmeans.fit_predict(scaled)
|
||||
|
||||
# 计算各类均值
|
||||
cluster_means = {}
|
||||
for c in range(n_clusters):
|
||||
mask = labels == c
|
||||
cluster_means[c] = scores[mask].mean().to_dict()
|
||||
|
||||
# 确定哪个是"较好类"(总均值更高的)
|
||||
avg_per_cluster = {c: np.mean(list(v.values())) for c, v in cluster_means.items()}
|
||||
sorted_clusters = sorted(avg_per_cluster.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
cluster_names = {}
|
||||
for rank, (c, _) in enumerate(sorted_clusters):
|
||||
if rank == 0:
|
||||
cluster_names[c] = "较好"
|
||||
else:
|
||||
cluster_names[c] = "待提升"
|
||||
|
||||
return {
|
||||
"labels": labels.tolist(),
|
||||
"school_clusters": {
|
||||
school: cluster_names[labels[i]]
|
||||
for i, school in enumerate(scores.index)
|
||||
},
|
||||
"cluster_means": cluster_means,
|
||||
"cluster_names": cluster_names,
|
||||
}
|
||||
|
||||
def t_test_vs_mean(self, school_scores: np.ndarray, ref_mean: float) -> Dict:
|
||||
"""
|
||||
单样本T检验:学校各学科得分 vs 参考均值
|
||||
|
||||
school_scores: 该学校在某维度各学科的得分
|
||||
ref_mean: 参考均值(如区均值、全市均值)
|
||||
"""
|
||||
scores = school_scores[~np.isnan(school_scores)]
|
||||
if len(scores) < 2:
|
||||
return {"t": np.nan, "p": np.nan, "significant": False, "n": len(scores)}
|
||||
|
||||
t_stat, p_value = stats.ttest_1samp(scores, ref_mean)
|
||||
return {
|
||||
"t": round(float(t_stat), 3),
|
||||
"p": round(float(p_value), 4),
|
||||
"significant": float(p_value) < 0.05,
|
||||
"n": len(scores),
|
||||
}
|
||||
|
||||
def correlation_analysis(self, dim_scores: pd.DataFrame) -> pd.DataFrame:
|
||||
"""维度间相关性分析"""
|
||||
dim_cols = [c for c in dim_scores.columns if c in DIMENSION_FRAMEWORK]
|
||||
return dim_scores[dim_cols].corr()
|
||||
|
||||
def compute_school_report_data(self, school: str,
|
||||
sub_scores: pd.DataFrame,
|
||||
dim_scores: pd.DataFrame) -> Dict:
|
||||
"""
|
||||
为某一所学校生成完整的报告数据包
|
||||
|
||||
返回包含所有统计分析结果的结构化数据
|
||||
"""
|
||||
schools = list(sub_scores.index)
|
||||
if school not in schools:
|
||||
raise ValueError(f"学校 '{school}' 不在数据中")
|
||||
|
||||
school_info = SCHOOL_TYPE_MAP.get(school, {})
|
||||
school_type = school_info.get("type", "")
|
||||
|
||||
# 区均值
|
||||
district_avg_sub = sub_scores.mean()
|
||||
district_avg_dim = dim_scores.mean()
|
||||
|
||||
# 同类学校均值
|
||||
same_type_schools = [s for s in schools if SCHOOL_TYPE_MAP.get(s, {}).get("type") == school_type]
|
||||
if same_type_schools:
|
||||
same_type_avg_sub = sub_scores.loc[same_type_schools].mean()
|
||||
same_type_avg_dim = dim_scores.loc[same_type_schools].mean()
|
||||
else:
|
||||
same_type_avg_sub = district_avg_sub
|
||||
same_type_avg_dim = district_avg_dim
|
||||
|
||||
# 水平判定
|
||||
levels = self.compute_levels(sub_scores)
|
||||
|
||||
# 聚类(二级维度)
|
||||
dim_cols = [c for c in dim_scores.columns if c in DIMENSION_FRAMEWORK]
|
||||
overall_cluster = self.cluster_analysis(dim_scores[dim_cols])
|
||||
|
||||
# 各二级维度聚类
|
||||
dim_clusters = {}
|
||||
for dim, info in DIMENSION_FRAMEWORK.items():
|
||||
sub_dims = [s for s in info["sub_dimensions"] if s in sub_scores.columns]
|
||||
if sub_dims:
|
||||
dim_clusters[dim] = self.cluster_analysis(sub_scores[sub_dims])
|
||||
|
||||
# 相关性
|
||||
correlation = self.correlation_analysis(dim_scores)
|
||||
|
||||
# 构建报告数据
|
||||
report = {
|
||||
"school": school,
|
||||
"school_info": school_info,
|
||||
|
||||
# 总体得分
|
||||
"overall": {
|
||||
"score": round(float(dim_scores.loc[school, "总体得分"]), 2),
|
||||
"district_avg": round(float(district_avg_dim["总体得分"]), 2),
|
||||
"same_type_avg": round(float(same_type_avg_dim.get("总体得分", PCA_MEAN)), 2),
|
||||
"rank_in_district": int((dim_scores["总体得分"] >= dim_scores.loc[school, "总体得分"]).sum()),
|
||||
"total_schools": len(schools),
|
||||
"cluster": overall_cluster["school_clusters"].get(school, ""),
|
||||
},
|
||||
|
||||
# 二级维度
|
||||
"dimensions": {},
|
||||
|
||||
# 三级维度
|
||||
"sub_dimensions": {},
|
||||
|
||||
# 相关性矩阵
|
||||
"correlation": correlation.to_dict(),
|
||||
|
||||
# 所有学校得分(用于对比)
|
||||
"all_schools_dim_scores": dim_scores.to_dict(),
|
||||
"all_schools_sub_scores": sub_scores.to_dict(),
|
||||
}
|
||||
|
||||
# 填充二级维度数据
|
||||
for dim in DIMENSION_FRAMEWORK:
|
||||
score = float(dim_scores.loc[school, dim])
|
||||
d_avg = float(district_avg_dim[dim])
|
||||
st_avg = float(same_type_avg_dim.get(dim, PCA_MEAN))
|
||||
|
||||
# T检验:该学校在该维度下各三级维度得分 vs 区均值
|
||||
sub_dims = DIMENSION_FRAMEWORK[dim]["sub_dimensions"]
|
||||
sub_vals = np.array([float(sub_scores.loc[school, s]) for s in sub_dims if s in sub_scores.columns])
|
||||
t_test = self.t_test_vs_mean(sub_vals, d_avg)
|
||||
|
||||
report["dimensions"][dim] = {
|
||||
"score": round(score, 2),
|
||||
"district_avg": round(d_avg, 2),
|
||||
"same_type_avg": round(st_avg, 2),
|
||||
"diff_district": round(score - d_avg, 2),
|
||||
"rank_in_district": int((dim_scores[dim] >= score).sum()),
|
||||
"t_test_vs_district": t_test,
|
||||
"cluster": dim_clusters.get(dim, {}).get("school_clusters", {}).get(school, ""),
|
||||
}
|
||||
|
||||
# 填充三级维度数据
|
||||
for dim, info in DIMENSION_FRAMEWORK.items():
|
||||
for sub_dim in info["sub_dimensions"]:
|
||||
if sub_dim not in sub_scores.columns:
|
||||
continue
|
||||
score = float(sub_scores.loc[school, sub_dim])
|
||||
d_avg = float(district_avg_sub[sub_dim])
|
||||
level = int(levels.loc[school, sub_dim])
|
||||
|
||||
# 各学校在该维度的排名
|
||||
rank = int((sub_scores[sub_dim] >= score).sum())
|
||||
|
||||
# 水平分布统计
|
||||
dim_levels = levels[sub_dim]
|
||||
level_dist = {
|
||||
f"水平{i}": int((dim_levels == i).sum())
|
||||
for i in range(1, 5)
|
||||
}
|
||||
|
||||
report["sub_dimensions"][sub_dim] = {
|
||||
"parent_dimension": dim,
|
||||
"score": round(score, 2),
|
||||
"district_avg": round(d_avg, 2),
|
||||
"diff_district": round(score - d_avg, 2),
|
||||
"rank_in_district": rank,
|
||||
"level": level,
|
||||
"level_description": self.get_level_description(sub_dim, level),
|
||||
"level_distribution": level_dist,
|
||||
}
|
||||
|
||||
return report
|
||||
Reference in New Issue
Block a user