generated from Java-2025Fall/final-vibevault-template
add autograde tests
This commit is contained in:
parent
ba051a1b97
commit
83cd133001
213
autograde/aggregate_final_grade.py
Normal file
213
autograde/aggregate_final_grade.py
Normal file
@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
aggregate_final_grade.py - 聚合最终成绩脚本
|
||||
将编程测试、报告评分等各部分成绩聚合为最终成绩
|
||||
"""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description='Final Grade Aggregation Script')
|
||||
parser.add_argument('--programming', required=True, help='编程测试评分结果文件')
|
||||
parser.add_argument('--report', required=True, help='REPORT.md评分结果文件')
|
||||
parser.add_argument('--frontend', required=True, help='FRONTEND.md评分结果文件')
|
||||
parser.add_argument('--out', required=True, help='输出最终成绩文件')
|
||||
parser.add_argument('--summary', required=True, help='输出最终成绩摘要文件')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_grade_file(file_path: str) -> Dict[str, Any]:
|
||||
"""加载评分结果文件"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def aggregate_grades(
|
||||
programming_grade: Dict[str, Any],
|
||||
report_grade: Dict[str, Any],
|
||||
frontend_grade: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""聚合各部分成绩"""
|
||||
# 各部分权重配置
|
||||
weights = {
|
||||
'programming': 0.6, # 编程测试占60%
|
||||
'report': 0.25, # 报告占25%
|
||||
'frontend': 0.15 # 前端报告占15%
|
||||
}
|
||||
|
||||
# 计算各部分成绩
|
||||
programming_score = programming_grade.get('total', 0.0)
|
||||
report_score = report_grade.get('total', 0.0)
|
||||
frontend_score = frontend_grade.get('total', 0.0)
|
||||
|
||||
# 计算加权平均分
|
||||
final_score = (
|
||||
programming_score * weights['programming'] +
|
||||
report_score * weights['report'] +
|
||||
frontend_score * weights['frontend']
|
||||
)
|
||||
|
||||
# 计算各部分的标准化得分(占总分的百分比)
|
||||
programming_percentage = (programming_score / 100.0) * weights['programming'] * 100.0
|
||||
report_percentage = (report_score / 100.0) * weights['report'] * 100.0
|
||||
frontend_percentage = (frontend_score / 100.0) * weights['frontend'] * 100.0
|
||||
|
||||
# 生成聚合结果
|
||||
result = {
|
||||
'final_score': final_score,
|
||||
'breakdown': {
|
||||
'programming': {
|
||||
'score': programming_score,
|
||||
'percentage': programming_percentage,
|
||||
'weight': weights['programming']
|
||||
},
|
||||
'report': {
|
||||
'score': report_score,
|
||||
'percentage': report_percentage,
|
||||
'weight': weights['report']
|
||||
},
|
||||
'frontend': {
|
||||
'score': frontend_score,
|
||||
'percentage': frontend_percentage,
|
||||
'weight': weights['frontend']
|
||||
}
|
||||
},
|
||||
'weights': weights,
|
||||
'details': {
|
||||
'programming': programming_grade,
|
||||
'report': report_grade,
|
||||
'frontend': frontend_grade
|
||||
},
|
||||
'status': 'completed',
|
||||
'timestamp': '2025-12-14T18:00:00Z'
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_summary(aggregate_result: Dict[str, Any]) -> str:
|
||||
"""生成最终成绩摘要"""
|
||||
summary = "# 最终成绩报告\n\n"
|
||||
|
||||
# 总体成绩
|
||||
final_score = aggregate_result['final_score']
|
||||
summary += f"## 总体成绩\n"
|
||||
summary += f"**最终得分**: {final_score:.2f}/100\n\n"
|
||||
|
||||
# 成绩分布
|
||||
summary += f"## 成绩分布\n"
|
||||
summary += "| 部分 | 得分 | 权重 | 占总分百分比 |\n"
|
||||
summary += "|------|------|------|------------|\n"
|
||||
|
||||
for part, data in aggregate_result['breakdown'].items():
|
||||
part_name = {
|
||||
'programming': '编程测试',
|
||||
'report': '后端与系统设计报告',
|
||||
'frontend': '前端界面与交互设计报告'
|
||||
}.get(part, part)
|
||||
|
||||
summary += f"| {part_name} | {data['score']:.2f} | {data['weight']*100:.0f}% | {data['percentage']:.2f} |\n"
|
||||
|
||||
summary += "\n"
|
||||
|
||||
# 详细说明
|
||||
summary += f"## 详细说明\n"
|
||||
|
||||
# 编程测试
|
||||
programming = aggregate_result['details']['programming']
|
||||
programming_total = programming.get('total', 0.0)
|
||||
summary += f"### 编程测试 ({aggregate_result['weights']['programming']*100:.0f}%)\n"
|
||||
summary += f"- 原始得分: {programming_total:.2f}/100\n"
|
||||
summary += f"- 测试通过率: {len([g for g in programming.get('groups', []) if g['passed'] > 0])}/{len(programming.get('groups', []))} 组\n"
|
||||
|
||||
for group in programming.get('groups', []):
|
||||
summary += f" - {group['group_name']}: {group['passed']}/{group['total']} 测试通过\n"
|
||||
|
||||
summary += "\n"
|
||||
|
||||
# 报告评分
|
||||
report = aggregate_result['details']['report']
|
||||
report_total = report.get('total', 0.0)
|
||||
summary += f"### 后端与系统设计报告 ({aggregate_result['weights']['report']*100:.0f}%)\n"
|
||||
summary += f"- 原始得分: {report_total:.2f}/100\n"
|
||||
|
||||
if report.get('feedback'):
|
||||
summary += f"- 反馈摘要: {report['feedback'][:100]}...\n"
|
||||
|
||||
summary += "\n"
|
||||
|
||||
# 前端报告评分
|
||||
frontend = aggregate_result['details']['frontend']
|
||||
frontend_total = frontend.get('total', 0.0)
|
||||
summary += f"### 前端界面与交互设计报告 ({aggregate_result['weights']['frontend']*100:.0f}%)\n"
|
||||
summary += f"- 原始得分: {frontend_total:.2f}/100\n"
|
||||
|
||||
if frontend.get('feedback'):
|
||||
summary += f"- 反馈摘要: {frontend['feedback'][:100]}...\n"
|
||||
|
||||
summary += "\n"
|
||||
|
||||
# 评分等级
|
||||
if final_score >= 90:
|
||||
grade_level = "优秀"
|
||||
elif final_score >= 80:
|
||||
grade_level = "良好"
|
||||
elif final_score >= 70:
|
||||
grade_level = "中等"
|
||||
elif final_score >= 60:
|
||||
grade_level = "及格"
|
||||
else:
|
||||
grade_level = "不及格"
|
||||
|
||||
summary += f"## 评分等级\n"
|
||||
summary += f"根据最终得分 {final_score:.2f}/100,该作业的评分等级为:**{grade_level}**\n"
|
||||
|
||||
# 建议
|
||||
summary += f"## 改进建议\n"
|
||||
summary += "1. 编程测试部分可以进一步提高测试覆盖率\n"
|
||||
summary += "2. 报告内容可以更加详细和系统化\n"
|
||||
summary += "3. 前端设计可以考虑更多用户体验细节\n"
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
args = parse_args()
|
||||
|
||||
# 加载各部分评分结果
|
||||
print(f"📁 加载编程测试评分: {args.programming}")
|
||||
programming_grade = load_grade_file(args.programming)
|
||||
|
||||
print(f"📁 加载报告评分: {args.report}")
|
||||
report_grade = load_grade_file(args.report)
|
||||
|
||||
print(f"📁 加载前端报告评分: {args.frontend}")
|
||||
frontend_grade = load_grade_file(args.frontend)
|
||||
|
||||
# 聚合成绩
|
||||
print("📊 聚合最终成绩...")
|
||||
final_result = aggregate_grades(programming_grade, report_grade, frontend_grade)
|
||||
|
||||
# 保存最终成绩
|
||||
print(f"💾 保存最终成绩: {args.out}")
|
||||
with open(args.out, 'w', encoding='utf-8') as f:
|
||||
json.dump(final_result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 生成摘要
|
||||
print(f"📝 生成最终成绩摘要: {args.summary}")
|
||||
summary = generate_summary(final_result)
|
||||
|
||||
with open(args.summary, 'w', encoding='utf-8') as f:
|
||||
f.write(summary)
|
||||
|
||||
print(f"✅ 成绩聚合完成! 最终得分: {final_result['final_score']:.2f}/100")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
533
autograde/generate_pdf_report.py
Normal file
533
autograde/generate_pdf_report.py
Normal file
@ -0,0 +1,533 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
generate_pdf_report.py - PDF报告生成脚本
|
||||
将评分结果和报告内容整合生成PDF文件
|
||||
"""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# 尝试导入reportlab,如果不存在则显示错误
|
||||
try:
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
|
||||
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer,
|
||||
Table, TableStyle, Image, PageBreak)
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.units import cm
|
||||
REPORTLAB_AVAILABLE = True
|
||||
|
||||
except ImportError:
|
||||
REPORTLAB_AVAILABLE = False
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description='PDF Report Generation Script')
|
||||
parser.add_argument('--score', required=True, help='最终成绩文件')
|
||||
parser.add_argument('--report', required=True, help='REPORT.md文件路径')
|
||||
parser.add_argument('--frontend', required=True, help='FRONTEND.md文件路径')
|
||||
parser.add_argument('--out', required=True, help='输出PDF文件路径')
|
||||
parser.add_argument('--student-id', required=True, help='学生ID')
|
||||
parser.add_argument('--student-name', required=True, help='学生姓名')
|
||||
parser.add_argument('--class-id', required=True, help='班级ID')
|
||||
parser.add_argument('--assignment-id', required=True, help='作业ID')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_file_content(file_path: str) -> str:
|
||||
"""加载文件内容"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def load_json_file(file_path: str) -> dict:
|
||||
"""加载JSON文件内容"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def markdown_to_paragraphs(markdown_text: str) -> list:
|
||||
"""将Markdown文本转换为ReportLab可处理的段落列表"""
|
||||
# 简单的Markdown处理,主要处理标题和段落
|
||||
paragraphs = []
|
||||
lines = markdown_text.split('\n')
|
||||
|
||||
current_paragraph = ""
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if not line:
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
continue
|
||||
|
||||
# 处理标题
|
||||
if line.startswith('# '):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('h1', line[2:]))
|
||||
elif line.startswith('## '):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('h2', line[3:]))
|
||||
elif line.startswith('### '):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('h3', line[4:]))
|
||||
elif line.startswith('#### '):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('h4', line[5:]))
|
||||
# 处理列表
|
||||
elif line.startswith('- '):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('list_item', line[2:]))
|
||||
elif line.startswith('* '):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('list_item', line[2:]))
|
||||
elif line.startswith('1. '):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('list_item', line[3:]))
|
||||
# 处理代码块
|
||||
elif line.startswith('```'):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('code_block_start', ''))
|
||||
elif paragraphs and paragraphs[-1][0] == 'code_block_start':
|
||||
if line.startswith('```'):
|
||||
paragraphs.append(('code_block_end', ''))
|
||||
else:
|
||||
paragraphs.append(('code_block', line))
|
||||
# 处理引用
|
||||
elif line.startswith('> '):
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
current_paragraph = ""
|
||||
paragraphs.append(('quote', line[2:]))
|
||||
# 处理粗体和斜体
|
||||
else:
|
||||
current_paragraph += ' ' + line if current_paragraph else line
|
||||
|
||||
if current_paragraph:
|
||||
paragraphs.append(('paragraph', current_paragraph))
|
||||
|
||||
return paragraphs
|
||||
|
||||
|
||||
def generate_pdf_report(args, score_data, report_content, frontend_content):
|
||||
"""生成PDF报告"""
|
||||
if not REPORTLAB_AVAILABLE:
|
||||
print("❌ ERROR: reportlab库未安装,无法生成PDF报告")
|
||||
print("请运行: pip install reportlab")
|
||||
return False
|
||||
|
||||
# 创建PDF文档
|
||||
doc = SimpleDocTemplate(args.out, pagesize=A4)
|
||||
story = []
|
||||
|
||||
# 获取样式
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# 自定义样式
|
||||
styles.add(ParagraphStyle(
|
||||
name='CustomHeading1',
|
||||
parent=styles['Heading1'],
|
||||
fontSize=24,
|
||||
alignment=TA_CENTER,
|
||||
textColor=colors.HexColor('#2c3e50'),
|
||||
spaceAfter=30
|
||||
))
|
||||
|
||||
styles.add(ParagraphStyle(
|
||||
name='CustomHeading2',
|
||||
parent=styles['Heading2'],
|
||||
fontSize=18,
|
||||
textColor=colors.HexColor('#34495e'),
|
||||
spaceAfter=15,
|
||||
spaceBefore=15
|
||||
))
|
||||
|
||||
styles.add(ParagraphStyle(
|
||||
name='CustomHeading3',
|
||||
parent=styles['Heading3'],
|
||||
fontSize=14,
|
||||
textColor=colors.HexColor('#7f8c8d'),
|
||||
spaceAfter=10
|
||||
))
|
||||
|
||||
styles.add(ParagraphStyle(
|
||||
name='CustomParagraph',
|
||||
parent=styles['Normal'],
|
||||
fontSize=12,
|
||||
leading=18,
|
||||
alignment=TA_JUSTIFY,
|
||||
textColor=colors.HexColor('#000000')
|
||||
))
|
||||
|
||||
styles.add(ParagraphStyle(
|
||||
name='CustomListItem',
|
||||
parent=styles['CustomParagraph'],
|
||||
leftIndent=20,
|
||||
bulletIndent=10
|
||||
))
|
||||
|
||||
styles.add(ParagraphStyle(
|
||||
name='CustomCentered',
|
||||
parent=styles['CustomParagraph'],
|
||||
alignment=TA_CENTER
|
||||
))
|
||||
|
||||
styles.add(ParagraphStyle(
|
||||
name='ScoreStyle',
|
||||
parent=styles['CustomCentered'],
|
||||
fontSize=36,
|
||||
textColor=colors.HexColor('#e74c3c'),
|
||||
fontWeight='bold',
|
||||
spaceAfter=20
|
||||
))
|
||||
|
||||
# 封面
|
||||
story.append(Spacer(1, 5*cm))
|
||||
story.append(Paragraph("作业成绩报告", styles['CustomHeading1']))
|
||||
story.append(Spacer(1, 2*cm))
|
||||
|
||||
# 学生信息
|
||||
student_info = [
|
||||
['学生姓名:', args.student_name],
|
||||
['学生ID:', args.student_id],
|
||||
['班级ID:', args.class_id],
|
||||
['作业ID:', args.assignment_id],
|
||||
['提交日期:', datetime.now().strftime('%Y-%m-%d')]
|
||||
]
|
||||
|
||||
table_style = TableStyle([
|
||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 12),
|
||||
('BOTTOMPADDING', (0, 0), (-1, -1), 10),
|
||||
('RIGHTPADDING', (0, 0), (0, -1), 20),
|
||||
])
|
||||
|
||||
info_table = Table(student_info, colWidths=[100, 200])
|
||||
info_table.setStyle(table_style)
|
||||
|
||||
story.append(Spacer(1, 1*cm))
|
||||
story.append(info_table)
|
||||
|
||||
# 最终成绩
|
||||
story.append(Spacer(1, 3*cm))
|
||||
story.append(Paragraph("最终成绩", styles['CustomHeading2']))
|
||||
story.append(Paragraph(f"{score_data['final_score']:.2f}/100", styles['ScoreStyle']))
|
||||
|
||||
# 成绩等级
|
||||
if score_data['final_score'] >= 90:
|
||||
grade = "优秀"
|
||||
grade_color = colors.HexColor('#27ae60')
|
||||
elif score_data['final_score'] >= 80:
|
||||
grade = "良好"
|
||||
grade_color = colors.HexColor('#f39c12')
|
||||
elif score_data['final_score'] >= 70:
|
||||
grade = "中等"
|
||||
grade_color = colors.HexColor('#e67e22')
|
||||
elif score_data['final_score'] >= 60:
|
||||
grade = "及格"
|
||||
grade_color = colors.HexColor('#d35400')
|
||||
else:
|
||||
grade = "不及格"
|
||||
grade_color = colors.HexColor('#c0392b')
|
||||
|
||||
story.append(Paragraph(f"等级: {grade}",
|
||||
ParagraphStyle(name='GradeStyle', parent=styles['CustomCentered'], fontSize=20, textColor=grade_color)))
|
||||
|
||||
story.append(PageBreak())
|
||||
|
||||
# 成绩分布
|
||||
story.append(Paragraph("成绩分布", styles['CustomHeading2']))
|
||||
|
||||
distribution_data = [
|
||||
['部分', '得分', '权重', '占总分百分比']
|
||||
]
|
||||
|
||||
for part, data in score_data['breakdown'].items():
|
||||
part_name = {
|
||||
'programming': '编程测试',
|
||||
'report': '后端与系统设计报告',
|
||||
'frontend': '前端界面与交互设计报告'
|
||||
}.get(part, part)
|
||||
|
||||
distribution_data.append([
|
||||
part_name,
|
||||
f"{data['score']:.2f}",
|
||||
f"{data['weight']*100:.0f}%",
|
||||
f"{data['percentage']:.2f}"
|
||||
])
|
||||
|
||||
distribution_table = Table(distribution_data, colWidths=[150, 60, 60, 100])
|
||||
distribution_table.setStyle(TableStyle([
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 12),
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#f8f9fa')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#2c3e50')),
|
||||
('BOTTOMPADDING', (0, 0), (-1, -1), 10),
|
||||
('TOPPADDING', (0, 0), (-1, -1), 10),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.HexColor('#dee2e6'))
|
||||
]))
|
||||
|
||||
story.append(Spacer(1, 1*cm))
|
||||
story.append(distribution_table)
|
||||
story.append(Spacer(1, 2*cm))
|
||||
|
||||
# 编程测试详情
|
||||
story.append(Paragraph("编程测试详情", styles['CustomHeading2']))
|
||||
|
||||
programming = score_data['details']['programming']
|
||||
programming_groups = programming.get('groups', [])
|
||||
|
||||
if programming_groups:
|
||||
prog_data = [['测试组', '通过测试', '总测试数', '得分', '通过率']]
|
||||
|
||||
for group in programming_groups:
|
||||
prog_data.append([
|
||||
group['name'],
|
||||
group['passed'],
|
||||
group['total'],
|
||||
f"{group['score']:.2f}",
|
||||
f"{group['passed']/group['total']*100:.1f}%"
|
||||
])
|
||||
|
||||
prog_table = Table(prog_data, colWidths=[100, 70, 70, 60, 70])
|
||||
prog_table.setStyle(TableStyle([
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 12),
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#f8f9fa')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#2c3e50')),
|
||||
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
|
||||
('TOPPADDING', (0, 0), (-1, -1), 8),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.HexColor('#dee2e6'))
|
||||
]))
|
||||
|
||||
story.append(Spacer(1, 1*cm))
|
||||
story.append(prog_table)
|
||||
else:
|
||||
story.append(Paragraph("暂无编程测试详情", styles['CustomParagraph']))
|
||||
|
||||
story.append(Spacer(1, 2*cm))
|
||||
story.append(PageBreak())
|
||||
|
||||
# 后端与系统设计报告
|
||||
story.append(Paragraph("后端与系统设计报告", styles['CustomHeading2']))
|
||||
story.append(Spacer(1, 1*cm))
|
||||
|
||||
report_paragraphs = markdown_to_paragraphs(report_content)
|
||||
in_code_block = False
|
||||
|
||||
for para_type, content in report_paragraphs:
|
||||
if para_type == 'h1':
|
||||
story.append(Paragraph(content, styles['CustomHeading1']))
|
||||
elif para_type == 'h2':
|
||||
story.append(Paragraph(content, styles['CustomHeading2']))
|
||||
elif para_type == 'h3':
|
||||
story.append(Paragraph(content, styles['CustomHeading3']))
|
||||
elif para_type == 'paragraph':
|
||||
story.append(Paragraph(content, styles['CustomParagraph']))
|
||||
story.append(Spacer(1, 0.5*cm))
|
||||
elif para_type == 'list_item':
|
||||
story.append(Paragraph(f'• {content}', styles['CustomListItem']))
|
||||
story.append(Spacer(1, 0.2*cm))
|
||||
elif para_type == 'quote':
|
||||
story.append(Paragraph(content,
|
||||
ParagraphStyle(name='QuoteStyle', parent=styles['CustomParagraph'],
|
||||
leftIndent=20, rightIndent=20,
|
||||
borderLeftWidth=3, borderLeftColor=colors.HexColor('#bdc3c7'))))
|
||||
story.append(Spacer(1, 0.5*cm))
|
||||
elif para_type == 'code_block_start':
|
||||
in_code_block = True
|
||||
story.append(Paragraph('```', styles['CustomParagraph']))
|
||||
elif para_type == 'code_block_end':
|
||||
in_code_block = False
|
||||
story.append(Paragraph('```', styles['CustomParagraph']))
|
||||
story.append(Spacer(1, 0.5*cm))
|
||||
elif para_type == 'code_block' and in_code_block:
|
||||
story.append(Paragraph(content,
|
||||
ParagraphStyle(name='CodeStyle', parent=styles['CustomParagraph'],
|
||||
fontName='Courier', fontSize=10,
|
||||
leftIndent=20, rightIndent=20)))
|
||||
|
||||
story.append(PageBreak())
|
||||
|
||||
# 前端界面与交互设计报告
|
||||
story.append(Paragraph("前端界面与交互设计报告", styles['CustomHeading2']))
|
||||
story.append(Spacer(1, 1*cm))
|
||||
|
||||
frontend_paragraphs = markdown_to_paragraphs(frontend_content)
|
||||
in_code_block = False
|
||||
|
||||
for para_type, content in frontend_paragraphs:
|
||||
if para_type == 'h1':
|
||||
story.append(Paragraph(content, styles['CustomHeading1']))
|
||||
elif para_type == 'h2':
|
||||
story.append(Paragraph(content, styles['CustomHeading2']))
|
||||
elif para_type == 'h3':
|
||||
story.append(Paragraph(content, styles['CustomHeading3']))
|
||||
elif para_type == 'paragraph':
|
||||
story.append(Paragraph(content, styles['CustomParagraph']))
|
||||
story.append(Spacer(1, 0.5*cm))
|
||||
elif para_type == 'list_item':
|
||||
story.append(Paragraph(f'• {content}', styles['CustomListItem']))
|
||||
story.append(Spacer(1, 0.2*cm))
|
||||
elif para_type == 'quote':
|
||||
story.append(Paragraph(content,
|
||||
ParagraphStyle(name='QuoteStyle', parent=styles['CustomParagraph'],
|
||||
leftIndent=20, rightIndent=20,
|
||||
borderLeftWidth=3, borderLeftColor=colors.HexColor('#bdc3c7'))))
|
||||
story.append(Spacer(1, 0.5*cm))
|
||||
elif para_type == 'code_block_start':
|
||||
in_code_block = True
|
||||
story.append(Paragraph('```', styles['CustomParagraph']))
|
||||
elif para_type == 'code_block_end':
|
||||
in_code_block = False
|
||||
story.append(Paragraph('```', styles['CustomParagraph']))
|
||||
story.append(Spacer(1, 0.5*cm))
|
||||
elif para_type == 'code_block' and in_code_block:
|
||||
story.append(Paragraph(content,
|
||||
ParagraphStyle(name='CodeStyle', parent=styles['CustomParagraph'],
|
||||
fontName='Courier', fontSize=10,
|
||||
leftIndent=20, rightIndent=20)))
|
||||
|
||||
# 构建并保存PDF
|
||||
try:
|
||||
doc.build(story)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ PDF生成失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def generate_text_report(args, score_data, report_content, frontend_content):
|
||||
"""生成纯文本报告作为备选方案"""
|
||||
text_report = []
|
||||
text_report.append("=" * 80)
|
||||
text_report.append(" 作业成绩报告 ")
|
||||
text_report.append("=" * 80)
|
||||
text_report.append("")
|
||||
|
||||
# 学生信息
|
||||
text_report.append(f"学生姓名: {args.student_name}")
|
||||
text_report.append(f"学生ID: {args.student_id}")
|
||||
text_report.append(f"班级ID: {args.class_id}")
|
||||
text_report.append(f"作业ID: {args.assignment_id}")
|
||||
text_report.append(f"提交日期: {datetime.now().strftime('%Y-%m-%d')}")
|
||||
text_report.append("")
|
||||
|
||||
# 最终成绩
|
||||
text_report.append("-" * 80)
|
||||
text_report.append(f"最终成绩: {score_data['final_score']:.2f}/100")
|
||||
|
||||
# 成绩等级
|
||||
if score_data['final_score'] >= 90:
|
||||
grade = "优秀"
|
||||
elif score_data['final_score'] >= 80:
|
||||
grade = "良好"
|
||||
elif score_data['final_score'] >= 70:
|
||||
grade = "中等"
|
||||
elif score_data['final_score'] >= 60:
|
||||
grade = "及格"
|
||||
else:
|
||||
grade = "不及格"
|
||||
|
||||
text_report.append(f"等级: {grade}")
|
||||
text_report.append("")
|
||||
|
||||
# 成绩分布
|
||||
text_report.append("成绩分布:")
|
||||
text_report.append("| 部分 | 得分 | 权重 | 占总分百分比 |")
|
||||
text_report.append("|------|------|------|------------|")
|
||||
|
||||
for part, data in score_data['breakdown'].items():
|
||||
part_name = {
|
||||
'programming': '编程测试',
|
||||
'report': '后端与系统设计报告',
|
||||
'frontend': '前端界面与交互设计报告'
|
||||
}.get(part, part)
|
||||
|
||||
text_report.append(f"| {part_name} | {data['score']:.2f} | {data['weight']*100:.0f}% | {data['percentage']:.2f} |")
|
||||
|
||||
text_report.append("")
|
||||
text_report.append("=" * 80)
|
||||
text_report.append("")
|
||||
|
||||
# 保存文本报告
|
||||
text_report_path = args.out.replace('.pdf', '.txt')
|
||||
with open(text_report_path, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(text_report))
|
||||
|
||||
print(f"✅ 已生成纯文本报告: {text_report_path}")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
args = parse_args()
|
||||
|
||||
print("📁 加载评分数据...")
|
||||
score_data = load_json_file(args.score)
|
||||
|
||||
print("📁 加载后端报告内容...")
|
||||
report_content = load_file_content(args.report)
|
||||
|
||||
print("📁 加载前端报告内容...")
|
||||
frontend_content = load_file_content(args.frontend)
|
||||
|
||||
print("📄 生成PDF报告...")
|
||||
pdf_success = generate_pdf_report(args, score_data, report_content, frontend_content)
|
||||
|
||||
# 如果PDF生成失败,生成纯文本报告
|
||||
if not pdf_success:
|
||||
print("🔄 尝试生成纯文本报告...")
|
||||
generate_text_report(args, score_data, report_content, frontend_content)
|
||||
else:
|
||||
print(f"✅ PDF报告生成成功: {args.out}")
|
||||
|
||||
# 生成一个简单的总结文件
|
||||
summary = {
|
||||
"student_info": {
|
||||
"name": args.student_name,
|
||||
"id": args.student_id,
|
||||
"class_id": args.class_id,
|
||||
"assignment_id": args.assignment_id
|
||||
},
|
||||
"final_score": score_data["final_score"],
|
||||
"report_path": args.out,
|
||||
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"status": "completed"
|
||||
}
|
||||
|
||||
summary_path = args.out.replace('.pdf', '_summary.json')
|
||||
with open(summary_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(summary, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"✅ 总结文件生成成功: {summary_path}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
181
autograde/grade_grouped.py
Normal file
181
autograde/grade_grouped.py
Normal file
@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
grade_grouped.py - 编程测试评分脚本
|
||||
根据测试分组配置对JUnit测试结果进行评分
|
||||
"""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description='Programming Test Grading Script')
|
||||
parser.add_argument('--junit-dir', required=True, help='JUnit test results directory')
|
||||
parser.add_argument('--groups', required=True, help='Test groups configuration file')
|
||||
parser.add_argument('--out', required=True, help='Output grade file (JSON)')
|
||||
parser.add_argument('--summary', required=True, help='Output summary file (Markdown)')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_test_groups(groups_file: str) -> Dict[str, Dict[str, any]]:
|
||||
"""加载测试分组配置"""
|
||||
with open(groups_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def parse_junit_results(junit_dir: str) -> Dict[str, Dict[str, any]]:
|
||||
"""解析JUnit测试结果"""
|
||||
results = {}
|
||||
|
||||
# 遍历JUnit目录中的所有XML文件
|
||||
for filename in os.listdir(junit_dir):
|
||||
if filename.endswith('.xml'):
|
||||
filepath = os.path.join(junit_dir, filename)
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
|
||||
# 命名空间处理
|
||||
ns = {'junit': 'http://www.junit.org/XML/ns/junit'} if '{' in root.tag else {}
|
||||
|
||||
# 遍历所有测试类
|
||||
for testcase in root.findall('.//testcase', ns):
|
||||
class_name = testcase.get('classname')
|
||||
test_name = testcase.get('name')
|
||||
full_test_name = f"{class_name}.{test_name}"
|
||||
|
||||
# 检查是否有失败或错误
|
||||
failure = testcase.find('.//failure', ns)
|
||||
error = testcase.find('.//error', ns)
|
||||
|
||||
results[full_test_name] = {
|
||||
'status': 'failed' if failure else 'error' if error else 'passed',
|
||||
'duration': float(testcase.get('time', '0')),
|
||||
'message': failure.get('message') if failure else error.get('message') if error else ''
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def calculate_grades(test_results: Dict[str, Dict[str, any]], test_groups: Dict[str, Dict[str, any]]) -> Tuple[float, List[Dict[str, any]]]:
|
||||
"""计算最终成绩"""
|
||||
total_score = 0.0
|
||||
group_details = []
|
||||
|
||||
for group_name, group_config in test_groups.items():
|
||||
group_weight = group_config.get('weight', 1.0)
|
||||
group_score = 0.0
|
||||
group_total = 0.0
|
||||
group_passed = 0
|
||||
group_total_tests = 0
|
||||
group_test_details = []
|
||||
|
||||
for test_pattern, test_weight in group_config.get('tests', {}).items():
|
||||
# 查找匹配的测试
|
||||
for test_name, test_result in test_results.items():
|
||||
if test_pattern in test_name:
|
||||
group_total += test_weight
|
||||
group_total_tests += 1
|
||||
|
||||
if test_result['status'] == 'passed':
|
||||
group_score += test_weight
|
||||
group_passed += 1
|
||||
|
||||
group_test_details.append({
|
||||
'test_name': test_name,
|
||||
'status': test_result['status'],
|
||||
'weight': test_weight,
|
||||
'score': test_weight if test_result['status'] == 'passed' else 0,
|
||||
'duration': test_result['duration'],
|
||||
'message': test_result['message']
|
||||
})
|
||||
|
||||
# 计算组内成绩
|
||||
if group_total > 0:
|
||||
group_percentage = (group_score / group_total) * 100
|
||||
weighted_group_score = (group_score / group_total) * group_weight
|
||||
total_score += weighted_group_score
|
||||
|
||||
group_details.append({
|
||||
'group_name': group_name,
|
||||
'weight': group_weight,
|
||||
'score': weighted_group_score,
|
||||
'percentage': group_percentage,
|
||||
'passed': group_passed,
|
||||
'total': group_total_tests,
|
||||
'tests': group_test_details
|
||||
})
|
||||
|
||||
return total_score, group_details
|
||||
|
||||
|
||||
def generate_summary(total_score: float, group_details: List[Dict[str, any]]) -> str:
|
||||
"""生成评分摘要Markdown"""
|
||||
summary = "# 编程测试评分报告\n\n"
|
||||
summary += f"## 最终成绩: {total_score:.2f}\n\n"
|
||||
|
||||
for group in group_details:
|
||||
summary += f"### {group['group_name']}\n"
|
||||
summary += f"- 权重: {group['weight']}\n"
|
||||
summary += f"- 得分: {group['score']:.2f} ({group['percentage']:.1f}%)\n"
|
||||
summary += f"- 通过率: {group['passed']}/{group['total']}\n\n"
|
||||
|
||||
summary += "| 测试名称 | 状态 | 权重 | 得分 | 耗时 (秒) | 错误信息 |\n"
|
||||
summary += "|---------|------|------|------|----------|---------|\n"
|
||||
|
||||
for test in group['tests']:
|
||||
status = "✅ 通过" if test['status'] == 'passed' else "❌ 失败" if test['status'] == 'failed' else "⚠️ 错误"
|
||||
message = test['message'][:50] + "..." if len(test['message']) > 50 else test['message']
|
||||
message = message.replace('|', '\\|').replace('\n', ' ') # 转义Markdown表格字符
|
||||
|
||||
summary += f"| {test['test_name']} | {status} | {test['weight']} | {test['score']:.2f} | {test['duration']:.2f} | {message} |\n"
|
||||
|
||||
summary += "\n"
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
args = parse_args()
|
||||
|
||||
# 加载测试分组配置
|
||||
print(f"📁 加载测试分组配置: {args.groups}")
|
||||
test_groups = load_test_groups(args.groups)
|
||||
|
||||
# 解析JUnit测试结果
|
||||
print(f"🔍 解析JUnit测试结果: {args.junit_dir}")
|
||||
test_results = parse_junit_results(args.junit_dir)
|
||||
|
||||
# 计算成绩
|
||||
print("📊 计算最终成绩...")
|
||||
total_score, group_details = calculate_grades(test_results, test_groups)
|
||||
|
||||
# 生成JSON输出
|
||||
print(f"💾 保存评分结果: {args.out}")
|
||||
output = {
|
||||
'total': total_score,
|
||||
'groups': group_details,
|
||||
'timestamp': '2025-12-14T18:00:00Z',
|
||||
'version': '1.0'
|
||||
}
|
||||
|
||||
with open(args.out, 'w', encoding='utf-8') as f:
|
||||
json.dump(output, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 生成摘要
|
||||
print(f"📝 生成评分摘要: {args.summary}")
|
||||
summary = generate_summary(total_score, group_details)
|
||||
|
||||
with open(args.summary, 'w', encoding='utf-8') as f:
|
||||
f.write(summary)
|
||||
|
||||
print(f"✅ 评分完成! 最终成绩: {total_score:.2f}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
210
autograde/llm_grade.py
Normal file
210
autograde/llm_grade.py
Normal file
@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
llm_grade.py - 使用LLM对报告进行评分
|
||||
"""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description='LLM Report Grading Script')
|
||||
parser.add_argument('--question', required=True, help='评分问题描述')
|
||||
parser.add_argument('--answer', required=True, help='待评分的答案文件')
|
||||
parser.add_argument('--rubric', required=True, help='评分标准文件')
|
||||
parser.add_argument('--out', required=True, help='输出评分结果文件')
|
||||
parser.add_argument('--summary', required=True, help='输出评分摘要文件')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_file_content(file_path: str) -> str:
|
||||
"""加载文件内容"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def load_rubric(rubric_path: str) -> Dict[str, Any]:
|
||||
"""加载评分标准"""
|
||||
with open(rubric_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def call_llm_api(prompt: str, max_retries: int = 3, timeout: int = 30) -> str:
|
||||
"""调用LLM API"""
|
||||
# 获取环境变量中的API配置
|
||||
api_key = os.environ.get('LLM_API_KEY', '')
|
||||
api_url = os.environ.get('LLM_API_URL', 'http://localhost:11434/api/generate')
|
||||
model = os.environ.get('LLM_MODEL', 'llama3')
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
if api_key:
|
||||
headers['Authorization'] = f'Bearer {api_key}'
|
||||
|
||||
payload = {
|
||||
'model': model,
|
||||
'prompt': prompt,
|
||||
'stream': False,
|
||||
'temperature': 0.3
|
||||
}
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = requests.post(api_url, json=payload, headers=headers, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
return result.get('response', '')
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"⚠️ LLM API调用失败 (尝试 {attempt + 1}/{max_retries}): {e}")
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(2 ** attempt) # 指数退避
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def generate_grading_prompt(question: str, answer: str, rubric: Dict[str, Any]) -> str:
|
||||
"""生成评分提示词"""
|
||||
prompt = f"""你是一位专业的课程作业评分专家。请根据以下评分标准,对学生的作业进行客观、公正的评分。
|
||||
|
||||
## 评分问题
|
||||
{question}
|
||||
|
||||
## 学生答案
|
||||
{answer}
|
||||
|
||||
## 评分标准
|
||||
{json.dumps(rubric, ensure_ascii=False, indent=2)}
|
||||
|
||||
## 评分要求
|
||||
1. 严格按照评分标准进行评分,每个评分项给出具体得分
|
||||
2. 详细说明每个评分项的得分理由
|
||||
3. 给出总体评价和建议
|
||||
4. 最终输出必须包含JSON格式的评分结果,格式如下:
|
||||
```json
|
||||
{
|
||||
"total": 总分,
|
||||
"scores": {
|
||||
"评分项1": 得分,
|
||||
"评分项2": 得分,
|
||||
...
|
||||
},
|
||||
"feedback": "详细的评分反馈和建议"
|
||||
}
|
||||
```
|
||||
|
||||
请确保输出格式正确,只包含上述JSON格式内容,不要添加任何其他说明。"""
|
||||
|
||||
return prompt
|
||||
|
||||
|
||||
def parse_llm_response(response: str) -> Dict[str, Any]:
|
||||
"""解析LLM响应"""
|
||||
# 提取JSON部分
|
||||
import re
|
||||
json_match = re.search(r'```json\n(.*?)\n```', response, re.DOTALL)
|
||||
|
||||
if json_match:
|
||||
json_str = json_match.group(1)
|
||||
try:
|
||||
return json.loads(json_str)
|
||||
except json.JSONDecodeError:
|
||||
print("⚠️ LLM响应中的JSON格式错误")
|
||||
|
||||
# 尝试直接解析响应
|
||||
try:
|
||||
return json.loads(response)
|
||||
except json.JSONDecodeError:
|
||||
print("⚠️ LLM响应不是有效的JSON格式")
|
||||
|
||||
# 如果都失败,返回默认值
|
||||
return {
|
||||
'total': 0.0,
|
||||
'scores': {},
|
||||
'feedback': '评分失败:无法解析LLM响应'
|
||||
}
|
||||
|
||||
|
||||
def generate_summary(grade_result: Dict[str, Any], rubric: Dict[str, Any]) -> str:
|
||||
"""生成评分摘要"""
|
||||
summary = "# LLM评分报告\n\n"
|
||||
|
||||
summary += f"## 总体评价\n"
|
||||
summary += f"- 最终得分: {grade_result['total']:.2f}/{sum(rubric.get('criteria', {}).values())}\n"
|
||||
summary += f"- 评分时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}\n\n"
|
||||
|
||||
summary += f"## 评分详情\n"
|
||||
summary += "| 评分项 | 得分 | 满分 | 评分标准 |\n"
|
||||
summary += "|-------|------|------|---------|\n"
|
||||
|
||||
for criterion, full_score in rubric.get('criteria', {}).items():
|
||||
score = grade_result['scores'].get(criterion, 0.0)
|
||||
summary += f"| {criterion} | {score:.2f} | {full_score} | {rubric.get('descriptions', {}).get(criterion, '')} |\n"
|
||||
|
||||
summary += "\n"
|
||||
summary += f"## 详细反馈\n"
|
||||
summary += grade_result['feedback'] + "\n"
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
args = parse_args()
|
||||
|
||||
# 加载文件内容
|
||||
print(f"📁 加载待评分文件: {args.answer}")
|
||||
answer_content = load_file_content(args.answer)
|
||||
|
||||
# 加载评分标准
|
||||
print(f"📋 加载评分标准: {args.rubric}")
|
||||
rubric = load_rubric(args.rubric)
|
||||
|
||||
# 生成评分提示词
|
||||
print("📝 生成评分提示词...")
|
||||
prompt = generate_grading_prompt(args.question, answer_content, rubric)
|
||||
|
||||
# 调用LLM API
|
||||
print("🤖 调用LLM进行评分...")
|
||||
try:
|
||||
llm_response = call_llm_api(prompt)
|
||||
print("✅ LLM API调用成功")
|
||||
except Exception as e:
|
||||
print(f"❌ LLM API调用失败: {e}")
|
||||
# 返回默认评分结果
|
||||
grade_result = {
|
||||
'total': 0.0,
|
||||
'scores': {criterion: 0.0 for criterion in rubric.get('criteria', {})},
|
||||
'feedback': f'评分失败:LLM API调用错误 - {str(e)}'
|
||||
}
|
||||
else:
|
||||
# 解析LLM响应
|
||||
print("📊 解析LLM评分结果...")
|
||||
grade_result = parse_llm_response(llm_response)
|
||||
|
||||
# 保存评分结果
|
||||
print(f"💾 保存评分结果: {args.out}")
|
||||
with open(args.out, 'w', encoding='utf-8') as f:
|
||||
json.dump(grade_result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 生成评分摘要
|
||||
print(f"📝 生成评分摘要: {args.summary}")
|
||||
summary = generate_summary(grade_result, rubric)
|
||||
|
||||
with open(args.summary, 'w', encoding='utf-8') as f:
|
||||
f.write(summary)
|
||||
|
||||
print(f"✅ 评分完成! 最终得分: {grade_result['total']:.2f}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user