Homerooms and Subject Groups¶
A Homeroom is a registration class. A SubjectGroup is a teaching course with its own roster. This chapter covers their curriculum anchors, permissions, placement workflows and temporal behavior. Timetables, attendance and grades describe the consumers.
1. Model and ownership¶
| Concept | Identity and responsibility |
|---|---|
| Homeroom | Department, curriculum, grade and academic year; roster, optional tutor and base room |
| SubjectGroup | One curriculum subject and grade in an academic year; independent roster, ordered teachers and optional base room |
| CombinedClass | Shared meetings for multiple courses; members retain their own rosters, grades and curriculum obligations |
| CurriculumSubject | Subject owned by a curriculum; optional track/block containment and grade-specific hours cells |
A homeroom does not choose a track. Students from different tracks can share it when curriculum and cohort match. Creating a homeroom does not create courses.
A course may have an optional homeroomId link. This expresses section affinity and drives the roster cascade below; it is not ownership or an eligibility fence. Unlinked courses have independent rosters. Italian labels use Corso/Corsi for SubjectGroup.
Effective-dated episodes represent rosters and teaching assignments. Exclusion constraints allow at most one concurrent homeroom per student, one course per student/subject/year, one assignment per course/teacher, and one combination per course. See migrations.
Curriculum substrate¶
A curriculum owns subjects, option blocks, tracks and rules. A subject is mandatory when it has no option block, or an alternative within a block. The database rejects contradictory mandatory/block shapes.
A CurriculumSubjectHours row means the subject is offered in its grade with positive weekly hours. Zero/null submissions represent an absent cell and are discarded. Structure is shared across grades; cells determine the offered set.
Classes and courses can anchor DRAFT or READY curricula. Offering a curriculum creates no courses. Demoting it is blocked by current/planned selection use; attached classes remain. Removing structure is different: in-use guards refuse student-bearing courses and clear removable empty courses before restricted parents are deleted. Preserve temporal/history guards on those cleanup paths.
Criteria, scales, levels and units¶
Criteria have a subject default (gradeId = null) and optional per-grade rows. A grade override replaces the default list; it does not merge. No override rows means inheritance, so a distinct empty override is not represented. Names are unique per scope; reads order by name.
The criteria scale resolves from the grade cell, then subject default, then resolved subject grading scale, including the curriculum fallback. Use the grade/scale resolver rather than duplicating its cascade.
Subject levels are tenant/global catalogue rows under curriculum configuration permission. Grid writes validate their visibility. LEVEL rules count selected subjects at one level; ENTIRE_PLAN rules count the whole plan. Block cardinality belongs to OptionBlock.minSelections/maxSelections.
A subject can be a leaf, a main subject with units, or a unit:
- Leaf/main subjects own hours, scheduling, subject scale, rooms, level and duration.
- Units carry criteria and, in UNIT criteria mode, a criteria scale. Their per-grade overrides are limited to the main subject's offered grades.
- Units have no independent hours, track/block choice or SubjectGroup. Structural coverage and scheduling count the main subject once.
Grid DTOs nest units one level deep. Unit reads expose empty hours; isContainer, criteriaLevel and nested units describe the shape. Main-subject grades identify the unit; attendance may tag covered units.
Sources: src/curriculum/curriculum-structure-sync.ts, curriculum.validation.ts, selection-consistency.ts and curriculum DTOs.
2. Permissions and disclosure¶
Both entities use composition for roster/header access, plus configured custom-field scopes. Their domain services return aggregate DTOs and explicitly own nested disclosure.
| Concern | Source |
|---|---|
| Homeroom read/write reach | src/homerooms/homerooms.policy.ts |
| Course and subject-anchor reach | src/subject-groups/subject-groups.policy.ts |
| Student disclosure | src/students/students.policy.ts |
| Qualified assignments | PermissionsService.resolveAuthorizedRoleSlice |
| Resolved relation membership | Policy-query files and PolicyEvaluation |
Policies declare broad school readers, teacher relationships and parameterized management reach; preset grants still decide route admission. A policy branch does not itself grant a scope.
Homeroom teacher reach includes directly tutored classes, even empty ones, and classes containing related students. Course reach includes effective ordinary teaching and assistance. Management reads can union personal reach with assigned departments/curricula. Writes use separate qualified management predicates; personal read reach cannot lend write authority.
Homeroom read policies require asynchronous resolution. Use resolvePolicyWhere and the operation's policy evaluation where needed. Resolve course membership at its own root before consuming it in combined queries. Reuse bounded membership facts instead of nesting complete foreign policy trees. See permissions.
AND policies with tenant/year/target restrictions. Hidden targets return 404. Access-context roster/pending rows intersect student visibility; linked courses intersect course visibility. Null context is reserved for trusted internal post-authorization reloads.
Custom-field details use a top-level bag from buildCustomFieldValues, including null for unset definitions. Writes use validateCustomFieldsFlat; PATCH merges validated values. Do not reshape aggregate DTOs into grouped CRUD responses to reuse filtering.
Homeroom detail GET adds roster study plans only with secondary students.assignment READ authority. Otherwise it omits the block. Write reloads do not perform that enrichment; curriculum trees are deduplicated by curriculum ID.
Student assignment reads expose study plan, homeroom and courses with grade-resolved hours and teachers. Detail loads a grade-sliced curriculum tree; lists avoid it. Sources: src/students/study-plan.mapper.ts and class-assignment.mapper.ts.
3. Headers and creation¶
Homeroom department, curriculum and grade are immutable after creation because they define eligibility. Name, tutor, room, custom fields and roster remain mutable. Renaming has no course-name cascade.
Course anchor/grade define its offered cell. Units cannot be anchors. Names are unique per year, grade, subject and name; P2002 maps to SUBJECT_GROUP_NAME_CONFLICT.
Creation validates references and eligibility, then writes header, temporal roster, implied selection and audit effects transactionally. A newly created homeroom has no linked courses to cascade into. Creation with a roster is not merely a header insert.
Teacher arrays use ordered set replacement: omitted is unchanged, empty clears, position zero is primary. Membership changes are dated; pure reordering is immediate presentation data.
Base rooms are timetable defaults. A lesson can select another compatible room; generation does not force the default. Combined members may have different defaults, and a combined placement row offers one only when non-null member defaults agree.
4. Eligibility pickers¶
Pickers and writes share classifyHomeroomEligibility and classifySubjectGroupEligibility in their domain eligibility files. They return a bucket or hidden reason; writes map hidden reasons to errors, pickers omit them.
Loaders must apply the same dated display cohort on both sides. Student head department/grade represent the terminal planned placement, so using those directly can disagree with today's governing placement. Display resolution keeps pre-year candidates usable; authorization/register paths retain strict temporal predicates.
| Route | Anchor | Buckets |
|---|---|---|
GET /students/eligible-for-homeroom |
Department, curriculum, grade | noSelection, selected |
GET /students/eligible-for-subject-group |
Subject, grade | noSelection, pending, selected |
These routes require the class entity READ scope, admin/department-head role gate and policy-dimension admission. Services narrow candidates and anchors. Do not widen management pickers to every class reader.
Homeroom eligibility requires ENROLLED status, matching department/grade, compatible or absent curriculum selection and no occupied forward homeroom. Track is irrelevant; compatible partial selections are selected.
Course eligibility requires ENROLLED status, matching grade and compatible explicit curriculum/track. Missing values may be filled. Mandatory subjects bucket complete selections as selected, partial as pending. Optional subjects bucket an existing choice as selected, an admissible fill as pending.
Add only fills free choice capacity. It never replaces another alternative's choice, whatever the caller's authority: a single-pick block holding another alternative hides the student (WRONG_OPTION_BLOCK_CHOICE), and so does a full multi-pick block (OPTION_BLOCK_FULL). Qualified course composition WRITE plus selection WRITE can retarget a section of the same subject. Switching alternatives is Move within the block, or the selection editor. Move names the source and checks final structure/upper bounds after replacement; school-side under-fill remains valid.
The forward selection is pending when present, otherwise today's. Forward membership counts booked joins as occupied and booked leaves as released. Pickers are advisory; writes revalidate transactionally.
Student-first pickers¶
GET /students/:studentId/assignable-homerooms returns the two homeroom buckets. GET /students/:studentId/assignable-subject-groups returns a pruned department → grade → curriculum → tracks tree with subject buckets/course sections.
Both resolve a visible active-year student, policy-visible targets and the shared classifier. Historical/foreign/hidden anchors return generic 404. Combined classes are not roster targets; their member courses can appear.
Register literal /students/eligible-for-* routes before /students/:id. Preserve module import order as well as controller order.
5. Time, roster writes and history¶
POST/DELETE /:id/students add/remove; POST /:id/students/move moves. Homeroom moves require equal curriculum and grade. Course moves require equal grade and either the same subject or same non-null option block.
Every command uses one { on, tick } boundary for membership, selection and structural audit effects. Capture school time once.
Boundary resolution¶
src/common/temporal/resolve-same-day-boundary.ts owns the rule:
| Request | Result |
|---|---|
| Omitted date, pre-year or no affected lesson today | Whole-day today |
| Omitted date, affected lessons today | Next affected start at/after now, or current tick if none remains |
| Close/shrink with a later conflicting cell | Beyond that cell; next school day if no later affected slot remains |
| Explicit future date | Scheduled whole-day boundary |
| Explicit today for a close/shrink | Whole-day only before an affected lesson starts and without conflicting cells |
| Past date | TEMPORAL_BACKDATE_FORBIDDEN |
The affected set includes replacements/cascades. An Add closing another membership needs close/shrink protection.
The enrollment/schedule graph lock precedes other structural locks. Same-day close/shrink protection takes the exclusive AY-day lock before probing attendance. The boundary helper's pure-open path does not probe; caller-owned command locks still apply.
Day-level reads show end-of-day state. Register readers apply ticks per slot so a period retains its governing roster/teachers. Same-day ticks are not future pending changes.
Pending transitions¶
Writers allow one outstanding transition per membership identity. Administrative commands replace, re-date, re-target or cancel plans instead of stacking them. superseded[] reports displaced plans.
Inspect both opening and closing boundaries: a scheduled removal need not be an open-ended row. Cancelled additions retain empty [X, X) evidence and disappear from current/pending reads. A same-day empty day range with ordered ticks can instead be a lived micro-episode, retained by slot readers and delete guards.
Details expose pendingChanges[], and courses pendingTeacherChanges[], with display names. Derived counterparts pair adjacent same-date move legs within the same membership identity. Cross-subject moves may have null counterparts. Narrow visibility before counterpart enrichment.
Parent deletion locks and inspects lived child/content evidence. Earlier days use the calendar rule; today requires an affected started lesson or anchored register cell. Pre-year configuration can remain deletable with episodes effective today. Use assertParentDeletable; no current students does not imply no history. Header changes themselves are not temporal versions.
6. Selection reconciliation¶
The curriculum selection aggregate owns the study plan. Course commands intentionally change choices when qualified authority permits it.
course-roster-plan.ts plans changes; course-roster-reconciliation.ts loads/locks evidence and executes them. src/students/class-selection-sync.ts owns selection planning/persistence.
- Add into a single-pick block refuses a student who still holds another alternative's membership, even one the selection doesn't reflect. A sibling membership released by a booked removal is closed at the Add's date.
- Same-block Move replaces its named source and retains unrelated choices.
- Last optional membership removal can clear the choice.
- Same-subject section moves, retained destinations and cancelled pending joins must not duplicate or unnecessarily remove choices.
- Explicit curriculum/track conflicts remain invalid; only missing values are initialized.
- Batch contradictions reject regardless of item order.
- Structural/upper-limit failures reject; school-side under-fill may remain incomplete.
Replacement authority resolves course WRITE for all affected courses and selection WRITE for the student/target curriculum independently. READ parameters and unrelated assignments cannot lend authority.
Homeroom fill and its mandatory course cascade use applyClassAssignmentToSelection. Derived closes are not recursively interpreted as explicit choice removal. Compatible pending selections survive; replaced incompatible plans are reported.
Selection-editor PATCH uses pruneIncompatibleClassMemberships and returns removed IDs. Whole selection and roster versions share the boundary; lived choices are not overwritten.
Completeness and grid edits¶
isComplete materializes consistency; confirmedAt is the referent lock. Referent writes must be complete and lock on success. Authorized school editors can save partial results without hard structural/upper violations. deriveSelectionStatus returns NOT_STARTED/IN_PROGRESS/COMPLETE. A relaxed rule may yield a complete but unlocked selection.
Grid identity is ID-or-key within a container. Round-trip IDs for renames to preserve choices/carry keys; container moves change identity.
The sweep compares committed substance before/after edits, updates completeness and releases confirmation for broken completeness or changed chosen levels. Mandatory changes are reported separately; cosmetic edits do not inherently invalidate. CurriculumService logs sweep change notices, so do not assume each descriptor has a user-delivery consumer.
7. Links and creation from a homeroom¶
A linked course must be mandatory and match the homeroom's curriculum, grade and year. Multiple linked sections for one subject are allowed. Changing the link performs no roster writes; homeroom deletion clears links through SetNull.
planLinkedCourseCascade runs within homeroom roster commands:
- Leaving closes forward memberships in source-linked courses.
- Entering adds explicitly belonging, eligible students to unambiguous target courses.
- Failed automatic placement is skipped per course, not a failure of the entire homeroom command.
- The shared boundary and protected attendance set include cascade effects.
Skip reasons are ALREADY_IN_COURSE_FOR_SUBJECT, AMBIGUOUS_LINKED_COURSES, TRACK_NOT_HELD, NOT_ELIGIBLE. Responses report courseCascade[] and superseded plans. Explicit belonging is shared with blueprint counts. Direct course roster writes do not require matching the linked class.
Blueprint and batch¶
GET /homerooms/:id/course-blueprint returns the visible class/roster and offered common/track subjects/blocks. Units are excluded. Subjects include existing sections and selected counts based on explicit choice/track belonging, minus occupied forward memberships. Zero permits shell creation.
POST /subject-groups/from-homeroom derives active year, grade and curriculum from the class:
| Item | Behavior |
|---|---|
No subjectGroupId |
Create, with required name and optional teachers/room/custom fields |
With subjectGroupId |
Merge roster; preserve metadata; reject teacher/room/custom-field changes |
The batch rechecks state, plans admitted items together and executes atomically. Already-forward-placed students can be rerun skips; booked leaves can be replaced. Empty CREATE rosters still create shells. Response sections are created[]/updated[] with per-item skip/replacement information.
Mandatory CREATE items link to the source class; optional ones stay unlinked. MERGE preserves the target link.
8. Lists and coverage¶
Tables¶
/homerooms/table and /subject-groups/table return paginated headers/counts, not rosters. The course table includes COMBINED_CLASS rows with a kind discriminator.
| Dimension | Homerooms | Courses |
|---|---|---|
| Structure | Department, curriculum, grade, homeroom | Department, curriculum, grade, subject, option block, effective track |
| People | Tutor, roster student | Teacher |
| Homeroom relation | Class identity | Stored link OR effective roster overlap |
| Search | Class name | Course/subject name; combination/member-subject name |
Values within a dimension union; different dimensions intersect. Search/policy/year are additional AND restrictions. Unknown IDs produce no matches. Sorting uses whitelisted columns and stable tie-breakers.
Effective track is subject track, otherwise block track. Track filters exclude Common unless includeCommonTrack is true. Option-block filters exclude mandatory subjects. Combined dimensions resolve through current authorized members; different selected dimensions can match different members.
Grouped boards and gaps¶
/homerooms/grouped-homerooms and /subject-groups/grouped-courses remain available, marked deprecated in Swagger. Their catalogue skeletons include empty configured nodes and DRAFT/READY curricula.
Homerooms group department → curriculum → grade. Courses group department → grade → curriculum → track, with leaves under mandatory/block subject coverage. Student counts apply independent student visibility; services assert student read permission in addition to the class route gate. Board payloads contain no student rows.
Coverage compares a track-filtered target plan with assignments; out-of-plan rows cannot inflate it. Minimum configured course counts are floors, not exact section requirements.
GET /subject-groups/by-subject/:curriculumSubjectId returns { subjectGroups, combinedClasses }. Optional anchors expand to their block, mandatory anchors to their exact subject; results span grades within the read year. A combined result needs a current member both matching and visible. This does not grant cross-grade Move authority.
/subject-groups/missing-courses reports offered subject/grade cells without any course. One course clears the gap regardless of roster. It is manager-gated, catalogue-only and requires no student READ. It shares the table DTO but applies only relevant catalogue dimensions, not teacher/homeroom/roster identity or pagination/sort.
Placement readiness¶
These name-ordered paginated student companions require class READ plus student identity/assignment READ:
| Route | Worklist |
|---|---|
/homerooms/table/unassigned-students |
ENROLLED students without forward homeroom membership |
/subject-groups/table/students-with-placement-gaps |
ENROLLED students with incomplete settled selection or missing forward mandatory/optional coverage |
Booked joins count as placed; booked leaves expose gaps. Coverage/display curriculum use the forward selection head. Lifecycle uses complete-wins pickSettledSelection, so current COMPLETE with a pending partial target can still show COMPLETE while target coverage has gaps.
Non-student filter dimensions contribute no rows when selected; do not reinterpret them as student ownership. Use the same predicate for page and count.
Dashboard counters (no curriculum, no homeroom, missing mandatory, missing optional) are overlapping distinct-student predicates, not additive totals. Actual course membership counts even when the course is combined.
9. Filter options¶
src/filters/ owns browsing/work-context options. Every request requires an endpoint-supported surface; missing/unsupported values are 400.
| Endpoint | Surfaces | Upstream narrowing |
|---|---|---|
| departments | curricula, students, homerooms, subject-groups, teachers, timetables | None |
| grades | students, homerooms, subject-groups, teachers, timetables | Department |
| curricula | students, homerooms, subject-groups, teachers, timetables | Department, grade |
| tracks | students, subject-groups | Curriculum |
| option-blocks | subject-groups | Curriculum |
| subjects | subject-groups | Required single curriculum; grade, track, Common |
| homerooms | students, homerooms, subject-groups, teachers, timetables | Department, grade, curriculum |
| rooms | homerooms, subject-groups, timetables | None |
| teachers | homerooms, subject-groups, timetables | Search, pagination |
| students | students, homerooms, timetables | Department, grade, search, pagination |
Population is the actual tenant/year targets intersected with the union of independently authorized structure and values projected from visible rows. Structural cascades narrow only the independent limb; reapplying them to projections can remove valid combined/roster-derived options. Options grant neither source visibility nor write eligibility.
Structure-authority companions live beside source policies. filter-context.queries.ts validates real anchors. Department parameters reach their structure; curriculum parameters reach only assigned curricula and linked grades/children/classes. A parent department does not grant sibling curricula.
Broad readers can see shared rooms. Narrow readers require a real authorized/projected department; stale/foreign/wrong-year IDs cannot unlock them.
Structural responses are bare { data }; people responses are paginated name-only items and intersect the target person policy across the whole union. Readable classes never disclose hidden classmates. Teacher surface follows the teacher table's employment/effective teaching/tutoring projection, not a course-table proxy.
Most endpoints accept a read year. Subjects is active-year-only with a single curriculum anchor. Timetable surfaces require a readable timetableId; its year owns the options, and a conflicting explicit year is 404. Timetable structural/teacher options do not require placed slots; students remain visible ENROLLED display-cohort candidates.
Clients clear incompatible downstream selections after upstream/authority changes. Empty results alone do not invalidate options. Staff and mutation forms use their own catalogues rather than invented filter surfaces.
10. Teachers, assistance and lesson subsets¶
Dated SubjectGroupTeacher episodes affect both display and authorization. Query them on the business date; an open-ended future row is not current. Teacher-list replacement closes/opens on one boundary. Reordering does not move intervals; a boundary-free edit rejects irrelevant validFrom.
Teaching/tutoring writes materialize TeacherDepartment through ensureTeacherDepartmentLinks. Employment edits refuse removing a department required by active/future teaching or current tutorship. Setup import preserves those required links. Ending a tie does not automatically remove employment membership.
Materialization and removal guards are service-level paths; do not assume a database trigger serializes them.
Assistance contributes to dated course/student visibility and filter projections without creating ordinary teaching episodes or student memberships. Caller assistance authority and projected assisting-teacher options are distinct facts; option projection is not limited to the caller's shifts.
Ordinary teachers remain the course-level pool. Scheduled lessons choose ALL or SELECTED teachers for actual occupancy, attendance and budgets. A selected teacher losing eligibility remains in the saved selection and produces diagnostics rather than silently widening. See timetables.
11. Combined classes¶
A combination owns shared lessons; each lesson has exactly one course/combination anchor. Members can span grades but share a department and teaching window.
Create/update validates at least two members, existence/year, write reach, forward occupancy, shared department, offered cells, shared hours within each cell, and resolved duration compatibility/divisibility. Base-room defaults need not match. Optional courses are admissible.
Solo hours equal each cell's hours minus shared hours. Edit curriculum hours at source rather than storing contradictory course quotas.
CombinedClassMembership is dated; SubjectGroup.combinedClassId is the terminal planned head, updated with episodes. A scheduled leave releases it for a same-date join elsewhere while current reads retain the earlier episode until the boundary.
Table aggregates use distinct student union and deduplicated teachers. Curriculum trees retain member badges. Grades remain per course.
A student may belong to multiple contributors without a consent flag. A combined occurrence is one physical commitment, while each course retains its obligations. Combined attendance uses the physical combination anchor; attendance owns recording semantics.
Below two members, a combination remains inert and needs attention. Diagnostics are WARNING without a judged shared lesson and ERROR when shared lessons depend on it. History deletion guards still apply.
12. Timetable review and notifications¶
Structural roster/selection/teacher saves retain normal success responses when scheduling conflicts result. They do not evaluate the timetable inside the domain write.
Actual scheduling-input changes mark tenant/year/date. After the outer commit, the interceptor may return X-Timetable-Review-Ids for official current/future versions. The lookup is bounded and fail-soft. Clients finish the save then fetch diagnostics; no header is not a health guarantee. Publication still rejects ERRORs.
Preserve graph lock order to serialize structural writes with publication/generation ingestion. Metadata-only and true no-op commands do not need review marks.
Notifications derive from actual episode deltas:
- New teaching episodes notify active linked teachers; stayers/reorders/removals are not joiners.
- Roster adds/removes/moves notify affected tutors or dated course teachers.
- Linked-course cascade effects contribute once per direction.
- Initial roster formation, import, rollover, unrelated reconciliation, deletion and cancelled transitions are excluded from movement notifications.
Resolve recipients/context in the transaction and register immutable intents with PostCommitCoordinator. Rollback drops them; fail-soft inbox/push delivery occurs after the real commit.
13. Extension checklist¶
- Read controller/DTO, policy, service and focused helpers.
- For fields, update storage, scope, DTO and projection together; follow migrations.
- Reuse classifier and dated-loader semantics between pickers and writes; preserve route order.
- Plan final effects, qualify authority, keep graph/day/row lock order and resolve one boundary.
- Protect history and selection consistency; never patch denormalized heads independently.
- Keep side-effect intent transactional and delivery post-commit.
- Update this contract in place without dated implementation addenda.
Entry points: the domain services, course-roster-reconciliation.ts, linked-course-cascade.ts, from-homeroom/, combined-classes/ and filters.service.ts.