sacs 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. sacs-0.1.0/LICENSE +21 -0
  2. sacs-0.1.0/PKG-INFO +223 -0
  3. sacs-0.1.0/README.md +181 -0
  4. sacs-0.1.0/pyproject.toml +67 -0
  5. sacs-0.1.0/sacs/__init__.py +12 -0
  6. sacs-0.1.0/sacs/alembic.ini +40 -0
  7. sacs-0.1.0/sacs/app.py +97 -0
  8. sacs-0.1.0/sacs/audit.py +55 -0
  9. sacs-0.1.0/sacs/cli.py +791 -0
  10. sacs-0.1.0/sacs/config.py +111 -0
  11. sacs-0.1.0/sacs/db.py +116 -0
  12. sacs-0.1.0/sacs/deps.py +101 -0
  13. sacs-0.1.0/sacs/events.py +233 -0
  14. sacs-0.1.0/sacs/migrations/env.py +56 -0
  15. sacs-0.1.0/sacs/migrations/script.py.mako +24 -0
  16. sacs-0.1.0/sacs/migrations/versions/ade8928a2588_initial_schema.py +420 -0
  17. sacs-0.1.0/sacs/models.py +461 -0
  18. sacs-0.1.0/sacs/rbac.py +106 -0
  19. sacs-0.1.0/sacs/routers/__init__.py +10 -0
  20. sacs-0.1.0/sacs/routers/academics.py +199 -0
  21. sacs-0.1.0/sacs/routers/auth.py +161 -0
  22. sacs-0.1.0/sacs/routers/bell.py +208 -0
  23. sacs-0.1.0/sacs/routers/platform.py +250 -0
  24. sacs-0.1.0/sacs/routers/realtime.py +78 -0
  25. sacs-0.1.0/sacs/routers/requests.py +158 -0
  26. sacs-0.1.0/sacs/routers/schools.py +407 -0
  27. sacs-0.1.0/sacs/routers/timetable.py +237 -0
  28. sacs-0.1.0/sacs/scheduler.py +200 -0
  29. sacs-0.1.0/sacs/security.py +99 -0
  30. sacs-0.1.0/sacs/seed.py +307 -0
  31. sacs-0.1.0/sacs/workflow.py +386 -0
  32. sacs-0.1.0/sacs.egg-info/PKG-INFO +223 -0
  33. sacs-0.1.0/sacs.egg-info/SOURCES.txt +44 -0
  34. sacs-0.1.0/sacs.egg-info/dependency_links.txt +1 -0
  35. sacs-0.1.0/sacs.egg-info/entry_points.txt +2 -0
  36. sacs-0.1.0/sacs.egg-info/requires.txt +20 -0
  37. sacs-0.1.0/sacs.egg-info/top_level.txt +1 -0
  38. sacs-0.1.0/setup.cfg +4 -0
  39. sacs-0.1.0/tests/test_auth.py +67 -0
  40. sacs-0.1.0/tests/test_bell.py +113 -0
  41. sacs-0.1.0/tests/test_cli.py +52 -0
  42. sacs-0.1.0/tests/test_membership.py +139 -0
  43. sacs-0.1.0/tests/test_packaging.py +81 -0
  44. sacs-0.1.0/tests/test_realtime.py +70 -0
  45. sacs-0.1.0/tests/test_tenant_isolation.py +84 -0
  46. sacs-0.1.0/tests/test_workflow.py +108 -0
