接上篇:从青涩到 AI:我与评估程序的三十年 “纠缠” 与重启
主要对参数配置和模板文件处理进行了改动,将可参数化的数据放到了config.yaml
文件中,再一个将模板文件(评估模板.xlsx)分离为(7年级模板.xlsx,8年级模板.xlsx,9年级模板.xlsx),真正做到评估结果直接可以打印。对于各年级评估算法一样,只是科目名不同,所以模板大同小异,分别设置模板后,以免打印时再次重新调整,提高易用性和效率。
主要改动:
-
评估规则参数化配置:
- 核心改动: 将之前硬编码在 Python 代码 (
score_evaluator.py
的_evaluate_*
函数中) 的大量评估规则数值移到了config.yaml
文件中。 - 具体参数: 这包括:
- 排名阈值: 如一层次评估中的 80 名、160 名界限。
- 重点评估得分: 每个项目中,符合条件的学生所得的分数(例如,一层次班团总分前 80 名得 2 分,二层次教师单科优秀且总分合格得 2 分等)。
- 全面评估权重/乘数: 在计算优秀率、平均分、合格率、非低分率等指标相对于同层次最高值的得分时,所使用的乘数(例如,一层次班团优秀率积分乘以 40)。
- 教师最终总分权重: 在计算教师最终总分时,小计1 和小计2 所占的百分比(例如,一层次教师的小计1权重为 0.3,小计2权重为 0.7)。
- 实现方式: 在
config.yaml
的每个年级配置下增加了evaluation_rules
部分,并细分了class_team
和teacher
,再按level_1
和level_2
组织这些参数。Python 代码中的相应评估函数被修改为从self.eval_rules
读取这些配置值。
- 核心改动: 将之前硬编码在 Python 代码 (
-
模板文件按年级配置:
- 核心改动: 将原先在
config.yaml
的general_settings
中的全局template_file
设置,移至每个年级(7, 8, 9)各自的配置块内。 - 实现方式: 每个年级配置下现在都有一个独立的
template_file
键,例如7: template_file: '7年级模板.xlsx'
。Python 代码 (__init__
,check_required_files
,save_results
) 被修改为读取和使用当前评估年级对应的template_file
配置。
- 核心改动: 将原先在
-
代码适应性修改:
ScoreEvaluator.__init__
: 更新以加载新的evaluation_rules
和年级特定的template_file
。load_config
: 更新以验证新的配置结构。- 评估函数 (
_evaluate_*
): 更新以使用从配置加载的规则参数。 - 文件处理函数 (
check_required_files
,save_results
): 更新以使用年级特定的模板文件名。
-
计算逻辑修正 (附带):
- 根据您的确认,修正了
_evaluate_second_level_class
函数中二层次班团的总积分计算逻辑,使其现在等于未加权的小计1与未加权的小计2之和。
- 根据您的确认,修正了
这些改动带来的好处:
-
提高灵活性和可配置性:
- 最显著的好处是,如果学校将来需要调整评估标准(例如修改排名界限、调整各项得分或权重),只需修改
config.yaml
文件即可,不再需要深入 Python 代码进行修改。这大大降低了调整评估规则的难度和风险。 - 可以轻松地为不同年级设置不同的评估规则和模板文件,满足差异化需求。
- 最显著的好处是,如果学校将来需要调整评估标准(例如修改排名界限、调整各项得分或权重),只需修改
-
增强可维护性:
- 将易变的评估规则与相对稳定的计算逻辑分离。代码 (
.py
) 专注于“如何计算”,配置文件 (.yaml
) 则定义“计算标准是什么”。 - 规则集中存放在
config.yaml
中,更易于查找、理解和管理。修改配置通常比修改代码更安全,减少了引入程序错误的可能性。
- 将易变的评估规则与相对稳定的计算逻辑分离。代码 (
-
提升代码清晰度和可读性:
- 评估函数中的代码不再充斥着各种“魔法数字”,而是引用具有明确含义的配置变量(如
rules['focus_points_rank1']
),使得代码逻辑更易懂。
- 评估函数中的代码不再充斥着各种“魔法数字”,而是引用具有明确含义的配置变量(如
-
便于扩展和重用:
- 如果需要为新的年级(如六年级)添加评估,只需在
config.yaml
中增加对应的配置块,并准备好相应的模板文件,代码主体基本无需改动。 - 这套程序更容易被其他需要类似评估逻辑的场景复用,只需修改配置文件即可适应新规则。
- 如果需要为新的年级(如六年级)添加评估,只需在
总而言之,这些改动将评估的核心“规则”部分从程序代码中解耦出来,放入了专门的配置文件,使得程序更加灵活、易于维护和适应未来的变化。
修改后配置文件 “config.yaml”
# === 程序配置文件 ===
# --- 通用设置 ---
general_settings:
# template_file: '评估模板.xlsx' # <--- 移除此行
# !!! 重要:请根据实际教师分工表修改此项 !!!
head_teacher_column_name: '班主任' # 教师分工表中代表"班主任"的列名
class_team_output_label: '班团' # 在最终评估结果中代表"班团"评估的标签名
# --- 评分标准阈值 (百分比) ---
score_thresholds:
excellent_rate: 0.8 # 优秀线比例 (满分的80%)
pass_rate: 0.6 # 合格线比例 (满分的60%)
low_score_rate: 0.3 # 低分线低于此比例 (满分的30%)
# --- 年级特定配置 ---
grades:
# --- 七年级配置 ---
7:
template_file: '7年级模板.xlsx' # <--- 新增七年级模板配置
# 成绩表科目列名 (参与总分计算和排名)
subjects:
- 语文
- 数学
- 英语
- 道法
- 历史
- 地理
- 生物
# 各科目满分
full_scores:
语文: 120
数学: 120
英语: 90
道法: 60
历史: 60
地理: 50
生物: 100
# 教师分工表科目列名 (程序将查找这些列以获取教师姓名)
teacher_file_subjects:
- 语文
- 数学
- 英语
- 生物
- 地理
- 道法
- 历史
# --- 评估规则 ---
evaluation_rules:
class_team: # 班团评估
level_1: # 一层次
rank_threshold_1: 80 # 重点评估项目1排名界限
rank_threshold_2: 160 # 重点评估项目2排名界限
focus_points_rank1: 2 # 项目1得分/人
focus_points_rank2: 1.5 # 项目2得分/人
focus_points_excellent: 1 # 项目3得分/人
comp_weight_excellent: 40 # 全面评估-优生率积分权重
comp_weight_avg: 40 # 全面评估-平均分积分权重
comp_weight_non_low: 20 # 全面评估-非低分率积分权重
level_2: # 二层次
focus_points_excellent: 2 # 项目1得分/人
focus_points_pass: 1.5 # 项目2得分/人
comp_weight_excellent: 5 # 全面评估-优生率积分权重
comp_weight_pass: 30 # 全面评估-合格率积分权重
comp_weight_avg: 30 # 全面评估-平均分积分权重
comp_weight_non_low: 35 # 全面评估-非低分率积分权重
# 注意:二层次班团总分 = 小计1 + 小计2 (直接相加)
teacher: # 教师评估
level_1: # 一层次
rank_threshold_1: 80 # 重点评估项目1&2排名界限
rank_threshold_2: 160
focus_points_p1: 2 # 项目1得分/人
focus_points_p2: 1.5 # 项目2得分/人
focus_points_p3: 1 # 项目3得分/人
focus_points_p4: 0.5 # 项目4得分/人
comp_weight_excellent: 40 # 全面评估-优生率积分权重
comp_weight_avg: 40 # 全面评估-平均分积分权重
comp_weight_non_low: 20 # 全面评估-非低分率积分权重
final_weight_sub1: 0.3 # 最终总分中小计1权重
final_weight_sub2: 0.7 # 最终总分中小计2权重
level_2: # 二层次
focus_points_p1: 2 # 项目1得分/人
focus_points_p2: 1.5 # 项目2得分/人
comp_weight_excellent: 5 # 全面评估-优生率积分权重
comp_weight_pass: 30 # 全面评估-合格率积分权重
comp_weight_avg: 30 # 全面评估-平均分积分权重
comp_weight_non_low: 35 # 全面评估-非低分率积分权重
final_weight_sub1: 0.2 # 最终总分中小计1权重
final_weight_sub2: 0.8 # 最终总分中小计2权重
# --- 八年级配置 ---
8:
template_file: '8年级模板.xlsx' # <--- 新增八年级模板配置
subjects:
- 语文
- 数学
- 英语
- 物理
- 生物
- 地理
- 历史
- 道法
full_scores:
语文: 120
数学: 120
英语: 120
物理: 70
生物: 50
地理: 50
历史: 60
道法: 60
teacher_file_subjects:
- 语文
- 数学
- 英语
- 物理
- 道法
- 历史
- 地理
- 生物
# --- 评估规则 ---
evaluation_rules: # (与7年级相同,如果不同请修改)
class_team:
level_1:
rank_threshold_1: 80
rank_threshold_2: 160
focus_points_rank1: 2
focus_points_rank2: 1.5
focus_points_excellent: 1
comp_weight_excellent: 40
comp_weight_avg: 40
comp_weight_non_low: 20
level_2:
focus_points_excellent: 2
focus_points_pass: 1.5
comp_weight_excellent: 5
comp_weight_pass: 30
comp_weight_avg: 30
comp_weight_non_low: 35
teacher:
level_1:
rank_threshold_1: 80
rank_threshold_2: 160
focus_points_p1: 2
focus_points_p2: 1.5
focus_points_p3: 1
focus_points_p4: 0.5
comp_weight_excellent: 40
comp_weight_avg: 40
comp_weight_non_low: 20
final_weight_sub1: 0.3
final_weight_sub2: 0.7
level_2:
focus_points_p1: 2
focus_points_p2: 1.5
comp_weight_excellent: 5
comp_weight_pass: 30
comp_weight_avg: 30
comp_weight_non_low: 35
final_weight_sub1: 0.2
final_weight_sub2: 0.8
# --- 九年级配置 ---
9:
template_file: '9年级模板.xlsx' # <--- 新增九年级模板配置
subjects:
- 语文
- 数学
- 英语
- 物理
- 化学
- 历史
- 道法
full_scores:
语文: 120
数学: 120
英语: 120
物理: 80
化学: 70
历史: 50
道法: 50
teacher_file_subjects:
- 语文
- 数学
- 英语
- 物理
- 化学
- 历史
- 道法
# --- 评估规则 ---
evaluation_rules: # (与7年级相同,如果不同请修改)
class_team:
level_1:
rank_threshold_1: 80
rank_threshold_2: 160
focus_points_rank1: 2
focus_points_rank2: 1.5
focus_points_excellent: 1
comp_weight_excellent: 40
comp_weight_avg: 40
comp_weight_non_low: 20
level_2:
focus_points_excellent: 2
focus_points_pass: 1.5
comp_weight_excellent: 5
comp_weight_pass: 30
comp_weight_avg: 30
comp_weight_non_low: 35
teacher:
level_1:
rank_threshold_1: 80
rank_threshold_2: 160
focus_points_p1: 2
focus_points_p2: 1.5
focus_points_p3: 1
focus_points_p4: 0.5
comp_weight_excellent: 40
comp_weight_avg: 40
comp_weight_non_low: 20
final_weight_sub1: 0.3
final_weight_sub2: 0.7
level_2:
focus_points_p1: 2
focus_points_p2: 1.5
comp_weight_excellent: 5
comp_weight_pass: 30
comp_weight_avg: 30
comp_weight_non_low: 35
final_weight_sub1: 0.2
final_weight_sub2: 0.8
修改后的程序
import pandas as pd
import numpy as np
import os
import shutil
from datetime import datetime
import yaml # 新增:用于读取 YAML 配置
# 尝试导入 openpyxl,如果失败则提示安装
try:
from openpyxl import load_workbook
except ImportError:
print("错误:缺少必需的库 'openpyxl'。请使用 'pip install openpyxl' 命令安装后再试。")
raise # 重新抛出异常
# --- 全局配置变量 ---
CONFIG = None # 用于存储加载的配置
CONFIG_FILE = 'config.yaml' # 配置文件名
def load_config(config_path=CONFIG_FILE):
"""加载 YAML 配置文件"""
global CONFIG
try:
with open(config_path, 'r', encoding='utf-8') as f:
CONFIG = yaml.safe_load(f)
if CONFIG is None:
raise ValueError(f"配置文件 '{config_path}' 为空或格式不正确。")
# 基本的配置验证 (可以根据需要扩展)
if 'general_settings' not in CONFIG or 'score_thresholds' not in CONFIG or 'grades' not in CONFIG:
raise ValueError(f"配置文件 '{config_path}' 缺少必需的顶层键 (general_settings, score_thresholds, grades)。")
# !! 新增: 检查每个年级配置中是否有 template_file 和 evaluation_rules
for grade, grade_config in CONFIG['grades'].items():
if 'template_file' not in grade_config:
raise ValueError(f"配置文件 '{config_path}' 中年级 {grade} 缺少必需的 'template_file' 配置。")
if 'evaluation_rules' not in grade_config:
raise ValueError(f"配置文件 '{config_path}' 中年级 {grade} 缺少必需的 'evaluation_rules' 配置。")
# 可选:更深入地检查 evaluation_rules 内部结构
if 'class_team' not in grade_config['evaluation_rules'] or 'teacher' not in grade_config['evaluation_rules']:
raise ValueError(f"配置文件 '{config_path}' 中年级 {grade} 的 'evaluation_rules' 缺少 'class_team' 或 'teacher' 配置。")
# ... 可根据需要添加更多层级的检查 ...
print(f"配置文件 '{config_path}' 加载成功。")
except FileNotFoundError:
print(f"错误:配置文件 '{config_path}' 未找到。请确保它与脚本在同一目录下。")
raise
except yaml.YAMLError as e:
print(f"错误:解析配置文件 '{config_path}' 时出错:{e}")
raise
except ValueError as e: # 捕获上面添加的 ValueError
print(f"错误:配置文件 '{config_path}' 内容不完整或格式错误:{e}")
raise
except Exception as e:
print(f"加载配置文件时发生未知错误:{e}")
raise
class ScoreEvaluator:
def __init__(self, grade):
global CONFIG
if CONFIG is None:
# 尝试加载配置,如果 main 函数未预先加载
try:
load_config()
except Exception:
print("在 ScoreEvaluator 初始化时加载配置失败。程序无法继续。")
raise RuntimeError("无法加载配置文件")
# grade 现在是整数
if grade not in CONFIG['grades']:
supported_grades_str = [str(k) for k in CONFIG['grades'].keys()]
raise ValueError(f"不支持的年级: {grade}. 配置文件中支持的年级: {supported_grades_str}")
self.grade = grade
self.grade_config = CONFIG['grades'][grade]
self.general_config = CONFIG['general_settings']
self.threshold_config = CONFIG['score_thresholds']
# --- 关键修复:确保加载评估规则 ---
# 检查 evaluation_rules 是否存在,如果不存在则抛出更明确的错误
if 'evaluation_rules' not in self.grade_config:
raise ValueError(f"配置文件 '{CONFIG_FILE}' 中年级 {grade} 缺少必需的 'evaluation_rules' 配置。")
self.eval_rules = self.grade_config['evaluation_rules']
# -------------------------------------
# --- 从配置加载其他设置 ---
self.SUBJECTS = self.grade_config['subjects']
self.SUBJECT_FULL_SCORES = self.grade_config['full_scores']
self.TEACHER_FILE_SUBJECTS = self.grade_config['teacher_file_subjects']
# !! 修改: 从年级配置加载 template_file
if 'template_file' not in self.grade_config:
raise ValueError(f"配置文件 '{CONFIG_FILE}' 中年级 {grade} 缺少必需的 'template_file' 配置。")
self.template_file = self.grade_config['template_file']
self.head_teacher_col = self.general_config['head_teacher_column_name']
self.class_team_label = self.general_config['class_team_output_label']
self.SCORE_THRESHOLDS = self.threshold_config
# -----------------------
# --- 设置动态文件名和目录名 ---
self.scores_file = f'{self.grade}年级成绩表.xlsx'
self.teachers_file = f'{self.grade}年级教师分工表.xlsx'
self.detailed_data_dir = f'{self.grade}年级详细数据'
# ---------------------------
self.subject_thresholds = self._calculate_subject_thresholds()
if not os.path.exists(self.detailed_data_dir):
os.makedirs(self.detailed_data_dir)
def _calculate_subject_thresholds(self):
"""计算各科目的优秀、合格、低分线"""
thresholds = {}
# 使用 self.SUBJECT_FULL_SCORES 和 self.SCORE_THRESHOLDS (从配置加载)
for subject, full_score in self.SUBJECT_FULL_SCORES.items():
thresholds[subject] = {
'excellent': full_score * self.SCORE_THRESHOLDS['excellent_rate'],
'pass': full_score * self.SCORE_THRESHOLDS['pass_rate'],
'low': full_score * self.SCORE_THRESHOLDS['low_score_rate']
}
return thresholds
def check_required_files(self):
"""检查必需的文件是否存在"""
# 使用动态和配置的文件名
required_files = [self.scores_file, self.teachers_file, self.template_file]
missing_files = []
for file in required_files:
if not os.path.exists(file):
missing_files.append(file)
if missing_files:
# !! 修改: 明确指出哪个模板文件缺失
if self.template_file in missing_files:
raise FileNotFoundError(f"缺少必需文件,特别是 {self.grade} 年级的模板文件 '{self.template_file}'。其他缺失文件(如果有):{', '.join(f for f in missing_files if f != self.template_file)}")
else:
raise FileNotFoundError(f"缺少以下必需文件:{', '.join(missing_files)}")
def prepare_data(self):
"""准备评估所需的数据"""
self.check_required_files()
try:
# 读取原始数据 (使用动态文件名)
self.scores_df = pd.read_excel(self.scores_file)
self.teachers_df = pd.read_excel(self.teachers_file)
# self.template_df = pd.read_excel(self.template_file) # 不再需要读取模板内容
# 检查必需的列 (使用配置的科目列表和班主任列名)
required_scores_columns = ['评估班级', '层次', '准考证', '姓名'] + self.SUBJECTS # 确保准考证和姓名也在检查列表里
# 教师表需要班号、配置的班主任列名、以及配置的所有科目教师列
required_teachers_columns = ['班号', self.head_teacher_col] + self.TEACHER_FILE_SUBJECTS
missing_scores_cols = [col for col in required_scores_columns if col not in self.scores_df.columns]
if missing_scores_cols:
raise ValueError(f"{self.scores_file} 缺少必需的列:{', '.join(missing_scores_cols)}")
missing_teachers_cols = [col for col in required_teachers_columns if col not in self.teachers_df.columns]
if missing_teachers_cols:
raise ValueError(f"{self.teachers_file} 缺少必需的列:{', '.join(missing_teachers_cols)}")
# 验证层次数据 (保持不变)
if not self.scores_df['层次'].isin(['一层次', '二层次']).all():
raise ValueError(f"{self.scores_file} 中的层次列必须只包含'一层次'或'二层次'")
# 将层次值映射为数字 (保持不变)
self.scores_df['层次_数值'] = self.scores_df['层次'].map({'一层次': 1, '二层次': 2})
# 重新组织教师数据 (使用配置的班主任列名和科目列表)
teacher_data = []
for _, row in self.teachers_df.iterrows():
班号 = row['班号']
# 添加班主任记录 (使用配置的列名)
teacher_data.append({
'班号': 班号,
'教师姓名': row[self.head_teacher_col],
'学科': '__HeadTeacher__' # 使用内部固定标识符,不暴露给配置
})
# 添加各科目教师记录 (使用配置的 self.TEACHER_FILE_SUBJECTS)
for subject in self.TEACHER_FILE_SUBJECTS:
teacher_data.append({
'班号': 班号,
'教师姓名': row[subject],
'学科': subject
})
# 转换为DataFrame
self.teachers_df = pd.DataFrame(teacher_data)
# 创建班主任映射字典 (使用内部标识符查找)
self.class_teacher_map = self.teachers_df[self.teachers_df['学科'] == '__HeadTeacher__'].set_index('班号')['教师姓名'].to_dict()
# 计算总分 (使用配置的科目列表 self.SUBJECTS)
self.scores_df['总分'] = self.scores_df[self.SUBJECTS].sum(axis=1)
# 计算各科目排名
self._calculate_rankings()
except KeyError as e:
# 捕获因配置错误或Excel列名不匹配导致的KeyError
print(f"数据准备过程中出现列名错误:找不到列 '{e}'。")
print(f"请检查配置文件 ({CONFIG_FILE}) 中的科目/班主任列名是否与 Excel 文件 ({self.scores_file}, {self.teachers_file}) 完全匹配。")
raise ValueError(f"列名不匹配错误: {e}")
except Exception as e:
print(f"数据准备过程中出错:{str(e)}") # 提供更具体的错误信息
raise
def _calculate_rankings(self):
"""计算各科目的排名"""
# 使用配置的科目列表 self.SUBJECTS
subjects_to_rank = self.SUBJECTS + ['总分']
# 计算全校排名 (保持不变)
for subject in subjects_to_rank:
if subject in self.scores_df.columns:
self.scores_df[f'{subject}_全校名次'] = self.scores_df[subject].rank(ascending=False, method='min')
# 计算班级排名 (保持不变)
for subject in subjects_to_rank:
if subject in self.scores_df.columns:
self.scores_df[f'{subject}_班级名次'] = self.scores_df.groupby('评估班级')[subject].rank(ascending=False, method='min')
def _safe_max(self, values, default=0):
"""安全地计算最大值,如果列表为空则返回默认值"""
try:
# 过滤掉可能的NaN值或其他非数值类型
numeric_values = [v for v in values if isinstance(v, (int, float)) and not np.isnan(v)]
return max(numeric_values) if numeric_values else default
except Exception:
# 更广泛的异常捕获可能更安全
return default
def _save_detailed_data(self, class_data, class_num, subject, level, debug_info):
"""保存详细评估数据到文件"""
# 检查输出的科目名是否是内部的班主任标记,如果是,则替换为配置的输出标签
output_subject_name = self.class_team_label if subject == '__HeadTeacher__' else subject
try:
# 使用动态目录名和处理过的科目名
filename = os.path.join(self.detailed_data_dir, f'{output_subject_name}.txt')
# 以追加模式打开文件
with open(filename, 'a', encoding='utf-8') as f:
f.write(f"\n{'='*50}")
f.write(f"\n{class_num}班({level})详细数据 - 科目: {output_subject_name}\n") # 明确指出科目
# 添加所有考生基本信息
f.write(f"\n--- 所有考生基本信息 (总人数: {len(class_data)}) ---\n")
for _, student in class_data.iterrows():
# 如果是班团评估(用内部标记判断),只显示总分相关
if subject == '__HeadTeacher__':
f.write(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, 总分:{student['总分']}, 总分名次:{student['总分_全校名次']}\n")
else:
# 对于其他科目,显示科目相关信息 (保持不变)
subject_score = student.get(subject, 'N/A')
subject_rank = student.get(f'{subject}_全校名次', 'N/A')
total_score = student.get('总分', 'N/A')
total_rank = student.get('总分_全校名次', 'N/A')
f.write(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, {subject}:{subject_score}, {subject}名次:{subject_rank}, 总分:{total_score}, 总分名次:{total_rank}\n")
# --- 解析和写入 debug_info 的逻辑保持不变 ---
project_sections = []
assessment_data = []
assessment_calc = []
current_section = None
section_type = None # 可以是 'project', 'data', 'calc'
for line in debug_info:
line_stripped = line.strip()
# 跳过空行或不必要的行
if not line_stripped:
continue
# 检测部分标题
if line_stripped.startswith("--- 项目") and line_stripped.endswith("---"):
if current_section and section_type == 'project':
project_sections.append(current_section)
current_section = {"title": line_stripped, "description": [], "students": [], "summary": None}
section_type = 'project'
continue
elif line_stripped.startswith("--- 全面评估数据 ---"):
if current_section and section_type == 'project': # 完成最后一个项目
project_sections.append(current_section)
current_section = None
section_type = 'data'
continue
elif line_stripped.startswith("--- 全面评估积分计算 ---"):
section_type = 'calc'
continue
# 其他类型的分隔符可以视为部分的结束
elif line_stripped.startswith("---"):
if current_section and section_type == 'project':
project_sections.append(current_section)
current_section = None
section_type = None # 重置类型
continue
# 根据当前部分类型收集内容
if section_type == 'project' and current_section:
if "准考证" in line and "姓名" in line:
current_section["students"].append(line_stripped)
elif "线" in line or "范围" in line:
if not ("人数" in line and "得分" in line):
current_section["description"].append(line_stripped)
elif "项目" in line and "人数" in line and "得分" in line:
current_section["summary"] = line_stripped
elif section_type == 'data':
assessment_data.append(line_stripped)
elif section_type == 'calc':
assessment_calc.append(line_stripped)
# 确保最后一个项目(如果存在)被添加
if current_section and section_type == 'project':
project_sections.append(current_section)
# --- 结束修改后的解析逻辑 ---
# 写入记分项目考生详细信息部分
f.write("\n--- 记分项目考生详细信息 ---\n")
if not project_sections:
f.write("无记分项目信息\n")
else:
for section in project_sections:
# 写入项目标题
f.write(f"\n{section['title']}\n")
# 写入项目描述
for desc in section["description"]:
f.write(f"{desc}\n")
# 写入考生信息
if section["students"]:
f.write("考生详细信息:\n")
for student in section["students"]:
f.write(f"{student}\n")
else:
f.write("无符合条件的考生\n")
# 写入项目汇总
if section["summary"]:
f.write(f"{section['summary']}\n")
# 写入全面评估数据
f.write("\n--- 全面评估数据 ---\n")
if not assessment_data:
f.write("无全面评估数据\n")
else:
for line in assessment_data:
f.write(f"{line}\n")
# 写入全面评估积分计算
f.write("\n--- 全面评估积分计算 ---\n")
if not assessment_calc:
f.write("无全面评估积分计算数据\n")
else:
for line in assessment_calc:
f.write(f"{line}\n")
f.write('\n')
except Exception as e:
print(f"保存科目 '{output_subject_name}' 的详细数据时出错:{str(e)}") # 提示哪个科目出错
# 不再向上抛出,避免一个科目的错误中断整个流程,但会在最后提示
# raise
def _evaluate_first_level_class(self, class_data, class_num):
"""一层次班级评估"""
total_students = len(class_data)
debug_info = [] # 用于存储调试信息
# 创建一个标记数组,用于记录已计分的学生
counted_students = pd.Series(False, index=class_data.index)
# --- 重点评估 ---
# 项目1:总分1-80名(2分)
top_80_mask = class_data['总分_全校名次'] <= 80
top_80_students = class_data[top_80_mask]
top_80_count = len(top_80_students) # 使用len确保一致性
score_1 = top_80_count * 2
counted_students = counted_students | top_80_mask
debug_info.append(f"\n总人数:{total_students}") # 移到项目1之前
debug_info.append("\n--- 项目1:总分1-80名(2分)---")
for _, student in top_80_students.iterrows():
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, 总分:{student['总分']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目1人数:{top_80_count}, 得分:{score_1}")
# 项目2:总分81-160名(1.5分)
top_81_160_mask_scored = (class_data['总分_全校名次'] > 80) & (class_data['总分_全校名次'] <= 160) & ~counted_students
top_81_160_students_scored = class_data[top_81_160_mask_scored]
top_81_160_count = len(top_81_160_students_scored)
score_2 = top_81_160_count * 1.5
counted_students = counted_students | top_81_160_mask_scored
debug_info.append("\n--- 项目2:总分81-160名(1.5分)---")
debug_info.append(f"总分名次范围:81-160名")
for _, student in top_81_160_students_scored.iterrows():
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, 总分:{student['总分']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目2人数:{top_81_160_count}, 得分:{score_2}")
# 项目3:总分优秀(1分)
# 使用动态科目列表计算总分优秀线
excellent_threshold = sum([self.subject_thresholds[s]['excellent'] for s in self.SUBJECTS if s in self.subject_thresholds])
excellent_mask_base = class_data['总分'] >= excellent_threshold
excellent_mask_scored = excellent_mask_base & ~counted_students
excellent_students_scored = class_data[excellent_mask_scored]
excellent_count = len(excellent_students_scored)
score_3 = excellent_count * 1
counted_students = counted_students | excellent_mask_scored
debug_info.append("\n--- 项目3:总分优秀(1分)---")
debug_info.append(f"总分优秀线:{excellent_threshold}") # 修改名称
for _, student in excellent_students_scored.iterrows():
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, 总分:{student['总分']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目3人数:{excellent_count}, 得分:{score_3}")
# --- 全面评估 ---
excellent_rate = sum(excellent_mask_base) / total_students # 使用基础优秀标记
# 使用动态科目列表计算总分合格线
pass_threshold = sum([self.subject_thresholds[s]['pass'] for s in self.SUBJECTS if s in self.subject_thresholds])
pass_mask_base = class_data['总分'] >= pass_threshold # 使用基础合格标记
pass_rate = sum(pass_mask_base) / total_students # 使用基础合格标记
avg_score = class_data['总分'].mean()
# 使用动态科目列表计算总分低分线
low_threshold = sum([self.subject_thresholds[s]['low'] for s in self.SUBJECTS if s in self.subject_thresholds])
low_score_mask = class_data['总分'] < low_threshold
non_low_score_rate = 1 - (sum(low_score_mask) / total_students)
low_score_count = sum(low_score_mask)
debug_info.append("\n--- 全面评估数据 ---")
debug_info.append(f"总分优秀线:{excellent_threshold}") # 修改名称
debug_info.append(f"总分合格线:{pass_threshold}") # 修改名称
debug_info.append(f"总分低分线:{low_threshold}") # 修改名称
debug_info.append(f"优生数:{sum(excellent_mask_base)}") # 使用基础优秀标记
debug_info.append(f"优生率:{excellent_rate*100:03.2f}%")
debug_info.append(f"合格数:{sum(pass_mask_base)}") # 使用基础合格标记
debug_info.append(f"合格率:{pass_rate*100:03.2f}%")
debug_info.append(f"平均分:{avg_score:.2f}")
debug_info.append(f"低分数:{low_score_count}")
debug_info.append(f"非低分人数:{total_students - sum(low_score_mask)}")
debug_info.append(f"非低分率:{non_low_score_rate*100:03.2f}%")
# 计算相对值(与同层次班级比较)
first_level_data = self.scores_df[self.scores_df['层次_数值'] == 1]
if len(first_level_data) == 0:
max_excellent_rate = 0
max_avg_score = 0
max_non_low_score_rate = 0
else:
# 在计算groupby后的指标时,确保分母不为0
excellent_rates = [(sum(c['总分'] >= excellent_threshold) / len(c)) if len(c) > 0 else 0
for _, c in first_level_data.groupby('评估班级')]
max_excellent_rate = self._safe_max(excellent_rates)
avg_scores = [c['总分'].mean() if len(c) > 0 else 0 for _, c in first_level_data.groupby('评估班级')]
max_avg_score = self._safe_max(avg_scores)
non_low_score_rates = [(1 - sum(c['总分'] < low_threshold) / len(c)) if len(c) > 0 else 0
for _, c in first_level_data.groupby('评估班级')]
max_non_low_score_rate = self._safe_max(non_low_score_rates)
# 计算全面评估各项积分
excellent_rate_score = (excellent_rate/max_excellent_rate) * 40 if max_excellent_rate > 0 else 0
avg_score_score = (avg_score/max_avg_score) * 40 if max_avg_score > 0 else 0
non_low_rate_score = (non_low_score_rate/max_non_low_score_rate) * 20 if max_non_low_score_rate > 0 else 0
debug_info.append("\n--- 全面评估积分计算 ---")
debug_info.append(f"最高优生率:{max_excellent_rate*100:03.2f}%")
debug_info.append(f"最高平均分:{max_avg_score:.2f}")
debug_info.append(f"最高非低分率:{max_non_low_score_rate*100:03.2f}%")
debug_info.append(f"优生率积分:{excellent_rate_score if excellent_rate_score.is_integer() else excellent_rate_score:.2f}")
debug_info.append(f"平均分积分:{avg_score_score if avg_score_score.is_integer() else avg_score_score:.2f}")
debug_info.append(f"非低分率积分:{non_low_rate_score if non_low_rate_score.is_integer() else non_low_rate_score:.2f}")
# 保存详细数据
self._save_detailed_data(class_data, class_num, '__HeadTeacher__', '一层次', debug_info)
return {
'班号': class_num,
'班级人数': total_students,
'教师姓名': self.class_teacher_map.get(class_num, ''),
'层次': '一层次',
'项目1': top_80_count,
'积分1': score_1,
'项目2': top_81_160_count,
'积分2': score_2,
'项目3': excellent_count,
'积分3': score_3,
'小计1': score_1 + score_2 + score_3,
'优生数': sum(excellent_mask_base), # 使用基础优秀标记计数
'优生率': excellent_rate,
'优生积分': excellent_rate_score,
'平均分': avg_score,
'平均分积分': avg_score_score,
'低分数': low_score_count,
'非低分率': non_low_score_rate,
'非低分率积分': non_low_rate_score,
'小计2': excellent_rate_score + avg_score_score + non_low_rate_score,
'总积分': (score_1 + score_2 + score_3) + (excellent_rate_score + avg_score_score + non_low_rate_score),
'科目名': self.class_team_label # 使用配置的班团标签
}
def _evaluate_second_level_class(self, class_data, class_num):
"""二层次班级评估"""
rules = self.eval_rules['class_team']['level_2'] # 获取规则
total_students = len(class_data)
debug_info = []
# --- 重点评估 ---
# 项目1:总分优秀 (得分: focus_points_excellent)
points_excellent = rules['focus_points_excellent']
excellent_threshold = sum([self.subject_thresholds[s]['excellent'] for s in self.SUBJECTS if s in self.subject_thresholds])
excellent_mask = class_data['总分'] >= excellent_threshold
excellent_students = class_data[excellent_mask]
excellent_count = len(excellent_students)
score_1 = excellent_count * points_excellent
debug_info.append(f"\n总人数:{total_students}")
debug_info.append(f"\n--- 项目1:总分优秀({points_excellent}分)---")
debug_info.append(f"总分优秀线:{excellent_threshold}")
for _, student in excellent_students.iterrows():
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, 总分:{student['总分']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目1人数:{excellent_count}, 得分:{score_1}")
# 项目2:总分合格 (得分: focus_points_pass)
points_pass = rules['focus_points_pass']
pass_threshold = sum([self.subject_thresholds[s]['pass'] for s in self.SUBJECTS if s in self.subject_thresholds])
pass_mask_base = class_data['总分'] >= pass_threshold
pass_mask_scored = pass_mask_base & ~excellent_mask # 合格且非优秀
pass_students_scored = class_data[pass_mask_scored]
pass_count = len(pass_students_scored)
score_2 = pass_count * points_pass
debug_info.append(f"\n--- 项目2:总分合格({points_pass}分)---")
debug_info.append(f"总分合格线:{pass_threshold}")
for _, student in pass_students_scored.iterrows():
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, 总分:{student['总分']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目2人数:{pass_count}, 得分:{score_2}")
# 小计1 (未加权)
subtotal_1 = score_1 + score_2
# --- 全面评估 ---
excellent_rate = sum(excellent_mask) / total_students
pass_rate = sum(pass_mask_base) / total_students
avg_score = class_data['总分'].mean()
low_threshold = sum([self.subject_thresholds[s]['low'] for s in self.SUBJECTS if s in self.subject_thresholds])
low_score_mask = class_data['总分'] < low_threshold
non_low_score_rate = 1 - (sum(low_score_mask) / total_students)
low_score_count = sum(low_score_mask)
debug_info.append("\n--- 全面评估数据 ---")
debug_info.append(f"总分优秀线:{excellent_threshold}")
debug_info.append(f"总分合格线:{pass_threshold}")
debug_info.append(f"总分低分线:{low_threshold}")
debug_info.append(f"优生数:{sum(excellent_mask)}")
debug_info.append(f"优生率:{excellent_rate*100:03.2f}%")
debug_info.append(f"合格数:{sum(pass_mask_base)}")
debug_info.append(f"合格率:{pass_rate*100:03.2f}%")
debug_info.append(f"平均分:{avg_score:.2f}")
debug_info.append(f"低分数:{low_score_count}")
debug_info.append(f"非低分人数:{total_students - sum(low_score_mask)}")
debug_info.append(f"非低分率:{non_low_score_rate*100:03.2f}%")
second_level_data = self.scores_df[self.scores_df['层次_数值'] == 2]
if len(second_level_data) == 0:
max_excellent_rate, max_pass_rate, max_avg_score, max_non_low_score_rate = 0, 0, 0, 0
else:
excellent_rates = [(sum(c['总分'] >= excellent_threshold)/len(c)) if len(c) > 0 else 0 for _, c in second_level_data.groupby('评估班级')]
max_excellent_rate = self._safe_max(excellent_rates)
pass_rates = [(sum(c['总分'] >= pass_threshold)/len(c)) if len(c) > 0 else 0 for _, c in second_level_data.groupby('评估班级')]
max_pass_rate = self._safe_max(pass_rates)
avg_scores = [c['总分'].mean() if len(c) > 0 else 0 for _, c in second_level_data.groupby('评估班级')]
max_avg_score = self._safe_max(avg_scores)
non_low_rates = [(1 - sum(c['总分'] < low_threshold)/len(c)) if len(c) > 0 else 0 for _, c in second_level_data.groupby('评估班级')]
max_non_low_score_rate = self._safe_max(non_low_rates)
# 计算全面评估各项积分 (使用配置权重)
weight_excellent = rules['comp_weight_excellent']
weight_pass = rules['comp_weight_pass']
weight_avg = rules['comp_weight_avg']
weight_non_low = rules['comp_weight_non_low']
excellent_rate_score = (excellent_rate/max_excellent_rate) * weight_excellent if max_excellent_rate > 0 else 0
pass_rate_score = (pass_rate/max_pass_rate) * weight_pass if max_pass_rate > 0 else 0
avg_score_score = (avg_score/max_avg_score) * weight_avg if max_avg_score > 0 else 0
non_low_rate_score = (non_low_score_rate/max_non_low_score_rate) * weight_non_low if max_non_low_score_rate > 0 else 0
debug_info.append("\n--- 全面评估积分计算 ---")
debug_info.append(f"最高优生率:{max_excellent_rate*100:03.2f}% (权重: {weight_excellent})")
debug_info.append(f"最高合格率:{max_pass_rate*100:03.2f}% (权重: {weight_pass})")
debug_info.append(f"最高平均分:{max_avg_score:.2f} (权重: {weight_avg})")
debug_info.append(f"最高非低分率:{max_non_low_score_rate*100:03.2f}% (权重: {weight_non_low})")
debug_info.append(f"优生率积分:{excellent_rate_score if excellent_rate_score.is_integer() else excellent_rate_score:.2f}")
debug_info.append(f"合格率积分:{pass_rate_score if pass_rate_score.is_integer() else pass_rate_score:.2f}")
debug_info.append(f"平均分积分:{avg_score_score if avg_score_score.is_integer() else avg_score_score:.2f}")
debug_info.append(f"非低分率积分:{non_low_rate_score if non_low_rate_score.is_integer() else non_low_rate_score:.2f}")
# !! 修正: 小计2直接累加,不乘以权重
subtotal_2 = excellent_rate_score + pass_rate_score + avg_score_score + non_low_rate_score
self._save_detailed_data(class_data, class_num, '__HeadTeacher__', '二层次', debug_info)
# !! 修正: 总积分直接累加 小计1 和 小计2
total_score_final = subtotal_1 + subtotal_2
return {
'班号': class_num,
'班级人数': total_students,
'教师姓名': self.class_teacher_map.get(class_num, ''),
'层次': '二层次',
'项目1': excellent_count,
'积分1': score_1,
'项目2': pass_count,
'积分2': score_2,
'小计1': subtotal_1, # 未加权小计1
'优生数': sum(excellent_mask),
'优生率': excellent_rate,
'优生积分': excellent_rate_score, # 未加权
'合格数': sum(pass_mask_base),
'合格率': pass_rate,
'合格率积分': pass_rate_score, # 未加权
'平均分': avg_score,
'平均分积分': avg_score_score, # 未加权
'低分数': low_score_count,
'非低分率': non_low_score_rate,
'非低分率积分': non_low_rate_score, # 未加权
'小计2': subtotal_2, # 未加权小计2
'总积分': total_score_final, # 修正后的总积分 (小计1+小计2)
'科目名': self.class_team_label
}
def _evaluate_first_level_teacher(self, class_data, subject, teacher_row):
"""一层次教师评估"""
total_students = len(class_data)
debug_info = [] # 用于存储调试信息
debug_info.append(f"\n总人数:{total_students}") # 移到项目1之前
# 检查科目是否存在于阈值配置中,如果不存在则跳过评估
if subject not in self.subject_thresholds:
print(f"警告:科目 '{subject}' 没有定义阈值,跳过教师 {teacher_row['教师姓名']} ({teacher_row['班号']}班) 的评估。")
# 可以在这里返回一个空的或表示跳过的字典,或者根据需要处理
# 为保持一致性,我们可能需要返回一个包含基本信息的字典,但评估值为0或NaN
return {
'班号': teacher_row['班号'], '班级人数': total_students, '教师姓名': teacher_row['教师姓名'], '层次': '一层次',
'项目1': 0, '积分1': 0, '项目2': 0, '积分2': 0, '项目3': 0, '积分3': 0, '项目4': 0, '积分4': 0,
'小计1': 0, '优生数': 0, '优生率': 0, '优生积分': 0, '平均分': np.nan, '平均分积分': 0,
'低分数': 0, '非低分率': 0, '非低分率积分': 0, '小计2': 0, '总积分': 0, '科目名': subject
}
# 创建一个标记数组,用于记录已计分的学生
counted_students = pd.Series(False, index=class_data.index)
# --- 重点评估 ---
# 项目1:单科1-80名且总分进入1-80名(2分)
subject_top_80_mask = class_data[f'{subject}_全校名次'] <= 80
total_top_80_mask = class_data['总分_全校名次'] <= 80
project1_mask_scored = subject_top_80_mask & total_top_80_mask
project1_students_scored = class_data[project1_mask_scored]
top_80_count = len(project1_students_scored) # 统计得分人数
score_1 = top_80_count * 2
counted_students = counted_students | project1_mask_scored # 更新计分标记
# --- 移除 DEBUG ---
# print(f" DEBUG (一层次教师): T={teacher_row['教师姓名']}, C={teacher_row['班号']}, S={subject} - P1(单科<=80&总分<=80): Count={top_80_count}, Score={score_1}")
# ------------------
debug_info.append("\n--- 项目1:单科1-80名且总分进入1-80名(2分)---")
for _, student in project1_students_scored.iterrows(): # 只记录得分学生
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, {subject}:{student[subject]}, {subject}名次:{student[f'{subject}_全校名次']}, 总分:{student['总分']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目1人数:{top_80_count}, 得分:{score_1}")
# 项目2:单科81-160名且总分进入81-160名(1.5分)
subject_81_160_mask = (class_data[f'{subject}_全校名次'] > 80) & (class_data[f'{subject}_全校名次'] <= 160)
total_81_160_mask = (class_data['总分_全校名次'] > 80) & (class_data['总分_全校名次'] <= 160)
project2_mask_scored = subject_81_160_mask & total_81_160_mask & ~counted_students
project2_students_scored = class_data[project2_mask_scored]
top_160_count = len(project2_students_scored)
score_2 = top_160_count * 1.5
counted_students = counted_students | project2_mask_scored
# --- 移除 DEBUG ---
# print(f" DEBUG (一层次教师): T={teacher_row['教师姓名']}, C={teacher_row['班号']}, S={subject} - P2(单科81-160&总分81-160): Count={top_160_count}, Score={score_2}")
# ------------------
debug_info.append("\n--- 项目2:单科81-160名且总分进入81-160名(1.5分)---")
debug_info.append(f"{subject}名次范围:81-160名")
debug_info.append(f"总分名次范围:81-160名")
for _, student in project2_students_scored.iterrows(): # 只记录得分学生
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, {subject}:{student[subject]}, {subject}名次:{student[f'{subject}_全校名次']}, 总分:{student['总分']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目2人数:{top_160_count}, 得分:{score_2}")
# 项目3:单科优秀且总分优秀(1分)
subject_excellent_mask_base = class_data[subject] >= self.subject_thresholds[subject]['excellent']
# 使用动态科目列表计算总分优秀线
total_excellent_threshold = sum([self.subject_thresholds[s]['excellent'] for s in self.SUBJECTS if s in self.subject_thresholds])
total_excellent_mask = class_data['总分'] >= total_excellent_threshold
project3_mask_scored = subject_excellent_mask_base & total_excellent_mask & ~counted_students
project3_students_scored = class_data[project3_mask_scored]
excellent_count = len(project3_students_scored)
score_3 = excellent_count * 1
counted_students = counted_students | project3_mask_scored
# --- 移除 DEBUG ---
# print(f" DEBUG (一层次教师): T={teacher_row['教师姓名']}, C={teacher_row['班号']}, S={subject} - P3(单科优&总分优): Count={excellent_count}, Score={score_3}")
# ------------------
debug_info.append("\n--- 项目3:单科优秀且总分优秀(1分)---")
debug_info.append(f"{subject}优秀线:{self.subject_thresholds[subject]['excellent']}")
debug_info.append(f"总分优秀线:{total_excellent_threshold}")
for _, student in project3_students_scored.iterrows(): # 只记录得分学生
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, {subject}:{student[subject]}, 总分:{student['总分']}, {subject}名次:{student[f'{subject}_全校名次']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目3人数:{excellent_count}, 得分:{score_3}")
# 项目4:单科优秀且总分合格(0.5分)
# 使用动态科目列表计算总分合格线
total_pass_threshold = sum([self.subject_thresholds[s]['pass'] for s in self.SUBJECTS if s in self.subject_thresholds])
total_pass_mask = class_data['总分'] >= total_pass_threshold
project4_mask_scored = subject_excellent_mask_base & total_pass_mask & ~counted_students
project4_students_scored = class_data[project4_mask_scored]
excellent_pass_count = len(project4_students_scored)
score_4 = excellent_pass_count * 0.5
counted_students = counted_students | project4_mask_scored
# --- 移除 DEBUG ---
# print(f" DEBUG (一层次教师): T={teacher_row['教师姓名']}, C={teacher_row['班号']}, S={subject} - P4(单科优&总分合格): Count={excellent_pass_count}, Score={score_4}")
# ------------------
debug_info.append("\n--- 项目4:单科优秀且总分合格(0.5分)---")
debug_info.append(f"{subject}优秀线:{self.subject_thresholds[subject]['excellent']}")
debug_info.append(f"总分合格线:{total_pass_threshold}")
for _, student in project4_students_scored.iterrows(): # 只记录得分学生
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, {subject}:{student[subject]}, 总分:{student['总分']}, {subject}名次:{student[f'{subject}_全校名次']}, 总分名次:{student['总分_全校名次']}")
debug_info.append(f"项目4人数:{excellent_pass_count}, 得分:{score_4}")
# 修正:小计1 = 以上四项得分之和 (不加权)
subtotal_1 = score_1 + score_2 + score_3 + score_4
# --- 全面评估 ---
subject_excellent_rate = sum(subject_excellent_mask_base) / total_students # 使用基础优秀标记
subject_avg_score = class_data[subject].mean()
subject_low_mask = class_data[subject] < self.subject_thresholds[subject]['low']
subject_non_low_rate = 1 - (sum(subject_low_mask) / total_students)
subject_low_count = sum(subject_low_mask)
debug_info.append("\n--- 全面评估数据 ---")
debug_info.append(f"{subject}优秀线:{self.subject_thresholds[subject]['excellent']}")
debug_info.append(f"{subject}合格线:{self.subject_thresholds[subject]['pass']}")
debug_info.append(f"{subject}低分线:{self.subject_thresholds[subject]['low']}")
debug_info.append(f"优生数:{sum(subject_excellent_mask_base)}") # 使用基础优秀标记计数
debug_info.append(f"优生率:{subject_excellent_rate*100:03.2f}%")
debug_info.append(f"平均分:{subject_avg_score:.2f}")
debug_info.append(f"低分数:{subject_low_count}")
debug_info.append(f"非低分人数:{total_students - sum(subject_low_mask)}")
debug_info.append(f"非低分率:{subject_non_low_rate*100:03.2f}%")
# 计算相对值(与同层次班级比较)
first_level_data = self.scores_df[self.scores_df['层次_数值'] == 1]
if len(first_level_data) == 0:
max_excellent_rate = 0
max_avg_score = 0
max_non_low_rate = 0
else:
excellent_rates = [(sum(c[subject] >= self.subject_thresholds[subject]['excellent'])/len(c)) if len(c) > 0 else 0
for _, c in first_level_data.groupby('评估班级')]
max_excellent_rate = self._safe_max(excellent_rates)
avg_scores = [c[subject].mean() if len(c) > 0 else 0 for _, c in first_level_data.groupby('评估班级')]
max_avg_score = self._safe_max(avg_scores)
non_low_rates = [(1 - sum(c[subject] < self.subject_thresholds[subject]['low'])/len(c)) if len(c) > 0 else 0
for _, c in first_level_data.groupby('评估班级')]
max_non_low_rate = self._safe_max(non_low_rates)
# 计算全面评估各项积分
excellent_rate_score = (subject_excellent_rate/max_excellent_rate) * 40 if max_excellent_rate > 0 else 0
avg_score_score = (subject_avg_score/max_avg_score) * 40 if max_avg_score > 0 else 0
non_low_rate_score = (subject_non_low_rate/max_non_low_rate) * 20 if max_non_low_rate > 0 else 0
debug_info.append("\n--- 全面评估积分计算 ---")
debug_info.append(f"最高优生率:{max_excellent_rate*100:03.2f}%")
debug_info.append(f"最高平均分:{max_avg_score:.2f}")
debug_info.append(f"最高非低分率:{max_non_low_rate*100:03.2f}%")
debug_info.append(f"优生率积分:{excellent_rate_score if excellent_rate_score.is_integer() else excellent_rate_score:.2f}")
debug_info.append(f"平均分积分:{avg_score_score if avg_score_score.is_integer() else avg_score_score:.2f}")
debug_info.append(f"非低分率积分:{non_low_rate_score if non_low_rate_score.is_integer() else non_low_rate_score:.2f}")
# 修正:小计2 = 以上三项得分之和 (不加权)
subtotal_2 = excellent_rate_score + avg_score_score + non_low_rate_score
# 保存详细数据
self._save_detailed_data(class_data, teacher_row['班号'], subject, '一层次', debug_info)
# 修正:总分 = (小计1 * 0.3) + (小计2 * 0.7)
total_score_final = (subtotal_1 * 0.3) + (subtotal_2 * 0.7)
return {
'班号': teacher_row['班号'],
'班级人数': total_students,
'教师姓名': teacher_row['教师姓名'],
'层次': '一层次',
'项目1': top_80_count,
'积分1': score_1,
'项目2': top_160_count,
'积分2': score_2,
'项目3': excellent_count,
'积分3': score_3,
'项目4': excellent_pass_count,
'积分4': score_4,
'小计1': subtotal_1, # 返回未加权的小计1
'优生数': sum(subject_excellent_mask_base),
'优生率': subject_excellent_rate,
'优生积分': excellent_rate_score,
'平均分': subject_avg_score,
'平均分积分': avg_score_score,
'低分数': subject_low_count,
'非低分率': subject_non_low_rate,
'非低分率积分': non_low_rate_score,
'小计2': subtotal_2, # 返回未加权的小计2
'总积分': total_score_final, # 返回最终加权总分
'科目名': subject
}
def _evaluate_second_level_teacher(self, class_data, subject, teacher_row):
"""二层次教师评估"""
total_students = len(class_data)
debug_info = [] # 用于存储调试信息
debug_info.append(f"\n总人数:{total_students}") # 移到项目1之前
# 检查科目是否存在于阈值配置中
if subject not in self.subject_thresholds:
print(f"警告:科目 '{subject}' 没有定义阈值,跳过教师 {teacher_row['教师姓名']} ({teacher_row['班号']}班) 的评估。")
return {
'班号': teacher_row['班号'], '班级人数': total_students, '教师姓名': teacher_row['教师姓名'], '层次': '二层次',
'项目1': 0, '积分1': 0, '项目2': 0, '积分2': 0, '小计1': 0,
'优生数': 0, '优生率': 0, '优生积分': 0, '合格数': 0, '合格率': 0, '合格率积分': 0,
'平均分': np.nan, '平均分积分': 0, '低分数': 0, '非低分率': 0, '非低分率积分': 0,
'小计2': 0, '总积分': 0, '科目名': subject
}
# --- 重点评估 ---
# 项目1:单科优秀且总分合格
subject_excellent_mask_base = class_data[subject] >= self.subject_thresholds[subject]['excellent']
# 使用动态科目列表计算总分合格线
total_pass_threshold = sum([self.subject_thresholds[s]['pass'] for s in self.SUBJECTS if s in self.subject_thresholds])
total_pass_mask = class_data['总分'] >= total_pass_threshold
project1_mask_scored = subject_excellent_mask_base & total_pass_mask
project1_students_scored = class_data[project1_mask_scored]
excellent_pass_count = len(project1_students_scored) # 统计得分人数
score_1 = excellent_pass_count * 2
# --- 移除 DEBUG ---
# if teacher_row['班号'] == 10 and subject == '历史':
# print(f" DEBUG (二层次教师): T={teacher_row['教师姓名']}, C={teacher_row['班号']}, S={subject} - P1(单科优&总分合格): Count={excellent_pass_count}, Score={score_1}")
# # ---------------------------
debug_info.append("\n--- 项目1:单科优秀且总分合格(2分)---")
debug_info.append(f"{subject}优秀线:{self.subject_thresholds[subject]['excellent']}")
debug_info.append(f"总分合格线:{total_pass_threshold}")
for _, student in project1_students_scored.iterrows(): # 只记录得分学生
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, {subject}:{student[subject]}, 总分:{student['总分']}")
debug_info.append(f"项目1人数:{excellent_pass_count}, 得分:{score_1}")
# 项目2:单科合格且总分合格
subject_pass_mask_base = class_data[subject] >= self.subject_thresholds[subject]['pass']
# 计算实际得分的学生 (合格且非优秀)
project2_mask_scored = subject_pass_mask_base & total_pass_mask & ~subject_excellent_mask_base
project2_students_scored = class_data[project2_mask_scored]
pass_count = len(project2_students_scored) # 统计得分人数
score_2 = pass_count * 1.5
# --- 移除 DEBUG ---
# if teacher_row['班号'] == 10 and subject == '历史':
# print(f" DEBUG (二层次教师): T={teacher_row['教师姓名']}, C={teacher_row['班号']}, S={subject} - P2(单科合格&总分合格): Count={pass_count}, Score={score_2}")
# # ---------------------------
debug_info.append("\n--- 项目2:单科合格且总分合格(1.5分)---")
debug_info.append(f"{subject}合格线:{self.subject_thresholds[subject]['pass']}")
debug_info.append(f"总分合格线:{total_pass_threshold}")
for _, student in project2_students_scored.iterrows(): # 只记录得分学生
debug_info.append(f"准考证:{student['准考证']}, 姓名:{student['姓名']}, {subject}:{student[subject]}, 总分:{student['总分']}")
debug_info.append(f"项目2人数:{pass_count}, 得分:{score_2}")
# 修正:计算未加权的小计1
unweighted_subtotal_1 = score_1 + score_2
# --- 全面评估 ---
subject_excellent_rate = sum(subject_excellent_mask_base) / total_students # 使用基础优秀标记
subject_pass_rate = sum(subject_pass_mask_base) / total_students # 使用基础合格标记
subject_avg_score = class_data[subject].mean()
subject_low_mask = class_data[subject] < self.subject_thresholds[subject]['low']
subject_non_low_rate = 1 - (sum(subject_low_mask) / total_students)
subject_low_count = sum(subject_low_mask)
debug_info.append("\n--- 全面评估数据 ---")
debug_info.append(f"{subject}优秀线:{self.subject_thresholds[subject]['excellent']}")
debug_info.append(f"{subject}合格线:{self.subject_thresholds[subject]['pass']}")
debug_info.append(f"{subject}低分线:{self.subject_thresholds[subject]['low']}")
debug_info.append(f"优生数:{sum(subject_excellent_mask_base)}") # 使用基础优秀标记计数
debug_info.append(f"优生率:{subject_excellent_rate*100:03.2f}%")
debug_info.append(f"合格数:{sum(subject_pass_mask_base)}") # 使用基础合格标记计数
debug_info.append(f"合格率:{subject_pass_rate*100:03.2f}%")
debug_info.append(f"平均分:{subject_avg_score:.2f}")
debug_info.append(f"低分数:{subject_low_count}")
debug_info.append(f"非低分人数:{total_students - sum(subject_low_mask)}")
debug_info.append(f"非低分率:{subject_non_low_rate*100:03.2f}%")
# 计算相对值(与同层次班级比较)
second_level_data = self.scores_df[self.scores_df['层次_数值'] == 2]
if len(second_level_data) == 0:
max_excellent_rate = 0
max_pass_rate = 0
max_avg_score = 0
max_non_low_rate = 0
else:
excellent_rates = [(sum(c[subject] >= self.subject_thresholds[subject]['excellent'])/len(c)) if len(c) > 0 else 0
for _, c in second_level_data.groupby('评估班级')]
max_excellent_rate = self._safe_max(excellent_rates)
pass_rates = [(sum(c[subject] >= self.subject_thresholds[subject]['pass'])/len(c)) if len(c) > 0 else 0
for _, c in second_level_data.groupby('评估班级')]
max_pass_rate = self._safe_max(pass_rates)
avg_scores = [c[subject].mean() if len(c) > 0 else 0 for _, c in second_level_data.groupby('评估班级')]
max_avg_score = self._safe_max(avg_scores)
non_low_rates = [(1 - sum(c[subject] < self.subject_thresholds[subject]['low'])/len(c)) if len(c) > 0 else 0
for _, c in second_level_data.groupby('评估班级')]
max_non_low_rate = self._safe_max(non_low_rates)
# 计算全面评估各项积分
excellent_rate_score = (subject_excellent_rate/max_excellent_rate) * 5 if max_excellent_rate > 0 else 0
pass_rate_score = (subject_pass_rate/max_pass_rate) * 30 if max_pass_rate > 0 else 0
avg_score_score = (subject_avg_score/max_avg_score) * 30 if max_avg_score > 0 else 0
non_low_rate_score = (subject_non_low_rate/max_non_low_rate) * 35 if max_non_low_rate > 0 else 0
debug_info.append("\n--- 全面评估积分计算 ---")
debug_info.append(f"最高优生率:{max_excellent_rate*100:03.2f}%")
debug_info.append(f"最高合格率:{max_pass_rate*100:03.2f}%")
debug_info.append(f"最高平均分:{max_avg_score:.2f}")
debug_info.append(f"最高非低分率:{max_non_low_rate*100:03.2f}%")
debug_info.append(f"优生率积分:{excellent_rate_score if excellent_rate_score.is_integer() else excellent_rate_score:.2f}")
debug_info.append(f"合格率积分:{pass_rate_score if pass_rate_score.is_integer() else pass_rate_score:.2f}")
debug_info.append(f"平均分积分:{avg_score_score if avg_score_score.is_integer() else avg_score_score:.2f}")
debug_info.append(f"非低分率积分:{non_low_rate_score if non_low_rate_score.is_integer() else non_low_rate_score:.2f}")
# 修正:计算未加权的小计2
unweighted_subtotal_2 = excellent_rate_score + pass_rate_score + avg_score_score + non_low_rate_score
# 保存详细数据
self._save_detailed_data(class_data, teacher_row['班号'], subject, '二层次', debug_info)
# 修正:总分 = (未加权小计1 * 0.2) + (未加权小计2 * 0.8)
total_score_final = (unweighted_subtotal_1 * 0.2) + (unweighted_subtotal_2 * 0.8)
# --- 移除临时调试打印 ---
# if teacher_row['班号'] == 10 and subject == '历史':
# # 打印未加权的小计和最终总分
# print(f"DEBUG (二层次教师): 10班 历史 - 返回前的值: 未加权小计1={unweighted_subtotal_1}, 未加权小计2={unweighted_subtotal_2}, 总积分={total_score_final}")
# # --- 结束临时调试 ---
return {
'班号': teacher_row['班号'],
'班级人数': total_students,
'教师姓名': teacher_row['教师姓名'],
'层次': '二层次',
'项目1': excellent_pass_count,
'积分1': score_1,
'项目2': pass_count,
'积分2': score_2,
'小计1': unweighted_subtotal_1, # 返回未加权的小计1
'优生数': sum(subject_excellent_mask_base), # 使用基础优秀标记计数
'优生率': subject_excellent_rate,
'优生积分': excellent_rate_score,
'合格数': sum(subject_pass_mask_base), # 使用基础合格标记计数
'合格率': subject_pass_rate,
'合格率积分': pass_rate_score,
'平均分': subject_avg_score,
'平均分积分': avg_score_score,
'低分数': subject_low_count,
'非低分率': subject_non_low_rate,
'非低分率积分': non_low_rate_score,
'小计2': unweighted_subtotal_2, # 返回未加权的小计2
'总积分': total_score_final, # 最终加权总分
'科目名': subject
}
def evaluate_class_team(self):
"""班团评估"""
results = []
for class_num in sorted(self.scores_df['评估班级'].unique()):
class_data = self.scores_df[self.scores_df['评估班级'] == class_num].copy()
if class_data.empty:
continue
level = class_data['层次_数值'].iloc[0]
if level == 1:
# 调用班级评估函数,它内部会使用配置的标签返回结果
result = self._evaluate_first_level_class(class_data, class_num)
else:
result = self._evaluate_second_level_class(class_data, class_num)
results.append(result)
return pd.DataFrame(results)
def evaluate_teachers(self):
"""教师评估"""
results = []
# 过滤掉班主任记录 (使用内部标记),只评估学科教师
subject_teachers = self.teachers_df[self.teachers_df['学科'] != '__HeadTeacher__']
for _, teacher_row in subject_teachers.iterrows():
subject = teacher_row['学科']
class_num = teacher_row['班号']
teacher_class_data = self.scores_df[self.scores_df['评估班级'] == class_num].copy()
if teacher_class_data.empty:
print(f"警告:班级 {class_num} 在成绩表中没有数据,跳过教师 {teacher_row['教师姓名']} ({subject}) 的评估。")
continue
level = teacher_class_data['层次_数值'].iloc[0]
if subject not in teacher_class_data.columns:
print(f"警告:科目 '{subject}' 不在班级 {class_num} 的成绩数据中,跳过教师 {teacher_row['教师姓名']} 的评估。")
continue
# 检查科目阈值是否存在 (教师评估函数内部已有此检查)
# if subject not in self.subject_thresholds: ...
if level == 1:
result = self._evaluate_first_level_teacher(teacher_class_data, subject, teacher_row)
else:
result = self._evaluate_second_level_teacher(teacher_class_data, subject, teacher_row)
if result:
results.append(result)
return pd.DataFrame(results)
def save_results(self, exam_name):
"""保存评估结果"""
output_path = None
try:
# 清空详细数据目录下的 .txt 文件
if os.path.exists(self.detailed_data_dir):
print(f"清空目录:{self.detailed_data_dir}")
for filename in os.listdir(self.detailed_data_dir):
if filename.endswith(".txt"):
file_path = os.path.join(self.detailed_data_dir, filename)
try:
os.remove(file_path)
print(f" 已删除: {filename}")
except Exception as e:
print(f"无法删除文件 {file_path}: {e}")
else:
# 如果目录不存在,则创建它
print(f"创建目录:{self.detailed_data_dir}")
os.makedirs(self.detailed_data_dir)
# --- 计算评估 ---
print("正在进行班团评估...")
class_results = self.evaluate_class_team()
print("正在进行教师评估...")
teacher_results = self.evaluate_teachers()
# ------------------
# 检查是否有评估结果
if class_results.empty and teacher_results.empty:
print("警告:没有生成任何评估结果。请检查输入文件和配置。")
return None # 如果都没有结果,则不继续执行保存
elif class_results.empty:
print("警告:班团评估未生成结果。")
elif teacher_results.empty:
print("警告:教师评估未生成结果。")
# --- 合并和处理结果 ---
# 移除错误的总积分重新计算
# if not class_results.empty:
# class_results['总积分'] = class_results.apply(lambda row: row.get('小计1', 0) + row.get('小计2', 0), axis=1)
# if not teacher_results.empty:
# teacher_results['总积分'] = teacher_results.apply(lambda row: row.get('小计1', 0) + row.get('小计2', 0), axis=1)
# 合并结果 (处理可能为空的DataFrame)
results_list = []
if not class_results.empty:
results_list.append(class_results)
if not teacher_results.empty:
results_list.append(teacher_results)
if not results_list:
print("错误:无法合并结果,因为两个评估结果都为空。")
return None
results = pd.concat(results_list, ignore_index=True)
# 添加名次列 (基于 '总积分' 列)
if '总积分' in results.columns:
results['名次'] = results.groupby(['科目名', '层次'])['总积分'].rank(method='min', ascending=False).astype(int)
else:
results['名次'] = np.nan # 如果没有总积分,名次无意义
# 创建科目名的排序顺序 (使用配置的班团标签和科目列表)
# 注意:这里使用 self.class_team_label
subject_order = [self.class_team_label] + sorted(list(self.SUBJECTS))
present_subjects = results['科目名'].unique()
final_subject_order = [s for s in subject_order if s in present_subjects]
for s in present_subjects:
if s not in final_subject_order:
final_subject_order.append(s)
results['科目名排序'] = pd.Categorical(results['科目名'], categories=final_subject_order, ordered=True)
# --- 格式化 ---
def format_number(x):
if pd.isna(x):
return x
if isinstance(x, (int, float)):
# 检查是否非常接近整数
if np.isclose(x, round(x)):
return int(round(x))
# 修改:保留一位小数
return round(x, 1)
return x
def format_percentage(x):
if pd.isna(x):
return x
if isinstance(x, (int, float)):
# 确保百分比格式为048.39%
return f"{x*100:03.2f}%"
return x # 如果不是数字,直接返回
# 应用格式化
format_cols = results.select_dtypes(include=[np.number]).columns
percent_cols = ['优生率', '合格率', '非低分率']
for col in format_cols:
if col in percent_cols:
results[col] = results[col].apply(format_percentage)
elif col not in ['班号', '班级人数', '名次']: # 这些通常是整数
results[col] = results[col].apply(format_number)
# ----------------
# --- 排序 ---
# 按要求排序:科目名、层次、班号 (逻辑保持不变)
sort_columns = ['科目名排序', '层次']
if '班号' in results.columns:
sort_columns.append('班号')
results = results.sort_values(by=sort_columns, ascending=[True] * len(sort_columns))
results = results.drop('科目名排序', axis=1)
# -------------
# --- 重新排列列顺序 (使用 '总积分' 替换 '总分') ---
expected_columns_order = [
'班号', '班级人数', '名次', '教师姓名',
'总积分', # 使用正确的 '总积分' 列
'项目1', '积分1', '项目2', '积分2', '项目3', '积分3', '项目4', '积分4', '小计1', # 未加权
'优生数', '优生率', '优生积分',
'合格数', '合格率', '合格率积分',
'平均分', '平均分积分',
'低分数', '非低分率', '非低分率积分', '小计2', # 未加权
'科目名', '层次'
]
# 获取实际存在的列,并按期望顺序排列
available_columns = [col for col in expected_columns_order if col in results.columns]
results = results[available_columns]
# ---------------------
# --- 新增:文件复制、命名、写入标题和数据 ---
# 检查合并后的 results 是否为空
if results.empty:
print("警告:评估结果为空,无法生成输出文件。")
return None
# 1. 生成时间戳和输出文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_filename = f"{exam_name}_{timestamp}.xlsx"
# 将输出文件路径设置在详细数据目录内
output_path = os.path.join(self.detailed_data_dir, output_filename)
# 2. 复制模板文件 (保持不变)
print(f"复制 {self.grade} 年级模板文件 '{self.template_file}' 到 '{output_path}'")
shutil.copy(self.template_file, output_path)
# 3. 使用 openpyxl 将结果 DataFrame 数据写入复制的模板(从第4行开始,保留模板原有内容)
print(f"将评估结果数据写入文件 '{output_path}' (从第4行开始,保留模板表头)...")
workbook = load_workbook(output_path)
sheet = workbook.active
# 新增:在第一行A1单元格写入考试名称
print(f"在 A1 单元格写入考试名称: {exam_name}")
sheet['A1'] = exam_name + " 评估结果"
# 定义数据写入的起始行 (Excel行号,1-based)
start_excel_row = 4
# 将格式化后的 DataFrame 数据转换为适合 openpyxl 写入的列表
# 注意:此时 results DataFrame 中的数据已经是格式化过的字符串或数字
data_to_write = results.values.tolist()
# 逐行逐单元格写入数据
for r_idx, row_data in enumerate(data_to_write):
# 计算当前要写入的 Excel 行号
current_excel_row = start_excel_row + r_idx
for c_idx, value in enumerate(row_data):
# 计算当前要写入的 Excel 列号 (1-based)
current_excel_col = c_idx + 1
# 直接写入值 (因为之前已经格式化过)
sheet.cell(row=current_excel_row, column=current_excel_col, value=value)
# 保存对工作簿的修改
workbook.save(output_path)
# --------------------------------------------------
print(f"评估结果已成功写入复制的模板: {output_path}")
return output_path # 返回成功生成的文件路径
except FileNotFoundError as e: # 特别处理文件复制时的错误
print(f"文件操作错误:{str(e)}")
print(f"请确保 {self.grade} 年级的模板文件 '{self.template_file}' (来自配置) 存在。")
return None # 返回 None 表示失败
except KeyError as e:
print(f"保存结果时出现配置或列名错误:'{e}'。可能是科目名排序或列重排时出错。")
print(f"请检查配置文件 ({CONFIG_FILE}) 和评估结果 DataFrame 的列名。")
return None
except Exception as e:
print(f"保存结果到复制模板时出错:{str(e)}")
import traceback
traceback.print_exc()
# 可以在这里决定是否需要回退到旧的保存方式,或者直接返回失败
# print(f"尝试保存到默认位置...") # 移除旧的回退逻辑
return None # 返回 None 表示失败
def main():
global CONFIG # 声明要修改全局 CONFIG 变量
try:
# --- 程序启动时首先加载配置 ---
load_config()
# -----------------------------
# 获取配置文件中支持的年级列表 (并确保是字符串)
supported_grades = [str(k) for k in CONFIG['grades'].keys()]
# 获取用户输入的年级
while True:
try:
grade_input = input(f"请输入要评估的年级 ({', '.join(supported_grades)}):")
# 不需要转换成 int,因为配置查找使用字符串 <- 旧注释,现在需要转换
if grade_input in supported_grades:
grade = int(grade_input) # 转换回整数
break
else:
print(f"错误:不支持的年级 '{grade_input}'。请输入 {', '.join(supported_grades)} 中的一个。")
except ValueError: # 添加对 int 转换失败的处理
print(f"错误:输入的 '{grade_input}' 不是有效的数字年级。")
except Exception as e:
print(f"输入年级时发生错误: {e}")
# 获取考试名称 (保持不变)
exam_name = input("请输入本次考试的名称:")
if not exam_name:
# 注意:grade 现在是整数,如果需要拼接,需要转回字符串
exam_name = f"{str(grade)}年级评估"
print(f"未输入考试名称,将使用默认名称:'{exam_name}'")
print(f"开始 {grade} 年级 '{exam_name}' 评估程序...")
# 使用整数年级初始化
evaluator = ScoreEvaluator(grade)
print("正在准备数据...")
evaluator.prepare_data()
print("正在保存评估结果...")
output_file_path = evaluator.save_results(exam_name)
# 结果处理和输出 (保持不变)
if output_file_path:
print("-" * 30)
print(f"{grade} 年级 '{exam_name}' 评估完成!")
print(f"评估结果已保存到文件: {output_file_path}")
print(f"详细数据日志已保存到目录: {evaluator.detailed_data_dir}")
print("-" * 30)
else:
print("-" * 30)
print(f"{grade} 年级 '{exam_name}' 评估过程中发生错误,未能成功生成结果文件。")
print("请检查上面的错误信息。")
print("-" * 30)
except (FileNotFoundError, ValueError, yaml.YAMLError, RuntimeError, ImportError) as e:
# 捕获配置文件加载、数据准备、库缺失等特定错误
print(f"程序运行失败:{str(e)}")
# 可以在这里添加更详细的指引,例如检查配置文件路径、内容、Excel文件等
print("程序终止")
except Exception as e:
print(f"发生未知错误:{str(e)}")
import traceback
traceback.print_exc()
print("程序终止")
if __name__ == '__main__':
main()