Files
breakpilot-lehrer/school-service/internal/models/timetable_constraints.go
T
Benjamin Admin e958f88a2d Add timetable scheduler Phases 1 + 2 to school-service
Phase 1 — Stammdaten (7 tables):
  tt_class, tt_period, tt_room, tt_subject, tt_teacher,
  tt_curriculum, tt_assignment with CRUD endpoints.

Phase 2 — Constraints (15 typed tables):
  Teacher (6): unavailable_day, unavailable_window, max_hours_day,
    max_hours_week, excluded_subject, excluded_room
  Subject (5): min_day_gap, max_consecutive, contiguous_when_repeated,
    preferred_period, double_lesson
  Class (2): max_hours_day, no_gaps
  Room (2): requires_type, unavailable

Each constraint row carries is_hard / weight / active / note /
created_by_user_id; ownership enforced via WHERE EXISTS against the
parent tt_teacher/tt_class/tt_subject/tt_room row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:12:23 +02:00

361 lines
15 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// Each constraint table carries the same audit/policy columns:
// - is_hard: true = solver must satisfy, false = soft (weighted)
// - weight: higher = stronger penalty when violated (used for soft constraints)
// - active: allows toggling a rule off without deletion
// - note: free-text rationale ("Lehrer X im Rollstuhl")
// - created_by_user_id: the Rektor account that owns this rule
// ---------- Teacher constraints (6) ----------
// TeacherUnavailableDay: Lehrer kann an Wochentag NIE.
type TeacherUnavailableDay struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
DayOfWeek int `json:"day_of_week" db:"day_of_week"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// TeacherUnavailableWindow: Lehrer kann an Tag X von HH:MM bis HH:MM nicht.
type TeacherUnavailableWindow struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
DayOfWeek int `json:"day_of_week" db:"day_of_week"`
StartTime string `json:"start_time" db:"start_time"`
EndTime string `json:"end_time" db:"end_time"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// TeacherMaxHoursDay: Lehrer darf max. N Stunden pro Tag haben.
type TeacherMaxHoursDay struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
MaxHours int `json:"max_hours" db:"max_hours"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// TeacherMaxHoursWeek: Teilzeit-Cap.
type TeacherMaxHoursWeek struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
MaxHours int `json:"max_hours" db:"max_hours"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// TeacherExcludedSubject: Lehrer darf bestimmtes Fach nicht unterrichten.
type TeacherExcludedSubject struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// TeacherExcludedRoom: Lehrer kann Raum nicht nutzen (z.B. kein Aufzug).
type TeacherExcludedRoom struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
RoomID uuid.UUID `json:"room_id" db:"room_id"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ---------- Subject constraints (5) ----------
// SubjectMinDayGap: Mindestens N Tage Abstand zwischen zwei Lessons desselben Fachs.
type SubjectMinDayGap struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
MinGapDays int `json:"min_gap_days" db:"min_gap_days"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// SubjectMaxConsecutive: Max. N aufeinander folgende Stunden des Fachs (keine Tripel-Stunde).
type SubjectMaxConsecutive struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
MaxConsecutive int `json:"max_consecutive" db:"max_consecutive"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// SubjectContiguousWhenRepeated: Wenn das Fach mehrfach am gleichen Tag stattfindet, dann nur als Block.
type SubjectContiguousWhenRepeated struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// SubjectPreferredPeriod: Fach lieber in einem bestimmten Period-Bereich (z.B. Hauptfächer morgens).
type SubjectPreferredPeriod struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
PeriodFrom int `json:"period_from" db:"period_from"`
PeriodTo int `json:"period_to" db:"period_to"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// SubjectDoubleLesson: Fach bevorzugt als Doppelstunde.
type SubjectDoubleLesson struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ---------- Class constraints (2) ----------
// ClassMaxHoursDay: Klasse darf max. N Stunden pro Tag haben.
type ClassMaxHoursDay struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
ClassID uuid.UUID `json:"class_id" db:"class_id"`
MaxHours int `json:"max_hours" db:"max_hours"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ClassNoGaps: Keine Freistunden für die Klasse zwischen Lessons (Soft-Standard).
type ClassNoGaps struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
ClassID uuid.UUID `json:"class_id" db:"class_id"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ---------- Room constraints (2) ----------
// RoomRequiresType: Fach benötigt einen bestimmten Raumtyp (Sport → Sporthalle).
type RoomRequiresType struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
RoomType string `json:"room_type" db:"room_type"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// RoomUnavailable: Raum an Tag X, Stunde Y blockiert (Wartung, Renovierung).
type RoomUnavailable struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
RoomID uuid.UUID `json:"room_id" db:"room_id"`
DayOfWeek int `json:"day_of_week" db:"day_of_week"`
PeriodIndex int `json:"period_index" db:"period_index"`
IsHard bool `json:"is_hard" db:"is_hard"`
Weight int `json:"weight" db:"weight"`
Active bool `json:"active" db:"active"`
Note string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ---------- Request DTOs ----------
// Teacher
type CreateTeacherUnavailableDayRequest struct {
TeacherID string `json:"teacher_id" binding:"required,uuid"`
DayOfWeek int `json:"day_of_week" binding:"required,min=1,max=7"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateTeacherUnavailableWindowRequest struct {
TeacherID string `json:"teacher_id" binding:"required,uuid"`
DayOfWeek int `json:"day_of_week" binding:"required,min=1,max=7"`
StartTime string `json:"start_time" binding:"required"`
EndTime string `json:"end_time" binding:"required"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateTeacherMaxHoursDayRequest struct {
TeacherID string `json:"teacher_id" binding:"required,uuid"`
MaxHours int `json:"max_hours" binding:"required,min=1,max=12"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateTeacherMaxHoursWeekRequest struct {
TeacherID string `json:"teacher_id" binding:"required,uuid"`
MaxHours int `json:"max_hours" binding:"required,min=1,max=40"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateTeacherExcludedSubjectRequest struct {
TeacherID string `json:"teacher_id" binding:"required,uuid"`
SubjectID string `json:"subject_id" binding:"required,uuid"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateTeacherExcludedRoomRequest struct {
TeacherID string `json:"teacher_id" binding:"required,uuid"`
RoomID string `json:"room_id" binding:"required,uuid"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
// Subject
type CreateSubjectMinDayGapRequest struct {
SubjectID string `json:"subject_id" binding:"required,uuid"`
MinGapDays int `json:"min_gap_days" binding:"required,min=1,max=4"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateSubjectMaxConsecutiveRequest struct {
SubjectID string `json:"subject_id" binding:"required,uuid"`
MaxConsecutive int `json:"max_consecutive" binding:"required,min=1,max=5"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateSubjectContiguousWhenRepeatedRequest struct {
SubjectID string `json:"subject_id" binding:"required,uuid"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateSubjectPreferredPeriodRequest struct {
SubjectID string `json:"subject_id" binding:"required,uuid"`
PeriodFrom int `json:"period_from" binding:"required,min=1,max=12"`
PeriodTo int `json:"period_to" binding:"required,min=1,max=12"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateSubjectDoubleLessonRequest struct {
SubjectID string `json:"subject_id" binding:"required,uuid"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
// Class
type CreateClassMaxHoursDayRequest struct {
ClassID string `json:"class_id" binding:"required,uuid"`
MaxHours int `json:"max_hours" binding:"required,min=1,max=12"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateClassNoGapsRequest struct {
ClassID string `json:"class_id" binding:"required,uuid"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
// Room
type CreateRoomRequiresTypeRequest struct {
SubjectID string `json:"subject_id" binding:"required,uuid"`
RoomType string `json:"room_type" binding:"required"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}
type CreateRoomUnavailableRequest struct {
RoomID string `json:"room_id" binding:"required,uuid"`
DayOfWeek int `json:"day_of_week" binding:"required,min=1,max=7"`
PeriodIndex int `json:"period_index" binding:"required,min=1,max=12"`
IsHard bool `json:"is_hard"`
Weight int `json:"weight" binding:"min=0,max=100"`
Active bool `json:"active"`
Note string `json:"note"`
}