Coverage for tools / sel_tools / code_evaluation / report.py: 100%
47 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-21 08:53 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-21 08:53 +0000
1"""Code evaluation report."""
3import json
4from dataclasses import dataclass
5from pathlib import Path
6from typing import Any
8from sel_tools.utils.comment import ProjectCommentParser
9from sel_tools.utils.repo import GitlabProject
11MD_EVALUATION_REPORT = """# {report_header}
13[Repo]({repo_url})
15Overall:
17## Auto Evaluation
19```json
20{evaluation_json}
21```
23## Manual Evaluation
25Use this section for notes when evaluating the code manually.
27## Student Section
29The content of this section and below of this sentence can be shared with the students:
31{student_section}
33"""
35STUDENT_SECTION_TEMPLATE = """### {report_header}
37Overall score: {score}/{max_score}
39If available, below are a few notes about your code:
40Please note that not all of them are errors.
42{notes}
43"""
45COMMENTS_FOR_PROJECT_TEMPLATE = (
46 ProjectCommentParser.PROJECT_COMMENT_IDENTIFIER_PREFIX
47 + """ {project_id}
49{student_section}
50---
51"""
52)
55@dataclass(frozen=True)
56class EvaluationResult:
57 """Evaluation result."""
59 name: str
60 score: int
61 max_score: int
62 comment: str = ""
65@dataclass
66class EvaluationReport:
67 """Evaluation report."""
69 def __init__(self, gitlab_project: GitlabProject, homework_number: int, results: list[EvaluationResult]) -> None:
70 self.repo_path = gitlab_project.local_path
71 self.project_id = gitlab_project.gitlab_project.id
72 self.url = gitlab_project.gitlab_project.web_url
73 self.homework_number = homework_number
74 self.score = sum(result.score for result in set(results))
75 self.max_score = sum(result.max_score for result in set(results))
76 self.results = results
78 def to_json(self) -> str:
79 class JsonEncoder(json.JSONEncoder):
80 """Evaluation report json encoder."""
82 def default(self, o: Any) -> str | Any:
83 if isinstance(o, Path):
84 return str(o)
85 return o.__dict__
87 return json.dumps(self, cls=JsonEncoder, indent=4)
89 def print_report_header(self) -> str:
90 return f"Homework {self.homework_number} Evaluation Report"
92 def to_md(self) -> str:
93 return MD_EVALUATION_REPORT.format(
94 report_header=self.print_report_header(),
95 repo_url=self.url,
96 evaluation_json=self.to_json(),
97 student_section=self.print_student_section(),
98 )
100 def print_student_section(self) -> str:
101 return STUDENT_SECTION_TEMPLATE.format(
102 report_header=self.print_report_header(),
103 score=self.score,
104 max_score=self.max_score,
105 notes="\n".join(f"- {result.comment}" for result in self.results if result.comment),
106 )
108 def print_project_comments(self) -> str:
109 return COMMENTS_FOR_PROJECT_TEMPLATE.format(
110 project_id=self.project_id,
111 student_section=self.print_student_section(),
112 )
115def write_evaluation_reports(reports: list[EvaluationReport], report_base_name: str) -> None:
116 """Write evaluation reports to disk."""
117 for report in reports:
118 report_path = report.repo_path / report_base_name
119 report_path.with_suffix(".md").write_text(report.to_md())
120 report_path.with_suffix(".json").write_text(report.to_json())
123def write_evaluation_report_for_student_comments(reports: list[EvaluationReport], workspace: Path) -> None:
124 """Write a single evaluation report with comments for the individual student projects."""
125 workspace.joinpath("evaluation_report_comments_for_students.md").write_text(
126 "\n".join(report.print_project_comments() for report in reports)
127 )