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

1"""Code evaluation report.""" 

2 

3import json 

4from dataclasses import dataclass 

5from pathlib import Path 

6from typing import Any 

7 

8from sel_tools.utils.comment import ProjectCommentParser 

9from sel_tools.utils.repo import GitlabProject 

10 

11MD_EVALUATION_REPORT = """# {report_header} 

12 

13[Repo]({repo_url}) 

14 

15Overall: 

16 

17## Auto Evaluation 

18 

19```json 

20{evaluation_json} 

21``` 

22 

23## Manual Evaluation 

24 

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

26 

27## Student Section 

28 

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

30 

31{student_section} 

32 

33""" 

34 

35STUDENT_SECTION_TEMPLATE = """### {report_header} 

36 

37Overall score: {score}/{max_score} 

38 

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

40Please note that not all of them are errors. 

41 

42{notes} 

43""" 

44 

45COMMENTS_FOR_PROJECT_TEMPLATE = ( 

46 ProjectCommentParser.PROJECT_COMMENT_IDENTIFIER_PREFIX 

47 + """ {project_id} 

48 

49{student_section} 

50--- 

51""" 

52) 

53 

54 

55@dataclass(frozen=True) 

56class EvaluationResult: 

57 """Evaluation result.""" 

58 

59 name: str 

60 score: int 

61 max_score: int 

62 comment: str = "" 

63 

64 

65@dataclass 

66class EvaluationReport: 

67 """Evaluation report.""" 

68 

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 

77 

78 def to_json(self) -> str: 

79 class JsonEncoder(json.JSONEncoder): 

80 """Evaluation report json encoder.""" 

81 

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

83 if isinstance(o, Path): 

84 return str(o) 

85 return o.__dict__ 

86 

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

88 

89 def print_report_header(self) -> str: 

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

91 

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 ) 

99 

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 ) 

107 

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 ) 

113 

114 

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

121 

122 

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 )