Average grades in export#2366
Conversation
af9697b to
382680b
Compare
dced350 to
6163186
Compare
niklasmohrin
left a comment
There was a problem hiding this comment.
Seems mostly correct, but I think we should make an effort to keep the structure of these methods clear
|
|
||
| self.assertEqual( | ||
| float(workbook.sheets()[0].row_values(5)[1]), | ||
| (float(workbook.sheets()[0].row_values(5)[2]) + float(workbook.sheets()[0].row_values(5)[3])) / 2, |
There was a problem hiding this comment.
From a quick glance, it looks like the setup above creates the same exact average result for both evaluations and same count of answers inside of them. This doesn't test whether we compute the average-of-averages with correct weighting. Can we change them to have different answer counts?
Coincidentally, float-equality here made me wonder about correctness. It should work if the two values here are identical, the sum is then 2 * the original value, which is divisible by 2 to perfectly recreate the original value. However, I'd like the test to test a somewhat more sophisticated case. We'll likely need to use assertAlmostEqual then.
There was a problem hiding this comment.
@fidoriel This currently compares a value the system produces against another value that the system produces, which leaves the chance that both might be wrong, but the test still passes.
For a bit more assertiveness, I would appreciate if we could change the assertion here to the exact string that should be shown in the output, optimally in the string-domain (without to-number conversion)
84a538a to
eb96a66
Compare
niklasmohrin
left a comment
There was a problem hiding this comment.
The output looks correct, but I think we need to spend some more time on the code and testing, otherwise it will become hard to maintain the exporter
| class ExcelExporter(ABC): | ||
| styles = { | ||
| "default": xlwt.Style.default_style, | ||
| "missing_average": xlwt.Style.default_style, |
There was a problem hiding this comment.
both styles should be defined in the same place, so this should be moved to the other class
| def filter_evaluations( # noqa: PLR0912 | ||
| semesters: Iterable[Semester] | None = None, | ||
| evaluation_states: Iterable[Evaluation.State] | None = None, | ||
| program_ids: Iterable[int] | None = None, | ||
| course_type_ids: Iterable[int] | None = None, | ||
| contributor: UserProfile | None = None, | ||
| include_not_enough_voters: bool = False, |
There was a problem hiding this comment.
I don't think that any of these should have defaults, there are two callers and both of them know exactly what they want to filter for.
I was wondering if we could get away without None by passing in Semester.objects.all() which results in something like WHERE "evaluation_course"."semester_id" IN (SELECT "evaluation_semester"."id" FROM "evaluation_semester") - I guess the database should be able to see that this check can be avoided @richardebeling ? But having None is also fine for now, I just think that the caller should be explicit about this
For contributor and include_not_enough_voters, I am not even sure if the current version is correct - wouldn't we want to use answers across all contributors for the average? Currently, we only use answers to the general questionnaire. And I guess include_not_enough_voters should be true for the average (@janno42 ?)
There was a problem hiding this comment.
I'm confused. Aren't there average values for contributor questions in the current version of this PR? I thought so.
But yes, include_not_enough_voters should be True for the averages.
There was a problem hiding this comment.
What I take from https://github.com/fidoriel/EvaP/blob/8a736d0713823104b69910edfbf23cfe49ab1ce4/evap/results/exporters.py#L415 is that even when contributor is not None we use the default of None for filter_evaluations
| all_evaluations_with_results, _, _ = self.filter_evaluations(evaluation_states=[Evaluation.State.PUBLISHED]) | ||
|
|
||
| for sheet_counter, (program_ids, course_type_ids) in enumerate(selection_list, 1): |
There was a problem hiding this comment.
Just to be sure, we want to have the same average in all sheets and not the average for the programs and course types of that sheet for every sheet? @janno42
There was a problem hiding this comment.
Now that you mention it... I think I wouldn't add this filter if we'd just have different course types, because their questions often differ. But for programs it might actually be important to have different averages. And then, when we're already at it, we can do the separation for both.
So yes, please calculate the averages for evaluations within the programs and course types of that sheet only.
| def _get_average_grade_and_approval_ratio( | ||
| cls, questionnaire_id: int, question: Question, results: OrderedDict[int, list[QuestionResult]] |
There was a problem hiding this comment.
This uses questionnaire_id and results only once to do results[questionnaire_id], so we should just do that in the caller
| if grade_result.question.id != question.id or not RatingResult.has_answers(grade_result): | ||
| continue | ||
|
|
||
| value_sum += grade_result.average * grade_result.count_sum |
There was a problem hiding this comment.
I think it would be fair to just ask the AnsweredRatingResult for the sum of grades instead of having it divided and then multiplied again; we can thus extract the summing out of its average method and make it available
| else: | ||
| self.write_cell(questionnaire.public_name, "bold") | ||
|
|
||
| self.write_cell("", "border_left_right") |
There was a problem hiding this comment.
this can just be merged with the next line (where the comment is also not correct anymore)
| for question in self.filter_text_and_heading_questions(questionnaire.questions.all()): | ||
| self.write_cell(question.text, "italic" if question.is_heading_question else "default") | ||
|
|
||
| average_grade, approval_ratio = self._get_average_of_average_grade_and_approval_ratio( |
There was a problem hiding this comment.
together with avg and average_approval_ratio below, it's not really clear what the difference between those four variables is; I think we can should use a clearer name
| self.assertEqual(sheet.row_values(5)[0], questions[1].text) | ||
| self.assertEqual(sheet.row_values(6)[0], questions[2].text) | ||
|
|
||
| def test_total_average(self): |
There was a problem hiding this comment.
this does not cover several important details:
- evaluations from previous semesters are included
- evaluations from other programs and course types are included
- only published evaluations are included
a. even if the unpublished evaluations are part of the export - which contributions are used
- we are averaging over the averages of several results
- are results with not enough voters used
So this test should be extended / more tests should be added. I think it is very easy to mess up some of these details in the future and when that happens, these tests should catch it
|
@fidoriel Do you want to continue working on this, or should someone else take over? |
I am currently short on time. If I am blocking factor, hence this PR is quite old, feel free to take over or find someone who is willing to. If this is non-blocking, I am happy to finish at some point :) |
Co-authored-by: Niklas Mohrin <dev@niklasmohrin.de>
Co-authored-by: Niklas Mohrin <dev@niklasmohrin.de>
Co-authored-by: Niklas Mohrin <dev@niklasmohrin.de>
Co-authored-by: Niklas Mohrin <dev@niklasmohrin.de>
Co-authored-by: Niklas Mohrin <dev@niklasmohrin.de>
a658751 to
93a347c
Compare
|
replaced by #2708 |
closes #2001