From 956b6f60121c0a18ba4296ce705fe8012293a5d7 Mon Sep 17 00:00:00 2001 From: sparsetable Date: Tue, 3 Feb 2026 03:27:05 +0000 Subject: [PATCH 1/4] Migrate documentation to timetable.py --- campus/model/timetable.py | 68 ++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/campus/model/timetable.py b/campus/model/timetable.py index 0f37cff4..3193b458 100644 --- a/campus/model/timetable.py +++ b/campus/model/timetable.py @@ -8,11 +8,6 @@ - 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 @@ -30,40 +25,57 @@ "constraints": [constraints.UNIQUE], }) +# 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. ## + @dataclass(eq=False, kw_only=True) class WeekDay(Model): """ Describes a day in a repeating timetable. + Assumption: This will stay constant across all allocations. """ id: schema.CampusID = unique_field - label: String - index: Integer + label: String # (cosmetic purposes: 'Mon A', 'Tue A', ... 'Mon B', 'Tue B', ..., 'Sat', 'Sun') + index: Integer # index 0 is earliest (eg. Mon A), followed by Tues A, etc. @dataclass(eq=False, kw_only=True) class TimeSlot(Model): """ Timeslot which repeats across all `WeekDay`s + Assumption: This will stay constant across all allocations. """ 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 + label: String # (primarily cosmetic: '0730', '0800', ...) + start_time: DateTime = unique_field # ISO8601 + end_time: DateTime = unique_field # ISO8601 + index: Integer = unique_field # index 0 is slot (eg. 0730), followed by 0800, etc. @dataclass(eq=False, kw_only=True) class Venue(Model): """ - Describes a single venue + 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"). """ id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - label: String + label: String # (e.g. '03-39', 'i-Space 1', ..., follow XML when possible) @dataclass(eq=False, kw_only=True) @@ -72,26 +84,34 @@ 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 + 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. """ 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 + weekday_id: schema.CampusID # FK -> WeekDay.id + timeslot_id: schema.CampusID # FK -> TimeSlot.id + venue_id: schema.CampusID # FK -> Venue.id __constraints__ = constraints.Unique("weekday_id", "timeslot_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 . ## @dataclass(eq=False, kw_only=True) class LessonGroup(Model): """ - This represents a specific lesson taught to a class. + This represents a specific subject taught to a class. Eg. Chem, taught to 2510. (eg. stored as '2510-CM') + These are labelled as an inconsistently formatted string, imported + directly from the XML. It seems we cannot seperate class and subject. """ id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - filename: String + filename: String # Allocation xml filename which this group is relevant to label: String @@ -109,7 +129,9 @@ class LessonGroupMember(Model): lambda: uid.generate_category_uid("timetable", length=8) )) filename: String - lessongroup_id: String + lessongroup_id: String # Ref -> LessonGroup.id + # ade -> XML id (aka TTCode or teacher_id). We have a unique mapping of these IDs + # to nyjc email etc., for each allocation. ade_participant: String __constraints__ = constraints.Unique("lessongroup_id", "ade_participant", "filename") @@ -117,13 +139,13 @@ class LessonGroupMember(Model): @dataclass(eq=False, kw_only=True) class Timetable(Model): """ - A timetable represents a lesson for a LessonGroup - at some VenueTimeSlot + A timetable represents a single 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 + lessongroup_id: String # Ref -> LessonGroup.id + venuetimeslot_id: String # Ref -> VenueTimeSlot.id __constraints__ = constraints.Unique("lessongroup_id", "venuetimeslot_id", "filename") From 880c90e731659922732b09e5a4a51964e20ad789 Mon Sep 17 00:00:00 2001 From: sparsetable Date: Tue, 3 Feb 2026 03:27:26 +0000 Subject: [PATCH 2/4] Delete timetable.dbml --- campus/model/timetable.dbml | 103 ------------------------------------ 1 file changed, 103 deletions(-) delete mode 100644 campus/model/timetable.dbml diff --git a/campus/model/timetable.dbml b/campus/model/timetable.dbml deleted file mode 100644 index 28241db7..00000000 --- a/campus/model/timetable.dbml +++ /dev/null @@ -1,103 +0,0 @@ -// 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 From 22ec35166424f2794975d1a0957d27523cdc490c Mon Sep 17 00:00:00 2001 From: sparsetable Date: Tue, 3 Feb 2026 03:35:53 +0000 Subject: [PATCH 3/4] Docstring documentation --- campus/model/timetable.py | 84 ++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/campus/model/timetable.py b/campus/model/timetable.py index 3193b458..7985a16f 100644 --- a/campus/model/timetable.py +++ b/campus/model/timetable.py @@ -8,13 +8,23 @@ - Lessons - Students and teachers involved in said lessons - Venues + +For all models, 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` + +TODO: Update doc link after migration """ -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.schema.openapi import String, Integer, DateTime from campus.common.utils import uid from .base import Model @@ -25,29 +35,22 @@ "constraints": [constraints.UNIQUE], }) -# 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. ## - @dataclass(eq=False, kw_only=True) class WeekDay(Model): """ Describes a day in a repeating timetable. Assumption: This will stay constant across all allocations. + + Fields: + label (String): (cosmetic purposes: 'Mon A', 'Tue A', ... 'Mon B', 'Tue B', ..., 'Sat', 'Sun') + index (Integer): index 0 is earliest (eg. Mon A), followed by Tues A, etc. """ id: schema.CampusID = unique_field - label: String # (cosmetic purposes: 'Mon A', 'Tue A', ... 'Mon B', 'Tue B', ..., 'Sat', 'Sun') - index: Integer # index 0 is earliest (eg. Mon A), followed by Tues A, etc. + label: String + index: Integer @dataclass(eq=False, kw_only=True) @@ -55,14 +58,20 @@ class TimeSlot(Model): """ Timeslot which repeats across all `WeekDay`s Assumption: This will stay constant across all allocations. + + Fields: + label (String): (primarily cosmetic: '0730', '0800', ...) + start_time (DateTime): ISO8601 + end_time (DateTime): ISO8601 + index (Integer): index 0 is earliest slot (eg. 0730), followed by 0800 at idx 1, etc. """ id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - label: String # (primarily cosmetic: '0730', '0800', ...) + label: String start_time: DateTime = unique_field # ISO8601 end_time: DateTime = unique_field # ISO8601 - index: Integer = unique_field # index 0 is slot (eg. 0730), followed by 0800, etc. + index: Integer = unique_field @dataclass(eq=False, kw_only=True) @@ -71,11 +80,14 @@ class Venue(Model): 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"). + + Fields: + label (String): (e.g. '03-39', 'i-Space 1', ..., follow XML when possible) """ id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - label: String # (e.g. '03-39', 'i-Space 1', ..., follow XML when possible) + label: String @dataclass(eq=False, kw_only=True) @@ -87,13 +99,18 @@ class VenueTimeSlot(Model): 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. + + Fields: + weekday_id (CampusID): FK referencing a WeekDay.id + timeslot_id (CampusID): FK referencing a TimeSlot.id + venue_id (CampusID): FK referencing a Venue.id """ 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 # FK -> TimeSlot.id - venue_id: schema.CampusID # FK -> Venue.id + weekday_id: schema.CampusID + timeslot_id: schema.CampusID + venue_id: schema.CampusID __constraints__ = constraints.Unique("weekday_id", "timeslot_id", "venue_id") ## Sections that are only relevant to a specific allocation. ## @@ -107,11 +124,15 @@ class LessonGroup(Model): Eg. Chem, taught to 2510. (eg. stored as '2510-CM') These are labelled as an inconsistently formatted string, imported directly from the XML. It seems we cannot seperate class and subject. + + Fields: + filename (String): Allocation xml filename which this entry is relevant to + label (String): Label brought over from xml, like 2527-COM """ id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - filename: String # Allocation xml filename which this group is relevant to + filename: String label: String @@ -124,14 +145,18 @@ class LessonGroupMember(Model): - All the students Where each individual is one LessonGroupMember. 2510-Chem will have its own set of entries, even if the participant is duplicated. + + Fields: + filename (String): Allocation xml filename which this entry is relevant to + lessongroup_id (CampusID): FK referencing a LessonGroup.id + ade_participant (String): XML id (aka TTCode or teacher_id). We have a unique mapping of these IDs + to nyjc email etc., for each allocation. """ id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) filename: String - lessongroup_id: String # Ref -> LessonGroup.id - # ade -> XML id (aka TTCode or teacher_id). We have a unique mapping of these IDs - # to nyjc email etc., for each allocation. + lessongroup_id: schema.CampusID ade_participant: String __constraints__ = constraints.Unique("lessongroup_id", "ade_participant", "filename") @@ -141,11 +166,16 @@ class Timetable(Model): """ A timetable represents a single lesson for a LessonGroup at some VenueTimeSlot. + + Fields: + filename (String): Allocation xml filename which this entry is relevant to + lessongroup_id (CampusID): FK referencing a LessonGroup.id + venuetimeslot_id (CampusID): FK referencing a VenueTimeSlot.id """ id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) filename: String - lessongroup_id: String # Ref -> LessonGroup.id - venuetimeslot_id: String # Ref -> VenueTimeSlot.id + lessongroup_id: schema.CampusID + venuetimeslot_id: schema.CampusID __constraints__ = constraints.Unique("lessongroup_id", "venuetimeslot_id", "filename") From 2c4850172d714aa0a8da7ec9f5d48b5936de83cd Mon Sep 17 00:00:00 2001 From: JS Ng Date: Tue, 3 Feb 2026 04:02:04 +0000 Subject: [PATCH 4/4] format: switch to namespace import --- campus/model/timetable.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/campus/model/timetable.py b/campus/model/timetable.py index 7985a16f..5fd8331e 100644 --- a/campus/model/timetable.py +++ b/campus/model/timetable.py @@ -24,7 +24,6 @@ from dataclasses import dataclass, field from campus.common import schema -from campus.common.schema.openapi import String, Integer, DateTime from campus.common.utils import uid from .base import Model @@ -49,8 +48,8 @@ class WeekDay(Model): index (Integer): index 0 is earliest (eg. Mon A), followed by Tues A, etc. """ id: schema.CampusID = unique_field - label: String - index: Integer + label: schema.String + index: schema.Integer @dataclass(eq=False, kw_only=True) @@ -68,10 +67,10 @@ class TimeSlot(Model): id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - label: String - start_time: DateTime = unique_field # ISO8601 - end_time: DateTime = unique_field # ISO8601 - index: Integer = unique_field + label: schema.String + start_time: schema.DateTime = unique_field # ISO8601 + end_time: schema.DateTime = unique_field # ISO8601 + index: schema.Integer = unique_field @dataclass(eq=False, kw_only=True) @@ -87,7 +86,7 @@ class Venue(Model): id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - label: String + label: schema.String @dataclass(eq=False, kw_only=True) @@ -132,8 +131,8 @@ class LessonGroup(Model): id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - filename: String - label: String + filename: schema.String + label: schema.String @dataclass(eq=False, kw_only=True) @@ -155,9 +154,9 @@ class LessonGroupMember(Model): id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - filename: String + filename: schema.String lessongroup_id: schema.CampusID - ade_participant: String + ade_participant: schema.String __constraints__ = constraints.Unique("lessongroup_id", "ade_participant", "filename") @@ -175,7 +174,7 @@ class Timetable(Model): id: schema.CampusID = field(default_factory=( lambda: uid.generate_category_uid("timetable", length=8) )) - filename: String + filename: schema.String lessongroup_id: schema.CampusID venuetimeslot_id: schema.CampusID __constraints__ = constraints.Unique("lessongroup_id", "venuetimeslot_id", "filename")