Skip to content

H1/h2 subs and excludes#240

Open
Dasa122 wants to merge 7 commits into
mainfrom
h1/h2
Open

H1/h2 subs and excludes#240
Dasa122 wants to merge 7 commits into
mainfrom
h1/h2

Conversation

@Dasa122

@Dasa122 Dasa122 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

This pull request updates the substitute teacher selection logic to prioritize teachers with "H1" and "H2" lessons, improves the candidate sorting and display, and adds new translation keys. The changes enhance how substitution candidates are filtered, ranked, and labeled, making the selection process more transparent and flexible.

Substitute Candidate Logic and Data Structure Updates:

  • Updated the candidate schema and logic to track whether a teacher has "H1" or "H2" lessons (hasH1, hasH2) during the selected periods, replacing the previous hasLessonBeforeOrAfter boolean. This allows more nuanced filtering and prioritization of substitute teachers. [1] [2] [3]
  • Improved conflict detection: teachers are now excluded only if they have a real scheduling conflict (not an "H1" or "H2" lesson) during the selected periods.

Sorting and Display Enhancements:

  • Changed the sorting algorithm for substitute candidates to prioritize those with "H1" lessons first, then "H2", and finally by name, instead of prioritizing based on lesson proximity. [1] [2]
  • Updated the display in the substitution dialog to show specific tags ("H1", "H2", or both) next to teacher names based on their lesson types, utilizing new translation keys.

Translation Updates:

  • Added new translation keys for "H1" and "H2" tags in both English and Hungarian locale files. [1] [2]

closes #232 and #209

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Note: range_230809d5d3c7 is assigned to layer_schema_and_data above (the duplicate alias in unassigned_ranges was a formatting artifact — see the ranges block in that layer).


Walkthrough

The substitution-candidate logic in lesson.ts is rewritten to track H1/H2 subjects instead of lesson proximity (before/after period). The schema, occupation map, conflict filter, and sort order are all updated. The substitution-dialog.tsx UI and English/Hungarian translation files are updated to surface the new hasH1/hasH2 flags as localized tags.

Changes

H1/H2-Based Substitution Candidate Logic

Layer / File(s) Summary
Schema and occupation data shape
apps/chronos/src/routes/timetable/lesson.ts
substitutionCandidateSchema replaces hasLessonBeforeOrAfter with hasH1/hasH2 booleans; candidateLessonsByTeacherId value type changes from plain period numbers to { period, subjectShort } pairs; minPeriod/maxPeriod computation is removed.
Candidate building, filtering, and sort order
apps/chronos/src/routes/timetable/lesson.ts
The occupation loop stores subjectShort per period; conflict check skips H1/H2 lessons at selected periods (setting hasH1/hasH2 instead of excluding the candidate); the sort comparator orders by hasH1, then hasH2, removing the old hasLessonBeforeOrAfter ordering.
Dialog labeling, sorting, and translations
apps/iris/src/components/admin/substitution-dialog.tsx, apps/iris/public/locales/en/translation.json, apps/iris/public/locales/hu/translation.json
substituteOptions appends localized h1Tag/h2Tag text based on candidate.hasH1/candidate.hasH2 instead of the old nearbyTeacherTag; sortedSubstituteOptions now sorts purely by label string; both locale files add h1Tag: "H1" and h2Tag: "H2" translation keys.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • filcdev/filc#217: Modifies substitution-dialog.tsx with the same goal of prioritizing H1-tagged candidates via candidate.hasH1 and label-based sorting, making it directly related to this PR's UI changes.
  • filcdev/filc#218: Also modifies substitution-dialog.tsx option labeling and sorting using isNearby/hasLessonBeforeOrAfter, which this PR replaces with hasH1/hasH2 tagging.

