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

1"""Code evaluation report.""" 

2 

3import json 

4from dataclasses import dataclass 

5from pathlib import Path 

6from typing import Any 

7 

8from sel_tools.utils.repo import GitlabProject 

9 

10MD_EVALUATION_REPORT = """# {report_header} 

11 

12[Repo]({repo_url}) 

13 

14Overall: 

15 

16## Auto Evaluation 

17 

18```json 

19{evaluation_json} 

20``` 

21 

22## Manual Evaluation 

23 

24Use this section for notes when evaluating the code manually. 

25 

26## Student Section 

27 

28The content of this section and below of this sentence can be shared with the students: 

29 

30{student_section} 

31 

32""" 

33 

34STUDENT_SECTION_TEMPLATE = """### {report_header} 

35 

36Overall score: {score}/{max_score} 

37 

38If available, below are a few notes about your code: 

39Please note that not all of them are errors. 

40 

41{notes} 

42""" 

43 

44COMMENTS_FOR_PROJECT_TEMPLATE = """## Comments for Project {project_id} 

45 

46{student_section} 

47--- 

48""" 

49 

50 

51@dataclass(frozen=True) 

52class EvaluationResult: 

53 """Evaluation result.""" 

54 

55 name: str 

56 score: int 

57 max_score: int 

58 comment: str = "" 

59 

60 

61@dataclass 

62class EvaluationReport: 

63 """Evaluation report.""" 

64 

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 

73 

74 def to_json(self) -> str: 

75 class JsonEncoder(json.JSONEncoder): 

76 """Evaluation report json encoder.""" 

77 

78 def default(self, o: Any) -> str | Any: 

79 if isinstance(o, Path): 

80 return str(o) 

81 return o.__dict__ 

82 

83 return json.dumps(self, cls=JsonEncoder, indent=4) 

84 

85 def print_report_header(self) -> str: 

86 return f"Homework {self.homework_number} Evaluation Report" 

87 

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 ) 

95 

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 ) 

103 

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 ) 

109 

110 

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()) 

117 

118 

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 )