From fa984b79f2764fdeed54cb6802b87e1ec99e6b25 Mon Sep 17 00:00:00 2001 From: sit002 Date: Tue, 2 Dec 2025 10:09:29 +0800 Subject: [PATCH] fix: support final grade type and flexible repo name parsing - Add GRADE_TYPE=final support for final_grade.json - Fix assignment_id extraction for non-hw format (e.g. final-vibevault) - Fix student_id extraction for xxx-stu_yyy format - Change default language from python to java --- .autograde/create_minimal_metadata.py | 134 +++++++++++++++++++++----- 1 file changed, 110 insertions(+), 24 deletions(-) diff --git a/.autograde/create_minimal_metadata.py b/.autograde/create_minimal_metadata.py index 97e0c9c..b98b879 100644 --- a/.autograde/create_minimal_metadata.py +++ b/.autograde/create_minimal_metadata.py @@ -2,7 +2,7 @@ """ 创建完整的成绩元数据文件 -从 grade.json 或 llm_grade.json 生成 metadata.json +从 grade.json / final_grade.json / llm_grade.json 生成 metadata.json 包含所有详细信息:未通过的测试、各题详情等 """ @@ -20,20 +20,22 @@ def extract_student_id(): if student_id: return student_id - # 从仓库名提取(格式:hw1-stu_sit001) + # 从仓库名提取 + # 支持格式:org/assignment-stu_xxx 或 org/assignment-stu-xxx repo = os.getenv("REPO", "") if repo: - # 匹配 hw1-stu_xxxxx 格式 - match = re.search(r'hw\d+-stu[_-]?([^/]+)', 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""" # 优先从环境变量获取 @@ -41,20 +43,94 @@ def extract_assignment_id(): if assignment_id: return assignment_id - # 从仓库名提取(格式:hw1-stu_sit001 或 hw1-template) + # 从仓库名提取 + # 支持格式:org/assignment-stu_xxx 或 org/assignment-template repo = os.getenv("REPO", "") if repo: - # 尝试匹配 hwX-stu_ 或 hwX-template - match = re.search(r'(hw\d+)-(?:stu|template)', repo) - if match: - return match.group(1) + # 取仓库名部分(去掉组织) + 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 - # 如果只是 hwX 格式 - match = re.search(r'(hw\d+)$', repo) - if match: - return match.group(1) - - return "hw1" # 默认回退 + 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", []) + } + } + 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", []) + } + } + 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 创建元数据,包含所有详细信息""" @@ -64,19 +140,20 @@ def create_grade_metadata(grade_file='grade.json'): assignment_id = extract_assignment_id() student_id = extract_student_id() - language = os.getenv("LANGUAGE", "python") + language = os.getenv("LANGUAGE", "java") # 提取所有相关信息 - final_score = grade_data.get("final_score", grade_data.get("score", 0)) + 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", "pytest") + 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 = { @@ -99,7 +176,8 @@ def create_grade_metadata(grade_file='grade.json'): "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 + "test_framework": test_framework, + "groups": groups } } @@ -248,7 +326,15 @@ def main(): grade_type = os.getenv("GRADE_TYPE", "programming").lower() grade_file_override = os.getenv("GRADE_FILE") - if grade_type == "llm": + 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): @@ -279,4 +365,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main()