Coverage for tools / sel_tools / code_evaluation / report.py: 100%
46 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-02 18:55 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-02 18:55 +0000
1"""Code evaluation report."""
3import json
4from dataclasses import dataclass
5from pathlib import Path
6from typing import Any
8from sel_tools.utils.repo import GitlabProject
10MD_EVALUATION_REPORT = """# {report_header}
12[Repo]({repo_url})
14Overall:
16## Auto Evaluation
18```json
19{evaluation_json}
20```
22## Manual Evaluation
24Use this section for notes when evaluating the code manually.
26## Student Section
28The content of this section and below of this sentence can be shared with the students:
30{student_section}
32"""
34STUDENT_SECTION_TEMPLATE = """### {report_header}
36Overall score: {score}/{max_score}
38If available, below are a few notes about your code:
39Please note that not all of them are errors.
41{notes}
42"""
44COMMENTS_FOR_PROJECT_TEMPLATE = """## Comments for Project {project_id}
46{student_section}
47---
48"""
51@dataclass(frozen=True)
52class EvaluationResult:
53 """Evaluation result."""
55 name: str
56 score: int
57 max_score: int
58 comment: str = ""
61@dataclass
62class EvaluationReport:
63 """Evaluation report."""
65 def __init__(self, gitlab_project: GitlabProject, homework_number: int, results: list[EvaluationResult]) -> None:
66 self.repo_path = gitlab_project.local_path
67 self.project_id = gitlab_project.gitlab_project.id
68 self.url = gitlab_project.gitlab_project.web_url
69 self.homework_number = homework_number
70 self.score = sum(result.score for result in set(results))
71 self.max_score = sum(result.max_score for result in set(results))
72 self.results = results
74 def to_json(self) -> str:
75 class JsonEncoder(json.JSONEncoder):
76 """Evaluation report json encoder."""
78 def default(self, o: Any) -> str | Any:
79 if isinstance(o, Path):
80 return str(o)
81 return o.__dict__
83 return json.dumps(self, cls=JsonEncoder, indent=4)
85 def print_report_header(self) -> str:
86 return f"Homework {self.homework_number} Evaluation Report"
88 def to_md(self) -> str:
89 return MD_EVALUATION_REPORT.format(
90 report_header=self.print_report_header(),
91 repo_url=self.url,
92 evaluation_json=self.to_json(),
93 student_section=self.print_student_section(),
94 )
96 def print_student_section(self) -> str:
97 return STUDENT_SECTION_TEMPLATE.format(
98 report_header=self.print_report_header(),
99 score=self.score,
100 max_score=self.max_score,
101 notes="\n".join(f"- {result.comment}" for result in self.results if result.comment),
102 )
104 def print_project_comments(self) -> str:
105 return COMMENTS_FOR_PROJECT_TEMPLATE.format(
106 project_id=self.project_id,
107 student_section=self.print_student_section(),
108 )
111def write_evaluation_reports(reports: list[EvaluationReport], report_base_name: str) -> None:
112 """Write evaluation reports to disk."""
113 for report in reports:
114 report_path = report.repo_path / report_base_name
115 report_path.with_suffix(".md").write_text(report.to_md())
116 report_path.with_suffix(".json").write_text(report.to_json())
119def write_evaluation_report_for_student_comments(reports: list[EvaluationReport], workspace: Path) -> None:
120 """Write a single evaluation report with comments for the individual student projects."""
121 workspace.joinpath("evaluation_report_comments_for_students.md").write_text(
122 "\n".join(report.print_project_comments() for report in reports)
123 )