Suggested reviewers

  • nemvince
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title 'H1/h2 subs and excludes' directly reflects the main changes: substitution logic refactored to handle H1/H2 subjects and exclude them from conflicts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot requested a review from nemvince June 23, 2026 21:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/chronos/src/routes/timetable/lesson.ts`:
- Around line 787-793: The H2 tie-breaker logic is currently nested inside the
condition that checks if neither candidate has H1, which causes the H2
comparison to be skipped when both candidates have H1. Move the H2 comparison
checks (the conditions checking a.hasH2 and b.hasH2) outside and after the H1
comparison block so that H2 acts as a tie-breaker that applies whenever H1
values are equal, regardless of whether they are both true or both false. This
ensures the comparator properly sorts by H1 first, then H2 as the next sorting
criterion.
- Around line 749-763: The conflict detection logic in the lesson.ts route
currently only triggers when there are NO H1/H2 lessons at a period, but it
should trigger when there ARE any non-H1/H2 lessons present. Replace the
conflict check condition from checking if neither hasH1AtPeriod nor
hasH2AtPeriod exist, to instead checking if any lesson in lessonsAtPeriod has a
subjectShort that is neither H1 nor H2, so that conflicts are properly detected
when real lessons coexist with H1/H2 placeholders at the same period.

In `@apps/iris/src/components/admin/substitution-dialog.tsx`:
- Around line 229-236: The sorting logic in the substitution-dialog.tsx file
incorrectly restricts the hasH2 tie-breaker comparison to only execute when
neither option has hasH1. This causes candidates with hasH1=true to be sorted
alphabetically instead of using hasH2 as a secondary sort criterion. Move the
hasH2 comparison logic (the if statements checking hasH2 values) outside of the
outer if statement that checks !(a.hasH1 || b.hasH1), so that hasH2 is always
evaluated as a secondary sort criterion regardless of the hasH1 values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3a8bab14-b85b-462e-be47-caf881ffcba8

📥 Commits

Reviewing files that changed from the base of the PR and between 1200ddc and f8c111f.

📒 Files selected for processing (4)
  • apps/chronos/src/routes/timetable/lesson.ts
  • apps/iris/public/locales/en/translation.json
  • apps/iris/public/locales/hu/translation.json
  • apps/iris/src/components/admin/substitution-dialog.tsx
📜 Review details
🧰 Additional context used
🪛 GitHub Actions: Code Quality / 0_lint.txt
apps/iris/src/components/admin/substitution-dialog.tsx

[error] 222-224: Biome lint (lint/complexity/noExcessiveCognitiveComplexity): Excessive complexity of 22 detected (max: 15). Please refactor this function to reduce its complexity score from 22 to the max allowed complexity 15.

apps/chronos/src/routes/timetable/lesson.ts

[error] 583-585: Biome lint (lint/complexity/noExcessiveCognitiveComplexity): Excessive complexity of 16 detected (max: 15). Please refactor this function to reduce its complexity score from 16 to the max allowed complexity 15.


[error] 731-733: Biome lint (lint/complexity/noExcessiveCognitiveComplexity): Excessive complexity of 18 detected (max: 15). Please refactor this function to reduce its complexity score from 18 to the max allowed complexity 15.


[error] 756-756: Biome lint (lint/style/useBlockStatements) FIXABLE: Block statements are preferred in this position. Unsafe fix: Wrap the statement with a JsBlockStatement.


[error] 757-757: Biome lint (lint/style/useBlockStatements) FIXABLE: Block statements are preferred in this position. Unsafe fix: Wrap the statement with a JsBlockStatement.


[error] 780-782: Biome lint (lint/complexity/noExcessiveCognitiveComplexity): Excessive complexity of 17 detected (max: 15). Please refactor this function to reduce its complexity score from 17 to the max allowed complexity 15.

🪛 GitHub Actions: Code Quality / lint
apps/iris/src/components/admin/substitution-dialog.tsx

[error] 222-222: Biome (lint/complexity/noExcessiveCognitiveComplexity): Excessive complexity of 22 detected (max: 15). Consider refactoring to reduce complexity.

apps/chronos/src/routes/timetable/lesson.ts

[error] 583-583: Biome (lint/complexity/noExcessiveCognitiveComplexity): Excessive complexity of 16 detected (max: 15). Consider refactoring to reduce complexity.


[error] 731-731: Biome (lint/complexity/noExcessiveCognitiveComplexity): Excessive complexity of 18 detected (max: 15). Consider refactoring to reduce complexity.


[error] 756-757: Biome (lint/style/useBlockStatements) FIXABLE: Block statements are preferred in this position. Unsafe fix: wrap the statement with a JsBlockStatement.


[error] 757-758: Biome (lint/style/useBlockStatements) FIXABLE: Block statements are preferred in this position. Unsafe fix: wrap the statement with a JsBlockStatement.


[error] 780-780: Biome (lint/complexity/noExcessiveCognitiveComplexity): Excessive complexity of 17 detected (max: 15). Consider refactoring to reduce complexity.

🔇 Additional comments (2)
apps/iris/public/locales/en/translation.json (1)

246-247: LGTM!

apps/iris/public/locales/hu/translation.json (1)

246-247: LGTM!

Comment on lines +749 to +763
const hasH1AtPeriod = lessonsAtPeriod.some(
(l) => l.subjectShort === 'H1'
);
const hasH2AtPeriod = lessonsAtPeriod.some(
(l) => l.subjectShort === 'H2'
);

if (hasH1AtPeriod) hasH1 = true;
if (hasH2AtPeriod) hasH2 = true;

// If none of the lessons at this period are H1 or H2, it's a real conflict
if (!(hasH1AtPeriod || hasH2AtPeriod)) {
hasConflict = true;
break;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Conflict check lets busy teachers pass when H1/H2 coexists with other lessons.

A candidate is currently accepted if any lesson at the selected period is H1/H2, even when another lesson at the same period is a real conflict. Conflict should trigger when any lesson at that period is not H1/H2.

Suggested fix
           const hasH1AtPeriod = lessonsAtPeriod.some(
             (l) => l.subjectShort === 'H1'
           );
           const hasH2AtPeriod = lessonsAtPeriod.some(
             (l) => l.subjectShort === 'H2'
           );

           if (hasH1AtPeriod) hasH1 = true;
           if (hasH2AtPeriod) hasH2 = true;

-          // If none of the lessons at this period are H1 or H2, it's a real conflict
-          if (!(hasH1AtPeriod || hasH2AtPeriod)) {
+          const hasNonH1H2AtPeriod = lessonsAtPeriod.some(
+            (l) => l.subjectShort !== 'H1' && l.subjectShort !== 'H2'
+          );
+          if (hasNonH1H2AtPeriod) {
             hasConflict = true;
             break;
           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasH1AtPeriod = lessonsAtPeriod.some(
(l) => l.subjectShort === 'H1'
);
const hasH2AtPeriod = lessonsAtPeriod.some(
(l) => l.subjectShort === 'H2'
);
if (hasH1AtPeriod) hasH1 = true;
if (hasH2AtPeriod) hasH2 = true;
// If none of the lessons at this period are H1 or H2, it's a real conflict
if (!(hasH1AtPeriod || hasH2AtPeriod)) {
hasConflict = true;
break;
}
const hasH1AtPeriod = lessonsAtPeriod.some(
(l) => l.subjectShort === 'H1'
);
const hasH2AtPeriod = lessonsAtPeriod.some(
(l) => l.subjectShort === 'H2'
);
if (hasH1AtPeriod) hasH1 = true;
if (hasH2AtPeriod) hasH2 = true;
const hasNonH1H2AtPeriod = lessonsAtPeriod.some(
(l) => l.subjectShort !== 'H1' && l.subjectShort !== 'H2'
);
if (hasNonH1H2AtPeriod) {
hasConflict = true;
break;
}
🧰 Tools
🪛 GitHub Actions: Code Quality / 0_lint.txt

[error] 756-756: Biome lint (lint/style/useBlockStatements) FIXABLE: Block statements are preferred in this position. Unsafe fix: Wrap the statement with a JsBlockStatement.


[error] 757-757: Biome lint (lint/style/useBlockStatements) FIXABLE: Block statements are preferred in this position. Unsafe fix: Wrap the statement with a JsBlockStatement.

🪛 GitHub Actions: Code Quality / lint

[error] 756-757: Biome (lint/style/useBlockStatements) FIXABLE: Block statements are preferred in this position. Unsafe fix: wrap the statement with a JsBlockStatement.


[error] 757-758: Biome (lint/style/useBlockStatements) FIXABLE: Block statements are preferred in this position. Unsafe fix: wrap the statement with a JsBlockStatement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chronos/src/routes/timetable/lesson.ts` around lines 749 - 763, The
conflict detection logic in the lesson.ts route currently only triggers when
there are NO H1/H2 lessons at a period, but it should trigger when there ARE any
non-H1/H2 lessons present. Replace the conflict check condition from checking if
neither hasH1AtPeriod nor hasH2AtPeriod exist, to instead checking if any lesson
in lessonsAtPeriod has a subjectShort that is neither H1 nor H2, so that
conflicts are properly detected when real lessons coexist with H1/H2
placeholders at the same period.

