From ee653c274ff295222a1eb3c0031b3d08cb83565d Mon Sep 17 00:00:00 2001 From: sparsetable Date: Sat, 31 Jan 2026 17:49:00 +0000 Subject: [PATCH] Preliminary schema --- campus/model/timetable.dbml | 103 ++++++++++++++++++++++++++++ campus/model/timetable.py | 129 ++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 campus/model/timetable.dbml create mode 100644 campus/model/timetable.py diff --git a/campus/model/timetable.dbml b/campus/model/timetable.dbml new file mode 100644 index 00000000..28241db7 --- /dev/null +++ b/campus/model/timetable.dbml @@ -0,0 +1,103 @@ +// For all, id is a CampusID with no relation to raw XML ID. + +// An allocation refers to a new set of classes, people and class timings +// imported with a new timetable. +// It refers to one of these: +// https://github.com/nyjc-computing/nyxchange-timetable-v2/blob/data-schema/tt_file/tt_xml_docs.md +// This year's timetable and last year's timetable are two seperate allocations. +// The data for an allocation is stored in some .xml file, named `filename` + +/* Sections that stay constant or only have additions. */ +/* They will be relevant every for every allocation. */ + +// Describes a day in a repeating timetable. +// Assumption: This will stay constant across all timetables. +Table WeekDay { + id varchar [primary key] + label varchar // (cosmetic purposes: 'Mon A', 'Tue A', ... 'Mon B', 'Tue B', ..., 'Sat', 'Sun') + index integer [unique] // index 0 is earliest (eg. Mon A), followed by Tues A, etc. +} + +// Timeslot which repeats across all `WeekDay`s +// Assumption: This will stay constant across all timetables. +Table TimeSlot { + id varchar [primary key] + label varchar // (primarily cosmetic: '0730', '0800', ...) + start_time varchar [unique] // ISO8601 + end_time varchar [unique] // ISO8601 + index integer [unique] // index 0 is slot (eg. 0730), followed by 0800, etc. +} + +// Describes a single venue +// We have a set of venues that remain across years, except additions. +// Cannot refer to a group (eg. "All science labs") +Table Venue { + id varchar [primary key] + label varchar // (mutable, e.g. '03-39', 'i-Space 1', ..., follow XML when possible) +} + +// Imagine each venue has its own timetable. +// This is one timeslot on such a timetable, representing an intersection of +// Venue and TimeSlot +// We use it for clean and convenient timetable coordinates, as +// we usually reason about an intersection of venue and time anyway +// Must be automatically generated for each Venue for all WeekDay, TimeSlot. +Table VenueTimeSlot { + id varchar [primary key] + weekday_id varchar + timeslot_id varchar + venue_id varchar + indexes { + (weekday_id, timeslot_id, venue_id) [unique] + } +} +Ref: VenueTimeSlot.weekday_id > WeekDay.id +Ref: VenueTimeSlot.timeslot_id > TimeSlot.id +Ref: VenueTimeSlot.venue_id > Venue.id + +/* Sections that are only relevant to a specific allocation. */ +/* New entries are created for each allocation. */ +/* Which allocation the entry is relevant to is . */ + +// This represents a specific lesson taught to a class. +// Eg. Chem, taught to 2510. (eg. stored as '2510-CM') +// These are stored as an inconsistently formatted string, imported +// directly from the XML. +Table LessonGroup { + id varchar [primary key] + filename varchar // Allocation xml filename which this group is relevant to + label varchar +} + +// Represents a single member of a LessonGroup. +// For example, 2510-Math will have: +// - Rosaline Tan +// - All the students +// Where each individual is one LessonGroupMember. +// 2510-Chem will have its own set of entries, even if the participant is duplicated. +Table LessonGroupMember { + id varchar [primary key] + filename varchar + lessongroup_id varchar + // XML id (aka TTCode or teacher_id). We have a unique mapping of these IDs + // to nyjc email etc., for each allocation. + ade_participant varchar + indexes { + (lessongroup_id, ade_participant, filename) [unique] + } +} +Ref: LessonGroup.id < LessonGroupMember.lessongroup_id + +// A timetable represents a lesson for a LessonGroup +// at some VenueTimeSlot +Table Timetable { + id varchar [primary key] + filename varchar + lessongroup_id varchar + venuetimeslot_id varchar + indexes { + (venuetimeslot_id, lessongroup_id, filename) [unique] + } +} +Ref: Timetable.lessongroup_id > LessonGroup.id +Ref: Timetable.venuetimeslot_id > VenueTimeSlot.id \ No newline at end of file diff --git a/campus/model/timetable.py b/campus/model/timetable.py new file mode 100644 index 00000000..0f37cff4 --- /dev/null +++ b/campus/model/timetable.py @@ -0,0 +1,129 @@ +"""campus.model.timetable + +Timetable model definitions for Campus. + +The timetable schema describes a schema that stores the +following information from a timetable: +- Timeslots +- Lessons +- Students and teachers involved in said lessons +- Venues + +!! The schema is documented in DETAIL by the `timetable.dbml` file. +!! Commenting both files would mean duplicate documentation. +!! Please visualise it [here](https://dbml-editor.alswl.com/)!!!! +!! The tool is also able to export the schema as SQL to create the respective tables. +""" + +from typing import ClassVar +from dataclasses import dataclass, field + +from campus.common import schema +from campus.common.schema.openapi import String, Integer, Boolean, DateTime +from campus.common.utils import uid + +from .base import Model +from . import constraints + +# NOTE: Assumes reusing the same object is not an issue +unique_field = field(metadata={ + "constraints": [constraints.UNIQUE], +}) + + +@dataclass(eq=False, kw_only=True) +class WeekDay(Model): + """ + Describes a day in a repeating timetable. + """ + id: schema.CampusID = unique_field + label: String + index: Integer + + +@dataclass(eq=False, kw_only=True) +class TimeSlot(Model): + """ + Timeslot which repeats across all `WeekDay`s + """ + id: schema.CampusID = field(default_factory=( + lambda: uid.generate_category_uid("timetable", length=8) + )) + label: String + start_time: DateTime = unique_field + end_time: DateTime = unique_field + index: Integer = unique_field + + +@dataclass(eq=False, kw_only=True) +class Venue(Model): + """ + Describes a single venue + """ + id: schema.CampusID = field(default_factory=( + lambda: uid.generate_category_uid("timetable", length=8) + )) + label: String + + +@dataclass(eq=False, kw_only=True) +class VenueTimeSlot(Model): + """ + Imagine each venue has its own timetable. + This is one timeslot on such a timetable, representing an intersection of + Venue and TimeSlot + """ + id: schema.CampusID = field(default_factory=( + lambda: uid.generate_category_uid("timetable", length=8) + )) + weekday_id: schema.CampusID # FK -> weekday_id + timeslot_id: schema.CampusID + venue_id: schema.CampusID + __constraints__ = constraints.Unique("weekday_id", "timeslot_id", "venue_id") + + +@dataclass(eq=False, kw_only=True) +class LessonGroup(Model): + """ + This represents a specific lesson taught to a class. + Eg. Chem, taught to 2510. (eg. stored as '2510-CM') + """ + id: schema.CampusID = field(default_factory=( + lambda: uid.generate_category_uid("timetable", length=8) + )) + filename: String + label: String + + +@dataclass(eq=False, kw_only=True) +class LessonGroupMember(Model): + """ + Represents a single member of a LessonGroup. + For example, 2510-Math will have: + - Rosaline Tan + - All the students + Where each individual is one LessonGroupMember. + 2510-Chem will have its own set of entries, even if the participant is duplicated. + """ + id: schema.CampusID = field(default_factory=( + lambda: uid.generate_category_uid("timetable", length=8) + )) + filename: String + lessongroup_id: String + ade_participant: String + __constraints__ = constraints.Unique("lessongroup_id", "ade_participant", "filename") + + +@dataclass(eq=False, kw_only=True) +class Timetable(Model): + """ + A timetable represents a lesson for a LessonGroup + at some VenueTimeSlot + """ + id: schema.CampusID = field(default_factory=( + lambda: uid.generate_category_uid("timetable", length=8) + )) + filename: String + lessongroup_id: String + venuetimeslot_id: String + __constraints__ = constraints.Unique("lessongroup_id", "venuetimeslot_id", "filename")