Files
JITToolBox/module/achievement/excel.py
2025-06-21 12:23:06 +08:00

596 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import datetime
import traceback
from typing import Optional, Callable
import openpyxl
from openpyxl.utils import get_column_letter, column_index_from_string
from openpyxl.workbook.workbook import Workbook
from openpyxl.worksheet.worksheet import Worksheet
from packaging import version
from module import LOGLEVEL, COMPATIBLE_VERSION
from module.schema import Performance
from utils.function import format_ranges, get_rank, min_score_people_name, get_class_index_range, check_version, \
gen_picture
class ExcelReader:
kpi_list: list[str]
hml_list: list[str]
kpi_number: int
file_path: str
course_name: str
course_teacher_name: str
total_class_str: str
class_list: list[str]
class_number: list[int]
course_objectives: list[str]
course_objectives_number: list[int]
evaluation_stage: list[tuple[Optional[str], Optional[str]]]
achievement_level: list[list[Performance]]
target_score: list[list[float]]
n_evaluation_methods: list[Optional[str]]
question_data: dict[str, list[tuple[str, int]]]
ignore_version_check: bool
pic_list: list
def __init__(self, file_path: str, version_check: bool = False,
signal: Callable[[str, str], None] = lambda x, y: print(x)):
super().__init__()
self.file_path = file_path
self.kpi_list = []
self.class_list = []
self.class_number = []
self.course_objectives = []
self.course_objectives_number = []
self.course_name = ""
self.course_teacher_name = ""
self.course_lead_teacher_name = ""
self.evaluation_stage = []
self.achievement_level = []
self.kpi_number = 0
self.stu_kpi_achieve_list = []
self.target_score = []
self.n_evaluation_methods = []
self.total_class_str = ""
self.hml_list = []
self.question_data = {}
self.ignore_version_check = version_check
self.pic_list = []
self.signal = signal
def parse_excel(self):
try:
wb: Workbook = openpyxl.load_workbook(self.file_path, read_only=True, data_only=True)
sheet: Worksheet = wb['初始录入']
# 读取版本号
e_version = sheet['V4'].value if sheet['V4'].value is not None else sheet['U4'].value
if e_version is None:
e_version = "0"
status, _ = check_version(e_version, COMPATIBLE_VERSION)
CUR_VERSION = version.parse(e_version)
if not status:
if not self.ignore_version_check:
raise NotImplementedError(f"版本不适配,当前表格版本:{e_version},适配版本:{COMPATIBLE_VERSION}")
self.signal("已忽略表格兼容性:您的表格版本不在适配版本中,可能导致未知错误。", LOGLEVEL.WARNING)
# 读取教学班级
self.total_class_str = sheet["D10"].value
# 读取课程名称
match CUR_VERSION:
case _ if CUR_VERSION <= version.parse("7.3"):
self.course_name = sheet["D6"].value
case _ if CUR_VERSION >= version.parse("7.4"):
self.course_name = sheet["D5"].value
# 读取任课教师
self.course_teacher_name = sheet["D7"].value
# 读取课程负责人
self.course_lead_teacher_name = sheet["D8"].value
# 读取班级和人数
max_class_size = 4
match CUR_VERSION:
case _ if CUR_VERSION <= version.parse("7.6"):
max_class_size = 3
for i in range(2, 2 + max_class_size):
name = sheet[f"K{i}"]
number = sheet[f"M{i}"]
if name.value is None or number.value is None:
break
self.class_list.append(name.value)
self.class_number.append(int(number.value))
# 读取课程指标个数
self.kpi_number = sheet["H8"].value
# 读取课程目标和指标点
hml_start = 22
match CUR_VERSION:
case _ if CUR_VERSION <= version.parse("7.6"):
hml_start = 21
for i in range(hml_start, hml_start + 5):
hml = sheet[f"H{i}"]
objective = sheet[f"I{i}"]
kpi = sheet[f"Q{i}"]
if kpi.value is None or objective.value is None:
break
self.hml_list.append(hml.value)
self.kpi_list.append(kpi.value)
self.course_objectives.append(objective.value)
# 读取考核方式及权重
k_start = 7
match CUR_VERSION:
case _ if CUR_VERSION <= version.parse("7.6"):
k_start = 6
for i in range(k_start, k_start + 6):
if i in (k_start + 1, k_start + 2, k_start + 4):
continue
way = sheet[f"K{i}"]
per = sheet[f"L{i}"]
self.evaluation_stage.append((way.value, per.value))
# 读取平时考核方式
end = k_start + 5
if CUR_VERSION >= version.parse("7.7"):
end += 1
for i in range(k_start, end):
val = sheet[f"M{i}"].value
self.n_evaluation_methods.append(val)
sheet = wb['达成度']
# 目标值表格列 | 目标X |
kpi_row_tuple = ( # | 平时 | 大作业 | 试卷 | 个人达成值 |
('D', 'E', 'F', 'G'), # 目标一 平均分 | |
('H', 'I', 'J', 'K'), # 目标二 得分率 | |
('L', 'M', 'N', 'O'), # 目标三 达成度 | |
('P', 'Q', 'R', 'S'), # 目标四
('T', 'U', 'V', 'W') # 目标五
)
# 获取达成度中的目标分值、班级平均分,得分率和达成度
for i in range(self.kpi_number):
# 目标分值
self.target_score.append([
sheet[kpi_row_tuple[i][0] + f"6"].value,
sheet[kpi_row_tuple[i][1] + f"6"].value,
sheet[kpi_row_tuple[i][2] + f"6"].value
])
base_row = 8
for j in range(len(self.class_number)):
# 平均分
avg = (
sheet[kpi_row_tuple[i][0] + f"{base_row + j * 3}"].value,
sheet[kpi_row_tuple[i][1] + f"{base_row + j * 3}"].value,
sheet[kpi_row_tuple[i][2] + f"{base_row + j * 3}"].value
)
# 得分率
per = (
sheet[kpi_row_tuple[i][0] + f"{base_row + 1 + j * 3}"].value,
sheet[kpi_row_tuple[i][1] + f"{base_row + 1 + j * 3}"].value,
sheet[kpi_row_tuple[i][2] + f"{base_row + 1 + j * 3}"].value
)
# 达成度
kpi_c = sheet[kpi_row_tuple[i][0] + f"{base_row + 2 + j * 3}"].value
if len(self.achievement_level) - 1 < j:
self.achievement_level.append([Performance(avg, per, kpi_c)])
else:
self.achievement_level[j].append(Performance(avg, per, kpi_c))
# 获取学生的各项目标值
# 读取 A18:O87 范围的数据
match CUR_VERSION:
case _ if CUR_VERSION <= version.parse("7.6"):
rows = sheet[f'B18:{kpi_row_tuple[self.kpi_number - 1][3]}{17 + sum(self.class_number)}']
case _:
rows = sheet[f'B21:{kpi_row_tuple[self.kpi_number - 1][3]}{20 + sum(self.class_number)}']
# 将数据按行保存到列表中
for row in rows:
row_data = [cell.value for cell in row]
self.stu_kpi_achieve_list.append(row_data)
# 获取试题信息
sheet = wb['成绩录入']
start_col = 'BC' # 起始列
row_keys = 9 # 键所在的行
row_values = [7, 8] # 值所在的行
# 确定终止列
col = start_col
while True:
if not sheet[col + str(row_keys)].value:
break
col = get_column_letter(column_index_from_string(col) + 1)
# 读取数据
for col_index in range(column_index_from_string(start_col),
column_index_from_string(col)):
col_letter = get_column_letter(col_index)
key = sheet[col_letter + str(row_keys)].value
if key: # 确保键不为空
values = tuple(sheet[col_letter + str(r)].value for r in row_values)
if key in self.question_data:
self.question_data[key].append(values)
else:
self.question_data[key] = [values]
self.validate_data()
self.gen_picture()
except Exception as e:
error_message = traceback.format_exc()
raise Exception(f"""
原始错误:
{error_message}
""")
def set_file_path(self, file_path: str):
self.file_path = file_path
def validate_data(self):
self.signal("正在验证数据", LOGLEVEL.INFO)
return 0
def run(self):
self.parse_excel()
def clear_all_data(self):
self.kpi_list = []
self.kpi_number = 0
self.file_path = ""
self.course_name = ""
self.course_teacher_name = []
self.class_list = []
self.class_number = []
self.course_objectives = []
self.course_objectives_number = []
self.evaluation_stage = []
self.achievement_level = []
self.kpi_number = 0
self.stu_kpi_achieve_list = []
self.target_score = []
self.n_evaluation_methods = []
self.hml_list = []
self.question_data = {}
self.pic_list = []
def set_version_check(self, version_check: bool):
self.ignore_version_check = version_check
def gen_picture(self):
self.signal("正在生成散点图", LOGLEVEL.INFO)
for i in range(len(self.class_list)):
l_index, r_index = get_class_index_range(self.class_number, i)
self.pic_list.append(gen_picture(
self.stu_kpi_achieve_list,
self.kpi_number,
l_index,
r_index
))
def get_word_template(self, class_index):
yield "课程目标"
for i in range(self.kpi_number):
yield f"课程目标{i + 1}{self.hml_list[i]}"
yield ("总成绩=\n{}".
format(
"\n+".join([f"{n if n != '试卷' else '期末'}成绩×{int(p * 100)}%" for n, p in self.evaluation_stage if
p is not None])))
for i in self.course_objectives:
yield i
yield "支撑毕业要求指标点"
for i in self.kpi_list:
yield i
yield "考核类型"
for i in range(self.kpi_number):
s_lst = self.achievement_level[class_index][i].scores
for index, (j, s) in enumerate(zip(self.evaluation_stage, s_lst)):
if s is None:
continue
if index == 2:
if len(self.n_evaluation_methods) == 6:
if self.n_evaluation_methods[5] == "试卷":
yield "期末考核\n(试卷)"
else:
yield "期末考核"
elif j[0] == "试卷":
yield "期末考核\n(试卷)"
else:
yield "期末考核"
else:
yield f"{j[0]}考核"
yield "目标分值(分)"
for i in self.target_score:
for j in i:
if j is None:
continue
yield j
yield "权重"
for i in range(self.kpi_number):
s_lst = self.achievement_level[class_index][i].scores
for j, s in zip(self.evaluation_stage, s_lst):
if s is None:
continue
yield j[1]
yield "学号"
yield "姓名"
for i in range(self.kpi_number):
s_lst = self.achievement_level[class_index][i].scores
for (index, j), s in zip(enumerate(self.evaluation_stage), s_lst):
if s is None:
continue
match index:
case 0:
yield "\n".join([x for x in self.n_evaluation_methods[:3] if x is not None])
case 1:
yield "\n".join([x for x in self.n_evaluation_methods[3:5] if x is not None])
case 2:
if len(self.n_evaluation_methods) == 6 and self.n_evaluation_methods[5] != "试卷":
yield self.n_evaluation_methods[5]
elif len(self.n_evaluation_methods) == 5 and self.evaluation_stage[2][0] != "试卷":
yield self.evaluation_stage[2][0]
else:
# 中文数字到数字的映射
chinese_num_map = {'': 1, '': 2, '': 3,
'': 4, '': 5, '': 6,
'': 7, '': 8, '': 9}
q_lst = self.question_data[f'目标{i + 1}']
q_dict = {key: [item[1] for item in q_lst if item[0] == key] for key in
set(key for key, _ in q_lst)}
q_str_lst = [f"{k[0]}{k[1:]}{format_ranges(v)}" for k, v in q_dict.items()]
q_str_lst.sort(key=lambda x: chinese_num_map[x[0]])
q_str = "\n".join(q_str_lst)
yield q_str
case _:
yield "如果你能看到本行文字,请联系开发者"
l_range, r_range = get_class_index_range(self.class_number, class_index)
data = self.stu_kpi_achieve_list[l_range:r_range]
# 要移除的索引位置
remove_indices = [5, 9, 13, 17, 21]
# 更新后的数据
updated_data = [[item for j, item in enumerate(row) if j not in remove_indices] for row in data]
for i in updated_data:
for j in i:
if j is None:
continue
yield j
yield "平均得分"
for i in range(self.kpi_number):
avg_scores_lst = self.achievement_level[class_index][i].scores
for j in avg_scores_lst:
if j is None:
continue
yield j
yield "得分率"
for i in range(self.kpi_number):
per_scores_lst = self.achievement_level[class_index][i].rates
for j in per_scores_lst:
if j is None:
continue
yield j
yield "课程目标达成值"
for i in range(self.kpi_number):
yield f"课程目标{i + 1}达成值"
rates_lst = []
for x in range(self.kpi_number):
rates_lst.append(self.achievement_level[class_index][x].rates)
lst = [f"{r}×{p}" for (_, p), r in zip(self.evaluation_stage, rates_lst[i]) if r is not None]
total = sum(p for (_, p), r in zip(self.evaluation_stage, rates_lst[i]) if r is not None)
yield "({})/{}={}".format(
"+".join(lst),
total if total != 1 else 1,
self.achievement_level[class_index][i].achievement)
yield "考核材料清单"
y_str = (
f"1.{self.evaluation_stage[0][0]}成绩:{''.join([x for x in self.n_evaluation_methods[:3] if x is not None])}"
f"2.{self.evaluation_stage[1][0]}成绩:{''.join([x for x in self.n_evaluation_methods[3:5] if x is not None])}"
)
# 7.7
if len(self.n_evaluation_methods) == 6 and self.n_evaluation_methods[5] is not None:
y_str += f"3.{self.evaluation_stage[2][0]}成绩:{self.n_evaluation_methods[5]}"
else:
y_str += f"3.{self.evaluation_stage[2][0] if self.evaluation_stage[2][0] != '试卷' else '期末'}成绩:{self.evaluation_stage[2][0]}"
yield y_str
if len(self.class_list) == 1:
yield "课程目标达成值"
for i in range(self.kpi_number):
yield self.achievement_level[0][i].achievement
yield "课程目标达成结论"
for i in range(self.kpi_number):
if self.achievement_level[0][i].achievement >= 0.65:
yield "R达成≥0.65 £未达成(<0.65"
else:
yield "£达成≥0.65 R未达成<0.65"
yield "结果分析"
analysis_results = f"1.总体目标:本门课程有{self.kpi_number}个课程目标,"
for index, p in enumerate(self.achievement_level[class_index]):
analysis_results += f"课程目标{index + 1}达成值为{p.achievement}"
if index + 1 == self.kpi_number:
analysis_results = analysis_results[:-1] + ""
for i in range(self.kpi_number):
analysis_results += f"课程目标{i + 1}学生"
if self.achievement_level[class_index][i].rates[0] is not None:
analysis_results += \
(f"{self.evaluation_stage[0][0]}环节中达到"
f"{get_rank(self.achievement_level[class_index][i].rates[0])}程度,")
if self.achievement_level[class_index][i].rates[1] is not None:
analysis_results += \
(f"{self.evaluation_stage[1][0]}环节中"
f"表现{get_rank(self.achievement_level[class_index][i].rates[1])}")
if self.achievement_level[class_index][i].rates[2] is not None:
analysis_results += \
(f"{self.evaluation_stage[2][0] if self.evaluation_stage[2][0] != '试卷' else '考试'}"
f"环节中为{get_rank(self.achievement_level[class_index][i].rates[2])}水平,")
analysis_results += f"总体目标{'达成' if self.achievement_level[class_index][i].achievement >= 0.65 else '未达成'}"
analysis_results = analysis_results[:-1] + ""
c_lst = [x.achievement for x in self.achievement_level[class_index]]
min_index = min(enumerate(c_lst), key=lambda x: x[1])[0]
analysis_results += f"分析{self.kpi_number}个课程目标的达成值发现,课程目标{min_index + 1}的达成情况较差。"
analysis_results += "\n2.个体差异:"
for i in range(self.kpi_number):
l, r = get_class_index_range(self.class_number, class_index)
lst = min_score_people_name(self.stu_kpi_achieve_list, i, l, r)
analysis_results += f"对于课程目标{i + 1}"
if self.achievement_level[class_index][i].rates[0] is not None:
analysis_results += \
(f"{''.join(lst[0][:2])}{'' if len(lst[0]) > 2 else ''}"
f"{self.evaluation_stage[0][0]}考核中能力较弱,")
if self.achievement_level[class_index][i].rates[1] is not None:
analysis_results += \
(f"{''.join(lst[1][:2])}{'' if len(lst[1]) > 2 else ''}"
f"{self.evaluation_stage[1][0]}考核中能力较弱,")
if self.achievement_level[class_index][i].rates[2] is not None:
analysis_results += \
(f"{''.join(lst[2][:2])}{'' if len(lst[2]) > 2 else ''}"
f"{self.evaluation_stage[2][0] if self.evaluation_stage[2][0] != '试卷' else '考试'}中能力较弱;")
analysis_results = analysis_results[:-1] + ""
yield analysis_results
yield "改进措施"
yield ("注:改进措施,包括课时分配、教材选用、教学方式、教学方法、教学内容、评分标准、过程评价及帮扶\n"
"\n\n\n在这填入您的改进措施\n\n\n")
for i in range(88888):
yield "如果您看到了本段文字,请联系开发者"
def get_word_template_part_2(self):
# self.get_word_template_part0()
yield "课程目标"
for i in range(self.kpi_number):
yield f"课程目标{i + 1} {self.hml_list[i]}"
yield ("总成绩=\n{}".
format(
"\n+".join([f"{n if n != '试卷' else '期末'}成绩×{int(p * 100)}%" for n, p in self.evaluation_stage if
p is not None])))
for i in self.course_objectives:
yield i
yield "支撑毕业要求指标点"
for i in self.kpi_list:
yield i
yield "考核类型"
for i in range(self.kpi_number):
s_lst = self.achievement_level[0][i].scores
for index, (j, s) in enumerate(zip(self.evaluation_stage, s_lst)):
if s is None:
continue
if index == 2:
yield f"期末考核\n{j[0]}"
else:
yield f"{j[0]}考核"
yield "目标分值(分)"
for i in self.target_score:
for j in i:
if j is None:
continue
yield j
yield "权重"
for i in range(self.kpi_number):
s_lst = self.achievement_level[0][i].scores
for j, s in zip(self.evaluation_stage, s_lst):
if s is None:
continue
yield j[1]
min_rates = []
for index, i in enumerate(self.class_list):
yield i
for a_index, j in enumerate(self.achievement_level[index]):
if len(min_rates) - 1 < a_index:
min_rates.append(j.achievement)
else:
min_rates[a_index] = min(min_rates[a_index], j.achievement)
yield j.achievement
yield "课程目标达成值\n(取各班级最小值)"
for i in min_rates:
yield i
yield "课程目标达成结论"
for i in min_rates:
if i >= 0.65:
yield "R达成≥0.65 £未达成(<0.65"
else:
yield "£达成≥0.65 R未达成<0.65"
yield "结果分析"
analysis_results = f"1.总体情况:\n 本门课程有{self.kpi_number}个课程目标,"
for i in range(self.kpi_number):
analysis_results += f"课程目标{i + 1}达成值为{min_rates[i]}"
analysis_results = analysis_results[:-1] + ""
passed_c_lst = [f"课程目标{index + 1}" for index, x in enumerate(min_rates) if x >= 0.65]
not_passed_c_lst = [f"课程目标{index + 1}" for index, x in enumerate(min_rates) if x < 0.65]
if len(passed_c_lst) > 0:
analysis_results += (f"{''.join(passed_c_lst)}达到0.65的达成标准,"
f"表明学生在{''.join(passed_c_lst)}方面基本达到了本门课程提出的能力要求"
f"{'' if len(not_passed_c_lst) > 0 else ''}")
if len(not_passed_c_lst) > 0:
analysis_results += (f"{''.join(not_passed_c_lst)}未达到0.65的达成标准,"
f"表明学生在{''.join(not_passed_c_lst)}方面未能达到课程提出的能力要求,"
f"学生能力还有待进一步加强。")
analysis_results += "\n2.班级差异:"
avg_c_lst = [sum(i.achievement for i in x) / len(x) for x in self.achievement_level]
top_index = max(enumerate(avg_c_lst), key=lambda x: x[1])[0]
bottom_index = min(enumerate(avg_c_lst), key=lambda x: x[1])[0]
analysis_results += (f"\n 1班级对比各班级之间比较而言"
f"{self.class_list[top_index]}班的课程目标达成值相对较高,"
f"{self.class_list[bottom_index]}班的课程目标达成值较低。")
analysis_results += f"\n 2同班级不同课程目标比较分析{self.kpi_number}个课程目标的达成值发现,"
for i in range(len(self.class_list)):
min_p_rate_index = min(enumerate(self.achievement_level[i]), key=lambda x: x[1].achievement)[0]
o_c_str = [f'课程目标{x + 1}' for x in range(self.kpi_number) if x != min_p_rate_index]
analysis_results += (f"{self.class_list[i]}班的课程目标{min_p_rate_index + 1}达成情况最低,"
f"达成值为{self.achievement_level[i][min_p_rate_index].achievement}"
f"{''.join(o_c_str)}的达成情况较好;")
analysis_results = analysis_results[:-1] + ""
analysis_results += "\n3.结果分析: \n在此填写您的结果分析\n\n"
yield analysis_results
yield "改进措施"
yield "注:改进措施,包括课时分配、教材选用、教学方式、教学方法、教学内容、评分标准、过程评价及帮扶\n\n\n\n"
for i in range(88888):
yield "如果您看到了本段文字,请联系开发者"
@staticmethod
def get_word_template_part_3():
yield "课程目标达成情况合理性评价"
yield "评价样本的合理性"
yield "R全体样本 £抽样样本"
yield "评价依据的合理性"
yield "考核方法 R合适 £不合适 "
yield "考核内容是否支撑课程目标 R是 £否"
yield "评分标准 R明确 £不明确"
yield "计算过程的合理性"
yield "R合理正确 £合理但不够准确 £不合理"
yield "问题分析的合理性"
yield "R合理深入 £合理但不够深入 £不合理"
yield "改进措施的合理性"
yield "R合理可行 £合理但不太可行 £不合理"
yield "合理性评价结果"
yield "R合理 £基本合理 £不合理"
yield "专业负责人/系主任(签字)"
yield ("整改意见:\n"
"\n\n\n\n\n"
"\n\n\n"
"\n\t\t\t\t\t\t\t\t\t签字:\t\t\t日期:{}\n".
format(datetime.datetime.now().strftime("%Y-%m-%d")))
yield "课程负责人(签字)"
yield ("拟整改计划与措施:\n"
"\n\n\n\n\n"
"\n\n\n"
"\n\t\t\t\t\t\t\t\t\t签字:\t\t\t日期:{}\n".
format(datetime.datetime.now().strftime("%Y-%m-%d")))
for i in range(88888):
yield "如果您看到了本段文字,请联系开发者"
if __name__ == '__main__':
excel_reader = ExcelReader('../files/21工管-房屋建筑学-点名册-1227.xlsm')
excel_reader.parse_excel()
s, e = get_class_index_range(excel_reader.class_number, 1)
r = min_score_people_name(excel_reader.stu_kpi_achieve_list, 0, s, e)
gen_picture(excel_reader.stu_kpi_achieve_list, 3, s, e)
print()