Comment on lines +787 to +793
if (!(a.hasH1 || b.hasH1)) {
if (a.hasH2 && !b.hasH2) {
return -1;
}
if (!a.hasH2 && b.hasH2) {
return 1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

H2 tie-break is skipped when both candidates already have H1.

The current comparator only checks hasH2 when neither side has hasH1. That breaks “sort by hasH1, then hasH2” for the (true, true) H1 case.

Suggested fix
-        if (!(a.hasH1 || b.hasH1)) {
-          if (a.hasH2 && !b.hasH2) {
-            return -1;
-          }
-          if (!a.hasH2 && b.hasH2) {
-            return 1;
-          }
-        }
+        if (a.hasH2 && !b.hasH2) {
+          return -1;
+        }
+        if (!a.hasH2 && b.hasH2) {
+          return 1;
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chronos/src/routes/timetable/lesson.ts` around lines 787 - 793, The H2
tie-breaker logic is currently nested inside the condition that checks if
neither candidate has H1, which causes the H2 comparison to be skipped when both
candidates have H1. Move the H2 comparison checks (the conditions checking
a.hasH2 and b.hasH2) outside and after the H1 comparison block so that H2 acts
as a tie-breaker that applies whenever H1 values are equal, regardless of
whether they are both true or both false. This ensures the comparator properly
sorts by H1 first, then H2 as the next sorting criterion.

Comment on lines +229 to +236
if (!(a.hasH1 || b.hasH1)) {
if (a.hasH2 && !b.hasH2) {
return -1;
}
if (!a.hasH2 && b.hasH2) {
return 1;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

UI sort has the same H2 tie-break gap as backend.

hasH2 is only considered when neither option has hasH1, so candidates with hasH1=true are incorrectly sorted alphabetically instead of by hasH2 second.

Suggested fix
-      if (!(a.hasH1 || b.hasH1)) {
-        if (a.hasH2 && !b.hasH2) {
-          return -1;
-        }
-        if (!a.hasH2 && b.hasH2) {
-          return 1;
-        }
-      }
+      if (a.hasH2 && !b.hasH2) {
+        return -1;
+      }
+      if (!a.hasH2 && b.hasH2) {
+        return 1;
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!(a.hasH1 || b.hasH1)) {
if (a.hasH2 && !b.hasH2) {
return -1;
}
if (!a.hasH2 && b.hasH2) {
return 1;
}
}
if (a.hasH2 && !b.hasH2) {
return -1;
}
if (!a.hasH2 && b.hasH2) {
return 1;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-dialog.tsx` around lines 229 -
236, The sorting logic in the substitution-dialog.tsx file incorrectly restricts
the hasH2 tie-breaker comparison to only execute when neither option has hasH1.
This causes candidates with hasH1=true to be sorted alphabetically instead of
using hasH2 as a secondary sort criterion. Move the hasH2 comparison logic (the
if statements checking hasH2 values) outside of the outer if statement that
checks !(a.hasH1 || b.hasH1), so that hasH2 is always evaluated as a secondary
sort criterion regardless of the hasH1 values.

@Dasa122 Dasa122 changed the title H1/h2 H1/h2 subs and excludes Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

Substitute selector shows H1/H2 for lesson even if candidate doesn't have H1/H2

1 participant