更改结构

This commit is contained in:
2025-05-31 16:36:34 +08:00
parent 75750180cc
commit 24c1dc6d59
7 changed files with 6 additions and 14 deletions

View File

505
module/achievement/doc.py Normal file
View File

@@ -0,0 +1,505 @@
import io
import os
import traceback
from typing import Callable
from docx import Document
from docx.enum.section import WD_ORIENT
from docx.enum.table import WD_ALIGN_VERTICAL, WD_ROW_HEIGHT_RULE
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Pt, Cm
from docx.text.run import Run
from module import LOGLEVEL
from module.achievement.excel import ExcelReader
class DocxWriter:
full_file_path: str
def __init__(self, save_path: str, filename: str, excel_reader: ExcelReader,
signal: Callable[[str, str], None] = lambda x, y: print(x)):
super().__init__()
self.save_path = save_path
self.filename = filename
self.excel_reader = excel_reader
self.full_file_path = os.path.join(save_path, filename) + '.docx'
self.signal = signal
def write(self) -> int:
try:
if not os.path.exists(self.save_path):
os.makedirs(self.save_path)
if os.path.exists(self.full_file_path):
self.signal(f"文件'{self.filename}'已存在,将覆盖原文件", LOGLEVEL.WARNING)
doc = Document()
for section in doc.sections:
new_width, new_height = Cm(29.7), Cm(21) # A4纸张
# 设置纸张方向为横向
section.orientation = WD_ORIENT.LANDSCAPE
# 设置页边距例如上下左右均为2厘米
section.top_margin = Cm(2)
section.bottom_margin = Cm(2)
section.left_margin = Cm(2)
section.right_margin = Cm(1)
section.page_width = new_width
section.page_height = new_height
paragraph = doc.add_paragraph()
# 设置段落居中对齐
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = paragraph.add_run(f"{self.excel_reader.course_name}》课程目标达成情况评价报告")
self.set_run_font(run, 16, '黑体', '黑体', True)
paragraph = doc.add_paragraph()
run = paragraph.add_run(
f"1.班级{'(年级)' if len(self.excel_reader.class_list) == 1 else ''}课程目标达成情况分析")
self.set_run_font(run, 14, 'Times New Roman', '黑体', True)
# 班级课程目标达成情况分析表
class_info_str = "班级:{} 学生人数:{} 任课教师:{}"
for r_index in range(len(self.excel_reader.class_list)):
self.signal(f"正在生成 {self.excel_reader.class_list[r_index]} 表格", LOGLEVEL.INFO)
paragraph = doc.add_paragraph()
# 设置段落为分散对齐
paragraph.alignment = WD_ALIGN_PARAGRAPH.DISTRIBUTE
run = paragraph.add_run(class_info_str.format(
self.excel_reader.class_list[r_index],
self.excel_reader.class_number[r_index],
self.excel_reader.course_teacher_name
))
self.set_run_font(run, 10.5, 'Times New Roman', '宋体')
if len(self.excel_reader.class_list) == 1:
rows = 14 + self.excel_reader.class_number[r_index] + self.excel_reader.kpi_number
else:
rows = 12 + self.excel_reader.class_number[r_index] + self.excel_reader.kpi_number
cols = 2 + self.calculate_columns(self.excel_reader.achievement_level[r_index]) # 加上第一列
table = doc.add_table(rows=rows, cols=cols)
# 设置外侧框线粗1.5磅内侧框线粗0.5磅
self.set_table_borders(table)
# 合并前六行头两个单元格
for row in range(6):
cell_start = table.cell(row, 0)
cell_end = table.cell(row, 1)
cell_start.merge(cell_end)
# 合并前三行的单元格
for row in range(3):
col_span = 2 # 从第三列开始
for performance in self.excel_reader.achievement_level[r_index]:
non_none_count = 3 - performance.scores.count(None)
if non_none_count > 1:
cell_start = table.cell(row, col_span)
cell_end = table.cell(row, col_span + non_none_count - 1)
cell_start.merge(cell_end)
col_span += non_none_count
X = self.excel_reader.kpi_number + 5 # X的值
if len(self.excel_reader.class_list) == 1:
X += 2
# 第一、二行合并第二个单元格
for row in range(rows - X, rows - X + 2):
table.cell(row, 0).merge(table.cell(row, 1))
# 第三行的合并操作
# 第一个单元格与第三+len(a[0])-1行第二个单元格合并
table.cell(rows - X + 2, 0).merge(table.cell(rows - X + 2 + self.excel_reader.kpi_number - 1, 1))
for i in range(self.excel_reader.kpi_number):
# 第三个与第四个单元格合并
table.cell(rows - X + 2 + i, 2).merge(table.cell(rows - X + i + 2, 3))
# 从第五个单元格合并到最后
start_cell = table.cell(rows - X + 2 + i, 4)
end_cell = table.cell(rows - X + 2 + i, cols - 1)
start_cell.merge(end_cell)
# 第三+len(a[0])行的合并操作
# 合并第一个到第二个单元格
table.cell(rows - X + 2 + self.excel_reader.kpi_number, 0).merge(
table.cell(rows - X + 2 + self.excel_reader.kpi_number, 1))
# 合并第三个到最后一个单元格
start_cell = table.cell(rows - X + 2 + self.excel_reader.kpi_number, 2)
end_cell = table.cell(rows - X + 2 + self.excel_reader.kpi_number, cols - 1)
start_cell.merge(end_cell)
if len(self.excel_reader.class_list) == 1:
# 合并前两个单元格
for i in range(2):
start = rows - X + 3 + i + self.excel_reader.kpi_number
table.cell(start, 0).merge(table.cell(start, 1))
# 依次合并后面的单元格
for row in range(start, start + self.excel_reader.kpi_number):
col_span = 2 # 从第三列开始
for performance in self.excel_reader.achievement_level[r_index]:
non_none_count = 3 - performance.scores.count(None)
if non_none_count > 1:
cell_start = table.cell(row, col_span)
cell_end = table.cell(row, col_span + non_none_count - 1)
cell_start.merge(cell_end)
col_span += non_none_count
start = rows - X + 3 + self.excel_reader.kpi_number
if len(self.excel_reader.class_list) == 1:
start += 2
end = rows
for row in range(start, end):
start_cell = table.cell(row, 1)
end_cell = table.cell(row, cols - 1)
start_cell.merge(end_cell)
# 填充表格数据
self.put_data_to_table(table, self.excel_reader.get_word_template, r_index)
doc.add_page_break()
if len(self.excel_reader.class_list) > 1:
paragraph = doc.add_paragraph()
run = paragraph.add_run("2.年级课程目标达成情况分析")
self.set_run_font(run, 14, 'Times New Roman', '黑体', True)
# 2.年级课程目标达成情况分析
class_info_str = "班级:{} 课程负责人:{}"
paragraph = doc.add_paragraph()
paragraph.alignment = WD_ALIGN_PARAGRAPH.DISTRIBUTE
run = paragraph.add_run(class_info_str.format(
self.excel_reader.total_class_str,
self.excel_reader.course_lead_teacher_name
))
self.set_run_font(run, 10.5, "Times New Roman", '宋体')
rows = 10 + len(self.excel_reader.class_list)
cols = 2 + self.calculate_columns(self.excel_reader.achievement_level[0]) # 加上第一列
table = doc.add_table(rows=rows, cols=cols)
# 设置外侧框线粗1.5磅内侧框线粗0.5磅
self.set_table_borders(table)
# 合并前六行头两个单元格
for row in range(rows - 2):
cell_start = table.cell(row, 0)
cell_end = table.cell(row, 1)
cell_start.merge(cell_end)
# 合并前三行的单元格
for row in range(3):
col_span = 2 # 从第三列开始
for performance in self.excel_reader.achievement_level[0]:
non_none_count = 3 - performance.scores.count(None)
if non_none_count > 1:
cell_start = table.cell(row, col_span)
cell_end = table.cell(row, col_span + non_none_count - 1)
cell_start.merge(cell_end)
col_span += non_none_count
# 合并中间X行的单元格
for row in range(6, 8 + len(self.excel_reader.class_list)):
col_span = 2
for performance in self.excel_reader.achievement_level[0]:
non_none_count = 3 - performance.scores.count(None)
if non_none_count > 1:
cell_start = table.cell(row, col_span)
cell_end = table.cell(row, col_span + non_none_count - 1)
cell_start.merge(cell_end)
col_span += non_none_count
# 合并最后两行的单元格
for row in range(2):
cell_start = table.cell(rows - row - 1, 1)
cell_end = table.cell(rows - row - 1, cols - 1)
cell_start.merge(cell_end)
# 填充数据
self.put_data_to_table(table, self.excel_reader.get_word_template_part_2)
doc.add_page_break()
# 3. 课程目标达成情况的合理性评价
paragraph = doc.add_paragraph()
run = paragraph.add_run(f"{3 if len(self.excel_reader.class_list) > 1 else 2}"
f". 课程目标达成情况的合理性评价")
self.set_run_font(run, 14, 'Times New Roman', '黑体', True)
rows = 9
cols = 4
table = doc.add_table(rows=rows, cols=cols)
# 设置外侧框线粗1.5磅内侧框线粗0.5磅
self.set_table_borders(table)
# 合并第一行
cell_start = table.cell(0, 0)
cell_end = table.cell(0, cols - 1)
cell_start.merge(cell_end)
# 合并第二行至最后
for i in range(1, 9):
if i == 2:
continue
cell_start = table.cell(i, 1)
cell_end = table.cell(i, cols - 1)
cell_start.merge(cell_end)
# 填充数据
self.put_data_to_table(table, self.excel_reader.get_word_template_part_3)
# 应用样式
self.signal("正在应用文字样式", LOGLEVEL.INFO)
# 遍历文档中的所有段落
for index, paragraph in enumerate(doc.paragraphs):
if index == 0:
self.set_paragraph_format(paragraph, WD_LINE_SPACING.ONE_POINT_FIVE)
else:
self.set_paragraph_format(paragraph)
# 遍历文档中的所有表格及其单元格
t_len = len(doc.tables)
part_1_table_index = [x for x in range(len(self.excel_reader.class_list))]
part_2_table_index = [len(part_1_table_index)] if len(self.excel_reader.class_list) != 1 else []
part_3_table_index = [t_len - 1]
for t_index, table in enumerate(doc.tables):
self.set_table_borders(table)
for r_index, row in enumerate(table.rows):
row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
row.height = Cm(0.7)
for c_index, cell in enumerate(row.cells):
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
self.set_cell_margins(cell, start=57, end=57)
for paragraph in cell.paragraphs:
for run in paragraph.runs:
self.set_run_font(run, 10.5, 'Times New Roman', '宋体')
self.set_paragraph_format(paragraph)
special_cell = []
if t_index in part_1_table_index:
ll = [(9 + x + self.excel_reader.class_number[t_index], 4) for x in
range(self.excel_reader.kpi_number)]
start_line = 9 + self.excel_reader.kpi_number
special_cell.extend(ll)
special_cell.extend([
(1, 2),
(2, 2),
(start_line + self.excel_reader.class_number[t_index], 2),
])
if len(self.excel_reader.class_list) == 1:
special_cell.extend([
(start_line + 3 + self.excel_reader.class_number[t_index], 1),
(start_line + 4 + self.excel_reader.class_number[t_index], 1),
])
lst = [(start_line + 3 + self.excel_reader.class_number[t_index], 1)]
else:
special_cell.extend([
(start_line + 1 + self.excel_reader.class_number[t_index], 1),
(start_line + 2 + self.excel_reader.class_number[t_index], 1),
])
lst = [(start_line + 1 + self.excel_reader.class_number[t_index], 1)]
if (r_index, c_index) in lst:
self.insert_image_from_data(cell,
self.excel_reader.pic_list[t_index],
Cm(7 * self.excel_reader.kpi_number)
)
if (len(self.excel_reader.class_list) == 1 and
r_index == start_line + 2 + self.excel_reader.class_number[t_index]):
self.change_font_to_wingding2(paragraph)
elif t_index in part_2_table_index:
special_cell = [
(1, 2),
(2, 2),
(8 + len(self.excel_reader.class_list), 1),
(9 + len(self.excel_reader.class_list), 1),
]
if r_index == 7 + len(self.excel_reader.class_list):
self.change_font_to_wingding2(paragraph)
elif t_index in part_3_table_index:
special_cell = [
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 1),
(7, 1),
(8, 1),
]
if r_index == 0:
for run in paragraph.runs:
self.set_run_font(run, 10.5, 'Times New Roman', '宋体', True)
else:
self.change_font_to_wingding2(paragraph)
if any(r_index == pair[0] and c_index >= pair[1] for pair in special_cell):
paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
else:
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.save(self.full_file_path)
except PermissionError as e:
raise Exception(f"""
简要错误信息:{str(e)}
这可能是由于Word文件被占用所导致的请关闭相应文件后再试。
""")
except Exception as _:
error_message = traceback.format_exc()
raise Exception(f"""
原始错误信息:
{error_message}
""")
def set_save_path(self, save_path: str):
self.save_path = save_path
self.full_file_path = os.path.join(save_path, self.filename)
def set_filename(self, filename: str):
self.filename = filename
self.full_file_path = os.path.join(self.save_path, filename)
def set_run_font(self, run: Run, size, western_font, chinese_font, bold=False):
run.font.size = Pt(size)
run.font.name = western_font
run.bold = bold
run._element.rPr.rFonts.set(qn('w:eastAsia'), chinese_font)
def set_cell_border(self, cell, **kwargs):
"""
设置单元格边框
kwargs: top, bottom, left, right, inside_h, inside_v
"""
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
# 创建一个新的'TC边框'元素
tcBorders = OxmlElement('w:tcBorders')
# 对于每个边框方向
for key, value in kwargs.items():
if value is not None:
tag = 'w:{}'.format(key)
border = OxmlElement(tag)
border.set(qn('w:val'), 'single')
border.set(qn('w:sz'), str(int(value * 8)))
border.set(qn('w:space'), '0')
border.set(qn('w:color'), 'auto')
tcBorders.append(border)
# 将边框添加到单元格属性中
tcPr.append(tcBorders)
def set_paragraph_format(self, paragraph, line_spacing_rule: WD_LINE_SPACING = WD_LINE_SPACING.SINGLE):
paragraph_format = paragraph.paragraph_format
paragraph_format.line_spacing_rule = line_spacing_rule # 单倍行距
paragraph_format.space_before = Pt(0) # 段前间距
paragraph_format.space_after = Pt(0) # 段后间距
def calculate_columns(self, performance_list):
"""
计算表格的列数
:param performance_list:
:return:
"""
col_count = 0
for performance in performance_list:
col_count += 3 - performance.scores.count(None) # 非None值的数量
return col_count
def put_data_to_table(self, table, gen_func, *args):
"""填充表格数据"""
data = gen_func(*args)
for i, row in enumerate(table.rows):
for j, cell in enumerate(row.cells):
if cell.text == "":
# 填充数据
# 这里需要根据您的数据结构来调整
d = next(data)
cell.text = str(d)
def set_cell_margins(self, cell, **kwargs):
"""
设置单元格的边距。
cell: 要修改的单元格实例
kwargs: 边距值以twips为单位1/1440英寸
示例set_cell_margins(cell, top=50, start=50, bottom=50, end=50)
"""
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
tcMar = OxmlElement('w:tcMar')
for m in ["top", "start", "bottom", "end"]:
if m in kwargs:
node = OxmlElement("w:{}".format(m))
node.set(qn('w:w'), str(kwargs.get(m)))
node.set(qn('w:type'), 'dxa')
tcMar.append(node)
tcPr.append(tcMar)
def is_start_of_merged_cells(self, cell):
""" 检查单元格是否是合并单元格的起始单元格 """
merge = cell._element.xpath('./w:tcPr/w:vMerge')
if merge:
return merge[0].get(qn('w:val')) == 'restart'
return False
def is_merged_vertically(self, cell):
""" 检查单元格是否垂直合并 """
merge = cell._element.xpath('./w:tcPr/w:vMerge')
return bool(merge)
def set_table_borders(self, table, outside_sz=1.5, inside_sz=0.5):
"""
设置整个表格的边框样式
outside_sz: 外侧边框大小
inside_sz: 内部分割线大小
"""
for row in table.rows:
for cell in row.cells:
self.set_cell_border(cell, top=inside_sz, bottom=inside_sz, left=inside_sz, right=inside_sz)
# 设置外侧边框
for cell in table.rows[0].cells:
self.set_cell_border(cell, top=outside_sz)
for cell in table.rows[-1].cells:
self.set_cell_border(cell, bottom=outside_sz)
for row in table.rows:
self.set_cell_border(row.cells[0], left=outside_sz)
self.set_cell_border(row.cells[-1], right=outside_sz)
def insert_image_from_data(self, cell, image_data, width=Cm(21), height=Cm(4.5)):
# 创建一个临时文件
with io.BytesIO(image_data) as image_stream:
image_stream.seek(0)
# 插入图片
# 在单元格中添加一个段落
paragraph = cell.paragraphs[0]
# 在段落中插入图片
run = paragraph.add_run()
run.add_picture(image_stream, width=width, height=height)
def is_chinese(self, char):
"""判断字符是否为中文"""
if '\u4e00' <= char <= '\u9fff':
return True
return False
def change_font_to_wingding2(self, paragraph):
for run in paragraph.runs:
if not run.text.strip(): # 跳过不包含文本的runs
continue
new_text = ""
for char in run.text:
if char in ['R', '£']:
# 对于特殊字符创建新的run
if new_text:
new_run = paragraph.add_run(new_text)
self.set_run_font(new_run, 10.5, 'Times New Roman', '宋体')
new_text = ""
special_run = paragraph.add_run(char)
self.set_run_font(special_run, 10.5, 'Wingdings 2', '宋体')
else:
# 累积其他字符
new_text += char
if new_text:
# 创建剩余文本的run
new_run = paragraph.add_run(new_text)
self.set_run_font(new_run, 10.5, 'Times New Roman', '宋体')
run.clear()

589
module/achievement/excel.py Normal file
View File

@@ -0,0 +1,589 @@
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:
yield f"期末考核\n{self.n_evaluation_methods[5]}"
else:
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[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] != "试卷" or
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()