final-vibevault-template/.autograde/create_minimal_metadata.py
2025-12-02 15:55:32 +08:00

373 lines
13 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.

#!/usr/bin/env python3
"""
创建完整的成绩元数据文件
从 grade.json / final_grade.json / llm_grade.json 生成 metadata.json
包含所有详细信息:未通过的测试、各题详情等
"""
import json
import os
import sys
import re
from datetime import datetime
def extract_student_id():
"""从环境变量或仓库名中提取学生 ID"""
# 优先从环境变量获取
student_id = os.getenv("STUDENT_ID")
if student_id:
return student_id
# 从仓库名提取
# 支持格式org/assignment-stu_xxx 或 org/assignment-stu-xxx
repo = os.getenv("REPO", "")
if repo:
# 匹配 xxx-stu_yyy 或 xxx-stu-yyy 格式
match = re.search(r'-stu[_-]([a-zA-Z0-9_]+)$', repo)
if match:
return match.group(1)
# 也尝试匹配 stu_xxx 在路径中的情况
match = re.search(r'stu[_-]([a-zA-Z0-9_]+)', repo)
if match:
return match.group(1)
return None
def extract_assignment_id():
"""从环境变量或仓库名中提取作业 ID"""
# 优先从环境变量获取
assignment_id = os.getenv("ASSIGNMENT_ID")
if assignment_id:
return assignment_id
# 从仓库名提取
# 支持格式org/assignment-stu_xxx 或 org/assignment-template
repo = os.getenv("REPO", "")
if repo:
# 取仓库名部分(去掉组织)
repo_name = repo.split("/")[-1] if "/" in repo else repo
# 移除 -stu_xxx 或 -template 后缀
assignment = re.sub(r'-stu[_-][a-zA-Z0-9_]+$', '', repo_name)
assignment = re.sub(r'-template$', '', assignment)
if assignment:
return assignment
return "unknown"
def create_final_metadata(final_grade_file='final_grade.json'):
"""从 final_grade.json 创建元数据(期末大作业专用)"""
try:
with open(final_grade_file, 'r', encoding='utf-8') as f:
final_data = json.load(f)
assignment_id = extract_assignment_id()
student_id = extract_student_id()
total_score = final_data.get("total_score", 0)
max_score = final_data.get("max_score", 100)
breakdown = final_data.get("breakdown", {})
# 构建各组成部分
components = []
# 编程测试部分
prog = breakdown.get("programming", {})
if prog:
prog_component = {
"type": "programming_java",
"score": prog.get("score", 0),
"max_score": prog.get("max_score", 80),
"details": {
"groups": prog.get("groups", {})
}
}
components.append(prog_component)
# REPORT.md 部分
report = breakdown.get("report", {})
if report:
report_component = {
"type": "llm_report",
"score": report.get("score", 0),
"max_score": report.get("max_score", 10),
"details": {
"flags": report.get("flags", []),
"confidence": report.get("confidence"),
"criteria": report.get("criteria", []) # LLM 各评分项及理由
}
}
components.append(report_component)
# FRONTEND.md 部分
frontend = breakdown.get("frontend", {})
if frontend:
frontend_component = {
"type": "llm_frontend",
"score": frontend.get("score", 0),
"max_score": frontend.get("max_score", 10),
"details": {
"flags": frontend.get("flags", []),
"confidence": frontend.get("confidence"),
"criteria": frontend.get("criteria", []) # LLM 各评分项及理由
}
}
components.append(frontend_component)
metadata = {
"version": "1.0",
"assignment": assignment_id,
"student_id": student_id,
"components": components,
"total_score": round(total_score, 2),
"total_max_score": max_score,
"timestamp": datetime.now().isoformat(),
"generator": "gitea-autograde"
}
return metadata
except Exception as e:
print(f"Error creating final metadata: {e}", file=sys.stderr)
return {}
def create_grade_metadata(grade_file='grade.json'):
"""从 grade.json 创建元数据,包含所有详细信息"""
try:
with open(grade_file, 'r') as f:
grade_data = json.load(f)
assignment_id = extract_assignment_id()
student_id = extract_student_id()
language = os.getenv("LANGUAGE", "java")
# 提取所有相关信息
final_score = grade_data.get("final_score", grade_data.get("total_score", grade_data.get("score", 0)))
base_score = grade_data.get("base_score", final_score)
penalty = grade_data.get("penalty", 0)
passed = grade_data.get("passed", 0)
total = grade_data.get("total", 0)
fails = grade_data.get("fails", [])
max_score = grade_data.get("max_score", 100)
test_framework = grade_data.get("test_framework", "junit")
coverage = grade_data.get("coverage")
raw_score = grade_data.get("raw_score")
groups = grade_data.get("groups", {})
# 动态生成 type 字段
type_map = {
"python": "programming_python",
"java": "programming_java",
"r": "programming_r"
}
component_type = type_map.get(language, f"programming_{language}")
component = {
"type": component_type,
"language": language,
"score": round(final_score, 2),
"max_score": max_score,
"details": {
"passed": passed,
"total": total,
"base_score": round(base_score, 2),
"penalty": round(penalty, 2),
"coverage": round(coverage, 2) if coverage else None,
"raw_score": round(raw_score, 2) if raw_score else None,
"failed_tests": fails,
"test_framework": test_framework,
"groups": groups
}
}
metadata = {
"version": "1.0",
"assignment": assignment_id,
"student_id": student_id,
"components": [component],
"total_score": round(final_score, 2),
"total_max_score": max_score,
"timestamp": datetime.now().isoformat(),
"generator": "gitea-autograde"
}
return metadata
except Exception as e:
print(f"Error creating grade metadata: {e}", file=sys.stderr)
return {}
def create_llm_metadata(llm_grade_file='artifacts/llm_grade.json'):
"""从 llm_grade.json 创建元数据,包含所有详细信息"""
try:
with open(llm_grade_file, 'r') as f:
llm_data = json.load(f)
assignment_id = extract_assignment_id()
student_id = extract_student_id()
# 提取聚合后的信息
total_score = llm_data.get("total_score", llm_data.get("total", 0))
max_score = llm_data.get("max_score", 30)
need_review = llm_data.get("need_review", False)
questions_data = llm_data.get("details", llm_data.get("questions", []))
# 构建各题详情
question_details = []
for i, q_data in enumerate(questions_data, 1):
q_score = q_data.get("total", q_data.get("score", 0))
q_max = q_data.get("max_score", 10)
q_confidence = q_data.get("confidence", 1.0)
q_flags = q_data.get("flags", [])
q_need_review = "need_review" in q_flags or q_data.get("need_review", False)
q_criteria = q_data.get("criteria", [])
# 规范化 criteria 格式
formatted_criteria = []
for crit in q_criteria:
formatted_criteria.append({
"id": crit.get("id", ""),
"score": round(float(crit.get("score", 0)), 2),
"reason": crit.get("reason", "")
})
question_detail = {
"question_id": f"SA{i}",
"question_name": q_data.get("question", f"SA{i}"),
"score": round(float(q_score), 2),
"max_score": q_max,
"confidence": round(float(q_confidence), 2),
"need_review": q_need_review,
"flags": q_flags,
"criteria": formatted_criteria
}
question_details.append(question_detail)
component = {
"type": "llm_essay",
"score": round(float(total_score), 2),
"max_score": max_score,
"details": {
"questions": len(question_details),
"need_review": need_review,
"question_details": question_details
}
}
metadata = {
"version": "1.0",
"assignment": assignment_id,
"student_id": student_id,
"components": [component],
"total_score": round(float(total_score), 2),
"total_max_score": max_score,
"timestamp": datetime.now().isoformat(),
"generator": "gitea-autograde"
}
return metadata
except Exception as e:
print(f"Error creating LLM metadata: {e}", file=sys.stderr)
return {}
def create_objective_metadata(objective_file='objective_grade.json'):
"""从 objective_grade.json 创建元数据"""
try:
with open(objective_file, 'r', encoding='utf-8') as f:
objective_data = json.load(f)
assignment_id = extract_assignment_id()
student_id = extract_student_id()
total_score = objective_data.get("score", 0)
max_score = objective_data.get("max_score", 0)
components = objective_data.get("components", [])
formatted_components = []
for comp in components:
comp_type = comp.get("type", "objective")
formatted_components.append({
"type": f"objective_{comp_type}",
"score": comp.get("score", 0),
"max_score": comp.get("max_score", 0),
"details": comp.get("details", {})
})
if not formatted_components:
formatted_components.append({
"type": "objective_total",
"score": total_score,
"max_score": max_score,
"details": {}
})
metadata = {
"version": "1.0",
"assignment": assignment_id,
"student_id": student_id,
"components": formatted_components,
"total_score": total_score,
"total_max_score": max_score,
"timestamp": datetime.now().isoformat(),
"generator": "gitea-autograde"
}
return metadata
except Exception as e:
print(f"Error creating objective metadata: {e}", file=sys.stderr)
return {}
def main():
"""主函数"""
# 检查命令行参数或环境变量
grade_type = os.getenv("GRADE_TYPE", "programming").lower()
grade_file_override = os.getenv("GRADE_FILE")
if grade_type == "final":
# 期末大作业成绩(包含编程+报告)
final_file = grade_file_override or "final_grade.json"
if os.path.exists(final_file):
metadata = create_final_metadata(final_file)
else:
print(f"Error: {final_file} not found", file=sys.stderr)
metadata = {}
elif grade_type == "llm":
# LLM 成绩
llm_file = grade_file_override or "artifacts/llm_grade.json"
if os.path.exists(llm_file):
metadata = create_llm_metadata(llm_file)
elif os.path.exists("llm_grade.json"):
metadata = create_llm_metadata("llm_grade.json")
else:
print(f"Error: {llm_file} not found", file=sys.stderr)
metadata = {}
elif grade_type == "objective":
objective_file = grade_file_override or "objective_grade.json"
if os.path.exists(objective_file):
metadata = create_objective_metadata(objective_file)
else:
print(f"Error: {objective_file} not found", file=sys.stderr)
metadata = {}
else:
# 编程成绩
grade_file = grade_file_override or "grade.json"
if os.path.exists(grade_file):
metadata = create_grade_metadata(grade_file)
else:
print(f"Error: {grade_file} not found", file=sys.stderr)
metadata = {}
# 输出到 stdout
print(json.dumps(metadata, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()