sacs-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ivanbright
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
sacs-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,223 @@
1
+ Metadata-Version: 2.4
2
+ Name: sacs
3
+ Version: 0.1.0
4
+ Summary: SACS - Smart Academic Coordination System: a real-time multi-tenant school coordination platform with a one-command live demo.
5
+ Author: ivanbright
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ivanbright/sacs
8
+ Project-URL: Repository, https://github.com/ivanbright/sacs
9
+ Project-URL: Issues, https://github.com/ivanbright/sacs/issues
10
+ Project-URL: Changelog, https://github.com/ivanbright/sacs/blob/main/CHANGELOG.md
11
+ Keywords: education,school,timetable,multi-tenant,fastapi,realtime
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Education
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: fastapi>=0.110
26
+ Requires-Dist: uvicorn[standard]>=0.27
27
+ Requires-Dist: sqlalchemy>=2.0
28
+ Requires-Dist: alembic>=1.13
29
+ Requires-Dist: typer>=0.12
30
+ Requires-Dist: rich>=13.0
31
+ Requires-Dist: httpx>=0.27
32
+ Requires-Dist: email-validator>=2.0
33
+ Requires-Dist: tzdata>=2024.1; platform_system != "Linux"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8.0; extra == "dev"
36
+ Requires-Dist: httpx>=0.27; extra == "dev"
37
+ Requires-Dist: build>=1.0; extra == "dev"
38
+ Requires-Dist: twine>=5.0; extra == "dev"
39
+ Provides-Extra: postgres
40
+ Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
41
+ Dynamic: license-file
42
+
43
+ # SACS — Smart Academic Coordination System
44
+
45
+ A real-time, multi-tenant school coordination platform that runs entirely from the terminal.
46
+ SACS coordinates **schools** (tenants), **staff**, **timetables**, **lesson sessions**, a
47
+ **lesson-completion approval workflow**, and a deterministic **software bell scheduler**,
48
+ streaming every event to authenticated workspaces over WebSockets.
49
+
50
+ There is no browser frontend: the whole product is exercised through the `sacs` CLI, which
51
+ talks to one shared FastAPI backend exactly like a real client would.
52
+
53
+ ---
54
+
55
+ ## Quick start
56
+
57
+ ```bash
58
+ pip install sacs # then:
59
+ sacs demo # migrate, seed, start the server, open the live dashboard
60
+ sacs status # show whether the demonstration is running
61
+ sacs demo reset # destroy ONLY the local demo database (requires confirmation)
62
+ sacs demo stop # stop the running demonstration
63
+ sacs check # verify dependencies and running environment
64
+ sacs version
65
+ ```
66
+
67
+ `sacs demo` is the product. It:
68
+
69
+ 1. migrates the database to the latest schema (Alembic) and seeds two schools;
70
+ 2. starts the shared SACS service on a local port;
71
+ 3. authenticates **five independently authorized identities** against the real API;
72
+ 4. shows a live multi-workspace dashboard (Rich) with keyboard commands for the teacher
73
+ submit → DOS review → head decide workflow, bell operations, and school administration.
74
+
75
+ The demonstration persists between restarts (`sacs demo` again keeps your data; use
76
+ `sacs demo reset` for a clean slate).
77
+
78
+ ### Demo identities
79
+
80
+ | Workspace | Email | Role | What they can do |
81
+ | ---------- | ------------------------------ | ------------- | -------------------------------------------------- |
82
+ | Platform | `platform@demo.sacs` | platform admin | create/suspend/reactivate schools, health, audit |
83
+ | Head | `head.northstar@demo.sacs` | school head | final approve/reject, staff & academics, bells |
84
+ | DOS | `dos.northstar@demo.sacs` | director of studies | review, forward, return requests; timetable |
85
+ | Teacher | `teacher.northstar@demo.sacs` | teacher | submit completion requests for own lessons |
86
+ | Bell | `bell.northstar@demo.sacs` | bell monitor | monitor bell events in real time |
87
+
88
+ (Pair for `riverside-institute`: `head.riverside@demo.sacs`, `dos.riverside@demo.sacs`,
89
+ `teacher.riverside@demo.sacs`, `bell.riverside@demo.sacs`.)
90
+
91
+ All demo accounts use the password **`SacsDemo123!`**.
92
+
93
+ ---
94
+
95
+ ## The lesson-completion workflow
96
+
97
+ A teacher submits a completion request for one of their published lessons. The request then
98
+ moves through a strictly enforced state machine — invalid transitions are rejected
99
+ server-side:
100
+
101
+ ```
102
+ submitted ──review──> under_dos_review
103
+ submitted ──forward─> forwarded_to_head
104
+ submitted ──return──> returned_for_correction
105
+ under_dos_review ──forward─> forwarded_to_head
106
+ under_dos_review ──return──> returned_for_correction
107
+ forwarded_to_head ──approve─> approved
108
+ forwarded_to_head ──reject──> rejected
109
+ returned_for_correction ──submit─> submitted
110
+ ```
111
+
112
+ Every transition records an audit action and the requester-facing history. A secondary-level
113
+ request may not be final-approved: `request.decide` (head) and `request.review` (DOS) are
114
+ deliberately disjoint.
115
+
116
+ ## Roles and permissions
117
+
118
+ | Permission | school_head | dos | teacher | bell_monitor |
119
+ | ---------------------- | :-: | :-: | :-: | :-: |
120
+ | `school.view` | ✓ | ✓ | ✓ | ✓ |
121
+ | `school.settings.manage` | ✓ | | | |
122
+ | `staff.manage` | ✓ | | | |
123
+ | `academics.manage` | ✓ | ✓ | | |
124
+ | `timetable.manage` | ✓ | ✓ | | |
125
+ | `timetable.publish` | ✓ | | | |
126
+ | `request.submit` | | | ✓ | |
127
+ | `request.review` | | ✓ | | |
128
+ | `request.decide` | ✓ | | | |
129
+ | `bell.view` | ✓ | ✓ | ✓ | ✓ |
130
+ | `bell.manage` | ✓ | | | |
131
+ | `audit.view.school` | ✓ | | | |
132
+
133
+ Platform permissions (`platform.schools.manage`, `platform.schools.suspend`,
134
+ `platform.health.view`, `platform.audit.view`) are exclusive to platform administrators and
135
+ never grant academic approval authority.
136
+
137
+ ## Bell scheduler
138
+
139
+ Each school runs `BellSchedule`s with per-period times evaluated in the school's own time
140
+ zone (`Europe/London`, `America/New_York`, …). A background loop ticks once per second; each
141
+ tick records every due-but-unrecorded occurrence with outcome:
142
+
143
+ - **fired** — executed inside the grace window (`SACS_BELL_GRACE_SECONDS`, default 90);
144
+ - **missed** — the window elapsed while the scheduler was offline;
145
+ - **skipped** — the schedule was paused;
146
+ - occurrences are keyed `schedule_id + day + period + (start|end)` with a unique constraint,
147
+ so restarts and overlapping ticks can never double-fire the school bell.
148
+
149
+ ### Deterministic test clock
150
+
151
+ In non-production environments the clock can be driven for testing and demonstrations:
152
+
153
+ ```
154
+ POST /api/test/clock/set { "now_utc": "2026-03-02T10:00:30+00:00" }
155
+ POST /api/test/bell/tick
156
+ POST /api/test/clock/clear
157
+ ```
158
+
159
+ The test clock is hard-disabled in production unless explicitly enabled.
160
+
161
+ ## Real-time events
162
+
163
+ Every state change is written to a transactional **outbox** in the same database commit as
164
+ the change itself, then drained and streamed to the exact recipients allowed server-side by
165
+ the event audience rule (`platform` | school roles | specific users). This guarantees a
166
+ persisted change never silently loses its notification, and gives tests a synchronous
167
+ delivery path.
168
+
169
+ ## Architecture
170
+
171
+ - **API**: FastAPI + Pydantic v2, packaged with uvicorn.
172
+ - **Persistence**: SQLAlchemy 2.0 (typed `Mapped` models); schema only ever created through
173
+ Alembic migrations — no implicit `create_all`.
174
+ - **Auth**: opaque hashed session tokens, PBKDF2 password hashing, per-school memberships.
175
+ - **Tenant isolation**: every school-scoped endpoint resolves the caller's membership and
176
+ re-checks the requested `school_id` server-side; a suspended school blocks all its members.
177
+ - **Time handling**: all timestamps are UTC; per-school time zones applied only for bell
178
+ scheduling and local display (`tzdata` included on Windows).
179
+
180
+ ## Configuration
181
+
182
+ Everything is configurable through environment variables (see `sacs/config.py`):
183
+
184
+ | Variable | Default |
185
+ | ------------------------- | ----------------------------------------- |
186
+ | `SACS_HOME` | `~/.sacs` (demo database lives here) |
187
+ | `SACS_DATABASE_URL` | `sqlite:///<SACS_HOME>/sacs.db` |
188
+ | `SACS_ENV` | `demo` (`demo`/`dev`/`prod`) |
189
+ | `SACS_HOST` / `SACS_PORT` | `127.0.0.1` / `0` (auto-pick a free port) |
190
+ | `SACS_ENABLE_TEST_CLOCK` | `true` (`false` forced in `prod`) |
191
+ | `SACS_SESSION_TTL_HOURS` | `12` |
192
+ | `SACS_BELL_GRACE_SECONDS` | `90` |
193
+
194
+ Deployments can point `SACS_DATABASE_URL` at PostgreSQL (see the `postgres` extra) and run
195
+ `uvicorn sacs.app:create_app --factory`.
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ pip install -e ".[dev]"
201
+ pytest # full test suite
202
+ pytest tests/test_packaging.py # build wheel/sdist and inspect artifacts
203
+ python -m build --no-isolation --outdir dist
204
+ ```
205
+
206
+ ## Repository layout
207
+
208
+ ```
209
+ sacs/
210
+ cli.py Typer CLI + the demo dashboard (five workspaces)
211
+ app.py FastAPI application and lifespan tasks
212
+ models.py SQLAlchemy models (19 tables)
213
+ db.py engines, sessions, Alembic wiring
214
+ security.py password + session token handling
215
+ rbac.py permission catalog for roles and platform
216
+ workflow.py the completion-request state machine
217
+ scheduler.py deterministic clock + bell loop and outcomes
218
+ events.py connection manager, transactional outbox, dispatcher
219
+ seed.py idempotent demo seed (two schools, five identities)
220
+ migrations/ Alembic baseline migration
221
+ routers/ platform, schools, academics, timetable, requests, bell, realtime, auth
222
+ tests/ unit + API + isolation + scheduler + packaging tests
223
+ ```
sacs-0.1.0/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # SACS — Smart Academic Coordination System
2
+
3
+ A real-time, multi-tenant school coordination platform that runs entirely from the terminal.
4
+ SACS coordinates **schools** (tenants), **staff**, **timetables**, **lesson sessions**, a
5
+ **lesson-completion approval workflow**, and a deterministic **software bell scheduler**,
6
+ streaming every event to authenticated workspaces over WebSockets.
7
+
8
+ There is no browser frontend: the whole product is exercised through the `sacs` CLI, which
9
+ talks to one shared FastAPI backend exactly like a real client would.
10
+
11
+ ---
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ pip install sacs # then:
17
+ sacs demo # migrate, seed, start the server, open the live dashboard
18
+ sacs status # show whether the demonstration is running
19
+ sacs demo reset # destroy ONLY the local demo database (requires confirmation)
20
+ sacs demo stop # stop the running demonstration
21
+ sacs check # verify dependencies and running environment
22
+ sacs version
23
+ ```
24
+
25
+ `sacs demo` is the product. It:
26
+
27
+ 1. migrates the database to the latest schema (Alembic) and seeds two schools;
28
+ 2. starts the shared SACS service on a local port;
29
+ 3. authenticates **five independently authorized identities** against the real API;
30
+ 4. shows a live multi-workspace dashboard (Rich) with keyboard commands for the teacher
31
+ submit → DOS review → head decide workflow, bell operations, and school administration.
32
+
33
+ The demonstration persists between restarts (`sacs demo` again keeps your data; use
34
+ `sacs demo reset` for a clean slate).
35
+
36
+ ### Demo identities
37
+
38
+ | Workspace | Email | Role | What they can do |
39
+ | ---------- | ------------------------------ | ------------- | -------------------------------------------------- |
40
+ | Platform | `platform@demo.sacs` | platform admin | create/suspend/reactivate schools, health, audit |
41
+ | Head | `head.northstar@demo.sacs` | school head | final approve/reject, staff & academics, bells |
42
+ | DOS | `dos.northstar@demo.sacs` | director of studies | review, forward, return requests; timetable |
43
+ | Teacher | `teacher.northstar@demo.sacs` | teacher | submit completion requests for own lessons |
44
+ | Bell | `bell.northstar@demo.sacs` | bell monitor | monitor bell events in real time |
45
+
46
+ (Pair for `riverside-institute`: `head.riverside@demo.sacs`, `dos.riverside@demo.sacs`,
47
+ `teacher.riverside@demo.sacs`, `bell.riverside@demo.sacs`.)
48
+
49
+ All demo accounts use the password **`SacsDemo123!`**.
50
+
51
+ ---
52
+
53
+ ## The lesson-completion workflow
54
+
55
+ A teacher submits a completion request for one of their published lessons. The request then
56
+ moves through a strictly enforced state machine — invalid transitions are rejected
57
+ server-side:
58
+
59
+ ```
60
+ submitted ──review──> under_dos_review
61
+ submitted ──forward─> forwarded_to_head
62
+ submitted ──return──> returned_for_correction
63
+ under_dos_review ──forward─> forwarded_to_head
64
+ under_dos_review ──return──> returned_for_correction
65
+ forwarded_to_head ──approve─> approved
66
+ forwarded_to_head ──reject──> rejected
67
+ returned_for_correction ──submit─> submitted
68
+ ```
69
+
70
+ Every transition records an audit action and the requester-facing history. A secondary-level
71
+ request may not be final-approved: `request.decide` (head) and `request.review` (DOS) are
72
+ deliberately disjoint.
73
+
74
+ ## Roles and permissions
75
+
76
+ | Permission | school_head | dos | teacher | bell_monitor |
77
+ | ---------------------- | :-: | :-: | :-: | :-: |
78
+ | `school.view` | ✓ | ✓ | ✓ | ✓ |
79
+ | `school.settings.manage` | ✓ | | | |
80
+ | `staff.manage` | ✓ | | | |
81
+ | `academics.manage` | ✓ | ✓ | | |
82
+ | `timetable.manage` | ✓ | ✓ | | |
83
+ | `timetable.publish` | ✓ | | | |
84
+ | `request.submit` | | | ✓ | |
85
+ | `request.review` | | ✓ | | |
86
+ | `request.decide` | ✓ | | | |
87
+ | `bell.view` | ✓ | ✓ | ✓ | ✓ |
88
+ | `bell.manage` | ✓ | | | |
89
+ | `audit.view.school` | ✓ | | | |
90
+
91
+ Platform permissions (`platform.schools.manage`, `platform.schools.suspend`,
92
+ `platform.health.view`, `platform.audit.view`) are exclusive to platform administrators and
93
+ never grant academic approval authority.
94
+
95
+ ## Bell scheduler
96
+
97
+ Each school runs `BellSchedule`s with per-period times evaluated in the school's own time
98
+ zone (`Europe/London`, `America/New_York`, …). A background loop ticks once per second; each
99
+ tick records every due-but-unrecorded occurrence with outcome:
100
+
101
+ - **fired** — executed inside the grace window (`SACS_BELL_GRACE_SECONDS`, default 90);
102
+ - **missed** — the window elapsed while the scheduler was offline;
103
+ - **skipped** — the schedule was paused;
104
+ - occurrences are keyed `schedule_id + day + period + (start|end)` with a unique constraint,
105
+ so restarts and overlapping ticks can never double-fire the school bell.
106
+
107
+ ### Deterministic test clock
108
+
109
+ In non-production environments the clock can be driven for testing and demonstrations:
110
+
111
+ ```
112
+ POST /api/test/clock/set { "now_utc": "2026-03-02T10:00:30+00:00" }
113
+ POST /api/test/bell/tick
114
+ POST /api/test/clock/clear
115
+ ```
116
+
117
+ The test clock is hard-disabled in production unless explicitly enabled.
118
+
119
+ ## Real-time events
120
+
121
+ Every state change is written to a transactional **outbox** in the same database commit as
122
+ the change itself, then drained and streamed to the exact recipients allowed server-side by
123
+ the event audience rule (`platform` | school roles | specific users). This guarantees a
124
+ persisted change never silently loses its notification, and gives tests a synchronous
125
+ delivery path.
126
+
127
+ ## Architecture
128
+
129
+ - **API**: FastAPI + Pydantic v2, packaged with uvicorn.
130
+ - **Persistence**: SQLAlchemy 2.0 (typed `Mapped` models); schema only ever created through
131
+ Alembic migrations — no implicit `create_all`.
132
+ - **Auth**: opaque hashed session tokens, PBKDF2 password hashing, per-school memberships.
133
+ - **Tenant isolation**: every school-scoped endpoint resolves the caller's membership and
134
+ re-checks the requested `school_id` server-side; a suspended school blocks all its members.
135
+ - **Time handling**: all timestamps are UTC; per-school time zones applied only for bell
136
+ scheduling and local display (`tzdata` included on Windows).
137
+
138
+ ## Configuration
139
+
140
+ Everything is configurable through environment variables (see `sacs/config.py`):
141
+
142
+ | Variable | Default |
143
+ | ------------------------- | ----------------------------------------- |
144
+ | `SACS_HOME` | `~/.sacs` (demo database lives here) |
145
+ | `SACS_DATABASE_URL` | `sqlite:///<SACS_HOME>/sacs.db` |
146
+ | `SACS_ENV` | `demo` (`demo`/`dev`/`prod`) |
147
+ | `SACS_HOST` / `SACS_PORT` | `127.0.0.1` / `0` (auto-pick a free port) |
148
+ | `SACS_ENABLE_TEST_CLOCK` | `true` (`false` forced in `prod`) |
149
+ | `SACS_SESSION_TTL_HOURS` | `12` |
150
+ | `SACS_BELL_GRACE_SECONDS` | `90` |
151
+
152
+ Deployments can point `SACS_DATABASE_URL` at PostgreSQL (see the `postgres` extra) and run
153
+ `uvicorn sacs.app:create_app --factory`.
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ pip install -e ".[dev]"
159
+ pytest # full test suite
160
+ pytest tests/test_packaging.py # build wheel/sdist and inspect artifacts
161
+ python -m build --no-isolation --outdir dist
162
+ ```
163
+
164
+ ## Repository layout
165
+
166
+ ```
167
+ sacs/
168
+ cli.py Typer CLI + the demo dashboard (five workspaces)
169
+ app.py FastAPI application and lifespan tasks
170
+ models.py SQLAlchemy models (19 tables)
171
+ db.py engines, sessions, Alembic wiring
172
+ security.py password + session token handling
173
+ rbac.py permission catalog for roles and platform
174
+ workflow.py the completion-request state machine
175
+ scheduler.py deterministic clock + bell loop and outcomes
176
+ events.py connection manager, transactional outbox, dispatcher
177
+ seed.py idempotent demo seed (two schools, five identities)
178
+ migrations/ Alembic baseline migration
179
+ routers/ platform, schools, academics, timetable, requests, bell, realtime, auth
180
+ tests/ unit + API + isolation + scheduler + packaging tests
181
+ ```
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sacs"
7
+ version = "0.1.0"
8
+ description = "SACS - Smart Academic Coordination System: a real-time multi-tenant school coordination platform with a one-command live demo."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "ivanbright" }]
13
+ keywords = ["education", "school", "timetable", "multi-tenant", "fastapi", "realtime"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Education",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Education",
25
+ ]
26
+ dependencies = [
27
+ "fastapi>=0.110",
28
+ "uvicorn[standard]>=0.27",
29
+ "sqlalchemy>=2.0",
30
+ "alembic>=1.13",
31
+ "typer>=0.12",
32
+ "rich>=13.0",
33
+ "httpx>=0.27",
34
+ "email-validator>=2.0",
35
+ "tzdata>=2024.1; platform_system != 'Linux'",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ dev = ["pytest>=8.0", "httpx>=0.27", "build>=1.0", "twine>=5.0"]
40
+ postgres = ["psycopg[binary]>=3.1"]
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/ivanbright/sacs"
44
+ Repository = "https://github.com/ivanbright/sacs"
45
+ Issues = "https://github.com/ivanbright/sacs/issues"
46
+ Changelog = "https://github.com/ivanbright/sacs/blob/main/CHANGELOG.md"
47
+
48
+ [project.scripts]
49
+ sacs = "sacs.cli:main"
50
+
51
+ [tool.setuptools]
52
+ include-package-data = true
53
+
54
+ [tool.setuptools.packages.find]
55
+ include = ["sacs", "sacs.*"]
56
+
57
+ [tool.setuptools.package-data]
58
+ sacs = ["migrations/*", "migrations/versions/*", "alembic.ini"]
59
+
60
+ [tool.pytest.ini_options]
61
+ testpaths = ["tests"]
62
+ addopts = "-q"
63
+ filterwarnings = ["ignore::DeprecationWarning"]
64
+
65
+ [tool.ruff]
66
+ line-length = 100
67
+ target-version = "py311"
@@ -0,0 +1,12 @@
1
+ """SACS - Smart Academic Coordination System.
2
+
3
+ A real-time, multi-tenant school coordination platform. The package ships a
4
+ Python CLI (``sacs``) that launches a complete local demonstration with five
5
+ simultaneously usable browser workspaces backed by one shared service.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ __version__ = "0.1.0"
11
+
12
+ __all__ = ["__version__"]
@@ -0,0 +1,40 @@
1
+ [alembic]
2
+ script_location = sacs/migrations
3
+ prepend_sys_path = .
4
+ version_path_separator = os
5
+ # Overridden programmatically by sacs.db; present for manual `alembic` use.
6
+ sqlalchemy.url = sqlite:///./_migrate_tmp.db
7
+
8
+ [loggers]
9
+ keys = root,sqlalchemy,alembic
10
+
11
+ [handlers]
12
+ keys = console
13
+
14
+ [formatters]
15
+ keys = generic
16
+
17
+ [logger_root]
18
+ level = WARNING
19
+ handlers = console
20
+ qualname =
21
+
22
+ [logger_sqlalchemy]
23
+ level = WARNING
24
+ handlers =
25
+ qualname = sqlalchemy.engine
26
+
27
+ [logger_alembic]
28
+ level = INFO
29
+ handlers =
30
+ qualname = alembic
31
+
32
+ [handler_console]
33
+ class = StreamHandler
34
+ args = (sys.stderr,)
35
+ level = NOTSET
36
+ formatter = generic
37
+
38
+ [formatter_generic]
39
+ format = %(levelname)-5.5s [%(name)s] %(message)s
40
+ datefmt = %H:%M:%S
sacs-0.1.0/sacs/app.py ADDED
@@ -0,0 +1,97 @@
1
+ """FastAPI application factory for SACS."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from contextlib import asynccontextmanager
7
+
8
+ from fastapi import FastAPI
9
+ from fastapi.middleware.cors import CORSMiddleware
10
+
11
+ from . import __version__
12
+ from .config import get_settings
13
+ from .db import get_sessionmaker
14
+ from .events import background_dispatcher
15
+ from .scheduler import bell_loop
16
+
17
+ from .routers import auth as auth_router
18
+ from .routers import academics as academics_router
19
+ from .routers import bell as bell_router
20
+ from .routers import platform as platform_router
21
+ from .routers import realtime as realtime_router
22
+ from .routers import requests as requests_router
23
+ from .routers import schools as schools_router
24
+ from .routers import timetable as timetable_router
25
+
26
+
27
+ def cors_urls() -> list[str]:
28
+ settings = get_settings()
29
+ origins = list(settings.cors_origins)
30
+ if settings.env in ("demo", "dev"):
31
+ origins = ["*"]
32
+ return origins
33
+
34
+
35
+ def create_app() -> FastAPI:
36
+ settings = get_settings()
37
+
38
+ @asynccontextmanager
39
+ async def lifespan(app: FastAPI):
40
+ session_factory = get_sessionmaker()
41
+ tasks = [
42
+ bell_loop(session_factory),
43
+ background_dispatcher(session_factory),
44
+ ]
45
+ for task in tasks:
46
+ app.state.tasks.append(asyncio.create_task(task))
47
+ yield
48
+ for task in app.state.tasks:
49
+ task.cancel()
50
+ await asyncio.gather(*app.state.tasks, return_exceptions=True)
51
+
52
+ app = FastAPI(
53
+ title="SACS - Smart Academic Coordination System",
54
+ version=__version__,
55
+ description="Multi-tenant academic coordination platform: one shared backend, "
56
+ "isolated school tenants, real-time delivery, and a software bell scheduler.",
57
+ lifespan=lifespan,
58
+ )
59
+ app.state.tasks = []
60
+
61
+ app.add_middleware(
62
+ CORSMiddleware,
63
+ allow_origins=cors_urls(),
64
+ allow_credentials=False,
65
+ allow_methods=["*"],
66
+ allow_headers=["*"],
67
+ )
68
+
69
+ app.include_router(schools_router.accept_router)
70
+ app.include_router(auth_router.router)
71
+ app.include_router(platform_router.router)
72
+ app.include_router(schools_router.router)
73
+ app.include_router(academics_router.router)
74
+ app.include_router(timetable_router.router)
75
+ app.include_router(requests_router.router)
76
+ app.include_router(bell_router.router)
77
+ app.include_router(bell_router.test_router)
78
+ app.include_router(realtime_router.router)
79
+
80
+ @app.get("/api/health", tags=["system"])
81
+ def public_health() -> dict:
82
+ return {"status": "ok", "service": "sacs", "version": __version__}
83
+
84
+ @app.get("/", include_in_schema=False)
85
+ def root() -> dict:
86
+ return {
87
+ "service": "sacs",
88
+ "version": __version__,
89
+ "docs": "/docs",
90
+ "health": "/api/health",
91
+ "note": "No browser frontend is shipped; the SACS CLI is the interface.",
92
+ }
93
+
94
+ return app
95
+
96
+
97
+ __all__ = ["create_app"]