Skip to content

fix: return one calendar event per lesson, not one per teacher - #105

Merged
nickknissen merged 1 commit into
mainfrom
fix/calendar-duplicate-lessons
Sep 18, 2026
Merged

nickknissen merged 1 commit into
mainfrom
fix/calendar-duplicate-lessons

Conversation

@nickknissen

Copy link
Copy Markdown
Owner

Problem

Aula's calendar returns a separate lesson row for every adult attached to a
lesson. The rows carry identical title, startDateTime, endDateTime,
primaryResource and belongsToProfiles, differing only in id and the
teacher in participants.

get_calendar_events mapped each row straight to a CalendarEvent, so a
lesson with two adults — common in the younger years — came back twice:

09:30  DFA 2, Lærer Nummer 1
09:30  DFA 2, Lærer Nummer 2
10:15  DFA 2, Lærer Nummer 1
10:15  DFA 2, Lærer Nummer 2

Every consumer inherited the duplicates, all day, every day.

Change

merge_duplicate_lessons() folds those rows back into one event each,
collecting the adults into new teacher_names / substitute_names lists.

The merge key is deliberately conservative — title, timeslot, room and
child must all match. Two different subjects in the same slot, the same subject
in two periods, and a lesson shared across two children all stay separate, so
nothing real is hidden. The helper returns new events via dataclasses.replace
rather than rewriting its inputs, keeps the first row's id and _raw, and
sets has_substitute when any row reported one.

_teacher_names() reads every participant in a role instead of just the first.
Aula normally splits co-taught lessons across rows, but a single row carrying
several same-role participants has also been seen; handling both shapes means
the teacher list is complete either way.

Compatibility

teacher_name and substitute_name are unchanged and point at the first
adult, so existing callers keep working without edits. The new lists are
additive and appear in dict(event) for JSON consumers.

One follow-on was required rather than optional: both CLI summaries rebuilt
their teacher list from the duplicate rows (dict.fromkeys(ev.teacher_name ...)).
Left alone they would have regressed to naming a single adult once the rows
merged, so they now read teacher_names through a shared _unique_names
helper — which also removes a copy-pasted comprehension.

Tests

11 model tests covering the merge and each case that must not merge, plus two
client-level tests: the real duplicated-row payload, and multiple teachers on a
single row.

813 passed, 4 skipped; ruff check and ruff format clean.

Aula's calendar answers with a separate lesson row for every adult
attached to a lesson: same title, timeslot, room and child, but a
distinct id and a different teacher in participants. Passed through as
they arrive, a co-taught lesson shows up two or three times in a row,
every day.

merge_duplicate_lessons folds those rows back into one event, collecting
the adults into new teacher_names and substitute_names lists. Rows merge
only when title, timeslot, room and child all match, so two genuinely
different events that merely overlap in time keep their own entries. The
scalar teacher_name and substitute_name stay and point at the first
adult, leaving existing callers working unchanged.

Teachers are also read per row rather than one-per-role, since a single
row has been seen carrying several participants in the same role.

The two CLI summaries rebuilt their teacher lists from the duplicate
rows, so they now read teacher_names instead and would otherwise have
regressed to naming a single adult.
@nickknissen
nickknissen merged commit a8ede89 into main Sep 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant