#!/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()