Skip to content

String-sg/reliefcher

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

247 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ReliefCher — Relief Teacher Planning

Stop scrambling. Start standing in.

ReliefCher is an open relief-planning platform for Singapore schools that replaces the daily WhatsApp chaos of coordinating teacher absences and relief coverage. Teachers report absences in under 30 seconds; the system instantly identifies who is free, auto-assigns relief fairly using configurable rules, and gives coordinators a single real-time dashboard to manage everything. Built for the Singapore school context — MOE timetable formats, OTPaaS authentication, Singpass identity verification, and government cloud deployment.

How this was built: ReliefCher was built using Claude Code — Anthropic's AI coding agent — with Superpowers as a prompt-engineering layer for structured reasoning. The developer is a product manager, not a software engineer. Agent output quality is governed by session rules (CLAUDE.md), git hooks (Husky), and structured skills (TDD, debugging, code review).


Table of Contents

  1. The Problem
  2. Our Solution
  3. Key Features
  4. Auto-Assign Algorithm
  5. Security and Authentication
  6. Tech Stack
  7. Deployment Architecture
  8. Background Workers
  9. Database Schema
  10. Application Routes and Roles
  11. Local Setup
  12. Running Tests
  13. Project Structure
  14. Piloting ReliefCher

The Problem

Every school morning plays out the same way: a teacher texts the WhatsApp group at 6:30 AM — "MC today, can someone cover my classes?" What follows is a 45-minute scramble of messages, spreadsheets, and sticky notes as the relief coordinator tries to figure out who is free, who has already been assigned too many relief periods this week, and which classes are still uncovered by the time the bell rings.

Most schools track relief assignments manually or through fragmented tools that don't talk to each other. Absences are reported in one system, payroll in another, and the actual assignment happens over chat. Classes fall through the cracks. Teachers burn out from uneven workload distribution. Coordinators spend their mornings firefighting instead of supporting students.


Our Solution

ReliefCher gives schools a single, real-time platform to manage the full relief workflow — from the moment a teacher reports sick to the moment every period is covered.

flowchart LR
    A["Teacher reports\nabsence (~30s)"] --> B["System detects\navailable staff"]
    B --> C["Auto-assign\nalgorithm runs"]
    C --> D["Coordinator reviews\non dashboard"]
    D --> E["Notifications sent\n(Telegram / Email)"]
    E --> F["Relief teacher\nacknowledges"]
Loading

Teachers report absences in under 30 seconds. The system instantly knows who is free based on the school's actual timetable. A smart auto-assign algorithm distributes relief fairly using configurable rules (workload caps, subject-match priority, consecutive-period optimisation). The relief coordinator sees everything on one dashboard and can override any assignment with a click.

No more WhatsApp threads. No more guesswork. No more uncovered classes.


Key Features

Timetable Import

Paste your aSc TimeTables CSV or XML export and the entire teacher, period, class, and subject structure is built automatically. Supports weekly, two-week (odd/even), and ten-day timetable cycles. No manual data entry — teachers, periods, classes, and subjects are created in a single transaction with deduplication.

One-Click Absence Reporting

Teachers pick their name, select the dates and affected periods, and submit. Takes ~30 seconds. The public report form at /<school-slug>/report can operate without login (legacy mode) or require MOE iCON SSO (per-school toggle). Supports 11 absence reason types including sick leave, childcare leave, compassionate leave, and official duty.

Per-Period Lesson Notes

Absent teachers leave handover instructions for each period individually — not one freetext blob for the whole absence. Relief teachers see exactly what to do for each class they're covering. Coverage decisions (relief required vs. no relief needed) are set per-period, not per-absence.

Smart Availability Detection

The system cross-references the school timetable (including odd/even week rotations and 10-day cycles) to show exactly which teachers are free for each period. Hard constraints are always enforced: no self-cover, no double-booking, no assigning sick teachers.

Auto-Assign Algorithm

Configurable rules based on workload fairness ("Hearts" system with weekly reset), subject/role priority ("Stars"), consecutive-period optimisation, and more. Two algorithm modes: cascade (rule-based filtering) and weighted (Rank-Order Centroid scoring with 2-opt swap). See Auto-Assign Algorithm for details.

Real-Time Dashboard

A single screen showing who is absent today, which periods need coverage, and who has been assigned. Includes a timetable grid with relief overlay, staff roster with bulk CSV import, and configurable algorithm settings. Auto-refreshes with optimistic updates.

Pool Teacher Matching

Reach beyond your school's staff to MOE's wider relief teacher pool (SRE/FAJT/CAJT). Pool teachers register via Singpass/MyInfo for identity verification. The matching engine scores candidates by proximity (postal code → URA planning area), subject match, teaching scheme, and school affiliation. Tiered matching: preferred affiliates first, then wider pool.

Telegram Notifications and Google Calendar Sync

Relief teachers receive assignments and respond directly via Telegram bot (inline accept/decline buttons). Assignments are pushed to Google Calendar with reminders. Daily summary emails are sent to school leaders.

Admin Panel

Superadmin dashboard with system-wide visibility: school management, user management, pool teacher administration (status, subjects, availability), algorithm tuning, audit logs, lead management, and user impersonation (dev/test only).

Demo Workspace

A seeded demo workspace at /demo/dashboard lets stakeholders explore the full dashboard without a production account. Demo session is path-scoped with its own cookie.


Auto-Assign Algorithm

ReliefCher supports two algorithm modes, configurable platform-wide via SystemConfig:

Cascade Mode (Legacy)

Each rule narrows the candidate pool sequentially. The first rule that reduces the pool to one candidate wins.

flowchart TD
    A["All available teachers"] --> B["H1: Remove absent teacher"]
    B --> C["H2: Remove double-booked"]
    C --> D["H3: Remove sick teachers"]
    D --> E["R1: Balance Workload\n(fewest shifts this week)"]
    E --> F["R2: Match Subject\n(same subject preference)"]
    F --> G["R3: Prefer Same Class\n(already teaches this class)"]
    G --> H["R4: Prefer Consecutive\n(same teacher for back-to-back)"]
    H --> I["R5: Follow Role Priority\n(school-defined role order)"]
    I --> J["R6: Avoid Back-to-Back\n(deprioritise 3+ consecutive)"]
    J --> K["R7: Hard Weekly Cap\n(enforce max relief/week)"]
    K --> L["Alphabetical tie-break"]
    L --> M["Winner assigned"]
Loading

Weighted Mode (ROC Scoring)

Rank-Order Centroid weights are derived from the admin's drag-to-reorder rule order — no manual weight tuning. Each candidate is scored across all rules simultaneously, followed by a 2-opt pairwise swap pass to catch cross-slot trades.

Hard Constraints (Always Enforced)

ID Constraint Description
H1 No self-cover Absent teacher never covers their own class
H2 No double-book Cannot assign a teacher already teaching that period
H3 No sick assignment Cannot assign a teacher marked absent that day

Configurable Rules

ID Rule Status Description
R1 Balance Workload Shipped Prefer teachers with fewer relief shifts this week (resets Monday). Configurable fairness window (weekly or rolling lookback).
R2 Match Subject Shipped Prefer teachers who teach the same subject as the absent teacher. Soft-skip if no match.
R3 Prefer Same Class Shipped Prefer teachers who already teach the same class in another period.
R4 Prefer Consecutive Shipped Keep the same reliever across back-to-back periods of the same class.
R5 Follow Role Priority Shipped School-defined role ordering (e.g. SRE → untrained → FAJT → EO).
R6 Avoid Back-to-Back Shipped Deprioritise teachers already on 3+ consecutive teaching periods that day. Configurable threshold.
R7 Hard Weekly Cap Shipped Treat teacher as unavailable once they hit a configurable max shifts/week.
R8 Longer-Horizon Fairness Proposed Extend fairness beyond weekly reset (4–8 week rolling history).
R9 Willingness Opt-In Proposed Flag teachers willing to take extra relief (professional development points).
R10 Multi-Day Continuity Proposed Reuse the reliever from day 1 of a multi-day absence on subsequent days.

Security and Authentication

Authentication Flow

sequenceDiagram
    participant U as User
    participant App as ReliefCher
    participant OTP as TechPass OTPaaS
    participant DB as Database

    U->>App: Enter email at /login
    App->>OTP: POST /otp/send (HMAC-signed API key)
    OTP-->>U: 6-digit OTP via email
    U->>App: Submit OTP
    App->>OTP: POST /otp/verify
    OTP-->>App: Valid / Invalid
    App->>DB: createSession(userId, token, expiresAt)
    App-->>U: Set HTTP-only cookie (reliefcher_session)
Loading

Authentication Methods

Method Purpose Gate
TechPass OTPaaS Primary login for all users Email must be allowlisted in OTPaaS
Google OAuth (MOE iCON) SSO for MOE staff Server-side restricted to @moe.edu.sg; requires email_verified === true
Singpass/MyInfo Pool teacher identity verification Fetches NRIC, name, DOB, address from MyInfo API

Session Management

Custom stateful session system — not NextAuth. A 32-byte random token is stored in an HTTP-only cookie (reliefcher_session) and looked up against the Session table on each request. Sessions expire after 7 days. Instant revocation: delete the row and the user is logged out everywhere. This was chosen over JWTs because the OTPaaS integration is a single-provider flow that doesn't benefit from NextAuth's multi-provider abstractions, and stateful sessions give us the revocation guarantee.

Security Controls

Control Implementation
NRIC encryption AES-256-GCM (src/lib/encryption.ts); separate HMAC-SHA256 hash for lookups (prevents rainbow tables)
Secret management All credentials via environment variables; .env files gitignored; .env.example committed with placeholders
Input validation Zod schemas at API boundary; Prisma parameterised queries (no raw SQL)
Audit logging Append-only AuditLog and AbsenceReportAudit tables; tracks auth attempts, admin mutations, pool operations
Transport security sslmode=verify-ca with AWS RDS CA bundle baked into Docker image
Impersonation Gated by ENABLE_IMPERSONATION env flag; dev/test only; never in production

Tech Stack

Layer Technology
Framework Next.js 16 (App Router, Server Components)
Language TypeScript 5, React 19
ORM Prisma 7 + PrismaPg driver adapter
Database PostgreSQL 15
Auth TechPass OTPaaS + Google OAuth (MOE iCON) + Singpass/MyInfo
Session Custom stateful (HTTP-only cookie + DB lookup)
Styling Tailwind CSS 4 + Flow Design System (@flow/core) + Radix UI Colors
Icons Lucide React
Animations Framer Motion
Validation Zod 4
Job queue Redis + BullMQ (ioredis)
Telegram grammy
Email Resend
Unit / integration tests Vitest 4 + Testing Library
End-to-end tests Playwright
CI / hooks Husky (pre-push: unit tests)
Containerisation Docker (multi-stage, Node 22 Alpine)
Deployment target AWS GCC (ECS/EC2 + ALB + RDS)

Deployment Architecture

flowchart LR
    subgraph Local
        L_PG["Postgres :5432"]
        L_RD["Redis :6379"]
        L_APP["next dev :3000"]
        L_WK["npm run workers"]
    end

    subgraph "Test (Vercel — interim)"
        T_APP["Vercel Edge"]
        T_PG["Supabase Postgres"]
    end

    subgraph "Prod (AWS GCC)"
        P_ALB["ALB"]
        P_ECS1["ECS: Next.js app"]
        P_ECS2["ECS: worker-matching"]
        P_ECS3["ECS: worker-notification"]
        P_ECS4["ECS: worker-calendar"]
        P_RDS["RDS PostgreSQL"]
        P_RD["ElastiCache Redis"]
        P_OTP["TechPass OTPaaS"]

        P_ALB --> P_ECS1
        P_ECS1 --> P_RDS
        P_ECS1 --> P_RD
        P_ECS2 --> P_RDS
        P_ECS2 --> P_RD
        P_ECS3 --> P_RD
        P_ECS4 --> P_RD
        P_ECS1 --> P_OTP
    end
Loading

Three Environments

Environment Hosting Database Workers Auth
Local localhost:3000 Docker Compose Postgres (port 5432) npm run workers (all 3 queues in one process) OTPaaS sandbox + Google/Singpass sandbox
Test (interim) Vercel Supabase Postgres Not required (manual testing) OTPaaS test + Google/Singpass test
Prod AWS GCC (ECS/EC2 + ALB) AWS RDS PostgreSQL 3 ECS tasks (one per queue) OTPaaS production + Google/Singpass production

Docker Multi-Stage Build

The Dockerfile uses three stages:

  1. depsnpm ci, copy Prisma schema
  2. builderprisma generate, next build
  3. runner — Alpine runtime with compiled .next output, worker sources (run via tsx at runtime), and AWS RDS CA bundle for TLS verification

A single Docker image serves both the web app and workers. The role is selected at runtime via the WORKER_QUEUE environment variable.


Background Workers

flowchart LR
    API["Next.js API Routes"] -->|enqueue| RD["Redis"]
    RD --> W1["Worker: matching"]
    RD --> W2["Worker: notification"]
    RD --> W3["Worker: calendar"]
    W1 -->|"PoolAssignment\ncreation"| DB[(Postgres)]
    W2 -->|"Telegram / Email\ndelivery"| EXT["grammy / Resend"]
    W3 -->|"Event create\n/ update"| GCAL["Google Calendar API"]
Loading

Queue Overview

Queue Responsibility Triggers
matching Pool teacher candidate ranking, assignment creation, offer expiry Absence report submitted; daily 5:30am cron
notification Email (Resend) and Telegram (grammy) delivery with retry Assignment created; pool offer sent; daily summary
calendar Google Calendar event create/update/delete with VALARM reminders Pool assignment confirmed; assignment changed

Topology

  • Local dev: npm run workers runs all three queues in a single Node process (WORKER_QUEUE=all)
  • Production (GCC): Three separate ECS tasks, each with WORKER_QUEUE=matching|notification|calendar
  • Notification outbox: All sends are persisted in the OutboundMessage table before queue processing. Status lifecycle: PENDINGSENT | FAILED | CANCELLED. Exponential backoff with configurable max retries.

Database Schema

Entity Relationship Diagram

erDiagram
    School ||--o{ Teacher : has
    School ||--o{ Period : has
    School ||--o{ TimetableEntry : has
    School ||--o{ AbsenceReport : has
    School ||--o{ ReliefAssignment : has
    School ||--o{ SchoolRole : has
    School ||--o{ Position : has
    School ||--o| MatchingConfig : has

    Teacher ||--o{ TimetableEntry : teaches
    Teacher ||--o{ AbsenceReport : reports
    Teacher ||--o{ ReliefAssignment : "covers for"

    AbsenceReport ||--o{ ReliefAssignment : generates
    AbsenceReport ||--o{ AbsencePeriodRequirement : "has per-period"
    AbsenceReport ||--o{ PoolAssignment : "triggers"

    TimetableEntry }o--|| Period : "in period"

    PoolTeacher ||--o{ PoolTeacherSubject : teaches
    PoolTeacher ||--o{ PoolTeacherLevel : "qualified for"
    PoolTeacher ||--o{ SchoolAffiliation : "affiliated with"
    PoolTeacher ||--o{ PoolAssignment : receives

    User ||--o{ Session : has
Loading

Core Models

Model Purpose
School Multi-tenant root. Holds timetable cycle config, algo rules JSON, postal code for proximity matching.
User App users with roles: SUPERADMIN, SCHOOL_ADMIN, TEACHER. Email-based auth via OTPaaS/Google.
Session Stateful auth tokens. HTTP-only cookie, 7-day expiry, instant revocation.
Teacher School staff member. Linked to timetable entries, subjects, school role, and position.
SchoolRole Configurable roles (e.g. "Relief Teacher", "FAJT", "EO") with priority order for auto-assign. Category flag: TEACHER, PERMANENT_RELIEF, EXTERNAL_RT.
Position Job titles (e.g. "HOD English") with optional default max relief per week.
Period School periods with start/end times (e.g. Period 1, 07:30–08:30).
TimetableEntry The core schedule: teacher × day × period × class × subject × week type. Composite unique index.
AbsenceReport Teacher absence record with reason, date range, coverage type, and per-period lesson notes.
AbsencePeriodRequirement Per-period granularity: coverage decision (relief required / no relief) and lesson note for each period of an absence.
ReliefAssignment Links an absence to a relief teacher for a specific timetable entry and date. Tracks acknowledgement and who assigned.
PoolTeacher External relief teachers (SRE/FAJT/CAJT). NRIC encrypted (AES-256-GCM), Singpass-verified, Telegram-linked.
PoolAssignment Pool teacher assignment with status lifecycle: CONFIRMEDCOMPLETED
MatchingConfig Per-school or platform-wide config for pool matching criteria, candidate window, cross-school conflict mode.
OutboundMessage Notification outbox. Tracks every Telegram/email send with status, retry count, and provider message ID.
AuditLog Append-only general audit trail for security and compliance events.
SystemConfig Platform-wide settings. Currently holds algoMode (CASCADE or WEIGHTED).
InterestLead Inbound interest signups from the public /interest form. CRM-lite with status tracking.

Application Routes and Roles

flowchart TD
    subgraph "Public (no auth)"
        P1["/ — Landing page"]
        P2["/interest — Early access signup"]
        P3["/feedback — Feedback form"]
        P4["/login — OTPaaS + Google SSO"]
        P5["/&lt;slug&gt;/report — Absence form"]
        P6["/register/pool-teacher — Singpass registration"]
    end

    subgraph "School Admin"
        S1["/&lt;slug&gt;/dashboard — Relief overview"]
        S2["/&lt;slug&gt;/dashboard/staff — Teacher roster"]
        S3["/&lt;slug&gt;/dashboard/timetable — Timetable grid"]
        S4["/&lt;slug&gt;/dashboard/relief-teachers — Pool teachers"]
        S5["/&lt;slug&gt;/dashboard/settings — School config"]
    end

    subgraph "Pool Teacher"
        PT1["/pool/profile — Profile & preferences"]
    end

    subgraph "Superadmin"
        A1["/admin/dashboard — System overview"]
        A2["/admin/schools — School management"]
        A3["/admin/users — User management"]
        A4["/admin/pool-teachers — Pool teacher admin"]
        A5["/admin/algorithm — Algorithm tuning"]
        A6["/admin/audit-logs — Audit trail"]
        A7["/admin/leads — Interest leads"]
        A8["/admin/impersonate — User impersonation"]
    end
Loading

Route Table

Route Access Description
/ Public Landing page with features, problem/solution narrative
/login Public OTPaaS email OTP + Google OAuth (MOE iCON) entry point
/interest Public Early-access signup form for schools
/feedback Public Feedback form (creates GitHub issues)
/[slug]/report Public or SSO Absence reporting form. Per-school toggle for SSO requirement.
/[slug]/reporting Public Post-submission confirmation page
/[slug]/dashboard School Admin Main relief dashboard: today's absences, assignments, coverage status
/[slug]/dashboard/staff School Admin Teacher roster with CSV import, bulk operations, subject management
/[slug]/dashboard/timetable School Admin Timetable grid view with relief overlay
/[slug]/dashboard/relief-teachers School Admin Pool teacher matching, preferred teachers, REMS candidates
/[slug]/dashboard/settings School Admin Algorithm rules config, matching criteria, school postal code
/register/pool-teacher Public Singpass/MyInfo registration flow for relief teachers
/pool/profile Pool Teacher Relief teacher profile, subject preferences, availability window
/teacher/absences Teacher Personal absence history
/onboarding Authenticated School setup wizard (first-time admin)
/admin/dashboard Superadmin System health, platform-wide stats
/admin/schools Superadmin School CRUD, bulk operations
/admin/users Superadmin User management, role assignment
/admin/pool-teachers Superadmin Pool teacher admin: status management, subjects, REMS import
/admin/algorithm Superadmin Algorithm mode toggle (cascade/weighted), rule visualisation
/admin/audit-logs Superadmin System audit trail
/admin/leads Superadmin Interest lead CRM (status, notes, conversion tracking)
/admin/impersonate Superadmin User impersonation for testing (dev/test env only)

Local Setup

Prerequisites

  • Node.js 22+ (LTS)
  • Docker and Docker Compose (for Postgres and Redis)
  • npm (comes with Node.js)

Step-by-Step

1. Clone the repository

git clone https://github.com/String-sg/relief-teacher-planning.git
cd relief-teacher-planning

2. Install dependencies

npm install

This also runs prisma generate via the postinstall hook.

3. Start local services

docker compose -f docker-compose.local.yml up -d

This starts Postgres on port 5432 and Redis on port 6379.

4. Configure environment

cp .env.example .env.local

Edit .env.local with your values. At minimum for local development:

DATABASE_URL=postgresql://postgres:postgres@localhost:5432/reliefcher
DIRECT_URL=postgresql://postgres:postgres@localhost:5432/reliefcher
NEXTAUTH_SECRET=<any-random-32-char-string>
NEXTAUTH_URL=http://localhost:3000
REDIS_URL=redis://localhost:6379
NRIC_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000

For full OTPaaS login, you also need OTPAAS_BASE_URL, OTPAAS_NAMESPACE, OTPAAS_APP_ID, and OTPAAS_SECRET. See .env.example for all variables.

5. Run database migrations

npx prisma migrate deploy

6. Seed the database

npm run seed

This populates demo schools, teachers, timetables, and absence reports.

7. Start the dev server

npm run dev

The app is now available at http://localhost:3000.

8. (Optional) Start background workers

npm run workers

This runs all three BullMQ worker queues (matching, notification, calendar) in a single process.


Running Tests

flowchart TD
    A["npm test"] -->|"Fast, no DB"| B["Unit / Logic Tests\nsrc/__tests__/**/*.test.ts"]
    C["npm run test:api"] -->|"Requires Postgres :5433"| D["API / Integration Tests\ntests/api/**/*.test.ts"]
    E["npm run test:e2e"] -->|"Requires running app"| F["E2E / Visual Regression\ne2e/**/*.spec.ts"]
Loading

Test Suites

Suite Location Command Database? Pre-push?
Unit / logic src/__tests__/**/*.test.ts npm test No Yes (Husky)
API / integration tests/api/**/*.test.ts npm run test:api Yes (Postgres on :5433) No
E2E / visual e2e/**/*.spec.ts npm run test:e2e Yes (full app running) No

Unit Tests

Fast, database-free tests for business logic — auto-assign rules, matching criteria, timetable import parsing, encryption, rate limiting. Runs on every git push via Husky pre-push hook. Cannot push if tests fail.

npm test              # single run
npm run test:watch    # watch mode
npm run test:coverage # with coverage report

API / Integration Tests

Full API route tests against a real Postgres instance on port 5433. Global setup creates the test database and runs prisma migrate deploy. Tests use factory helpers (createSchool, createUser, createTeacher, etc.) and truncateAll() for cleanup between tests.

# Start test database (if not using docker-compose.local.yml)
docker run -d --name reliefcher-test-db -p 5433:5432 \
  -e POSTGRES_DB=reliefcher_test -e POSTGRES_PASSWORD=postgres postgres:15

npm run test:api

E2E / Visual Regression

Playwright tests with screenshot comparisons on three iPhone models (SE, 14, 14 Pro Max). Requires the built app running on port 3001.

npm run build
npm run start -- -p 3001 &
npm run test:e2e           # run tests
npm run test:e2e:ui        # interactive UI mode
npm run test:e2e:update    # update baseline snapshots

Project Structure

relief-teacher-planning/
├── prisma/
│   ├── schema.prisma                 # Data model (30+ models, 15+ enums)
│   ├── seed.ts                       # Demo data seeding
│   ├── migrations/                   # Migration chain (rebaselined Apr 2026)
│   └── README.md                     # Migration deploy/resolve guide
├── src/
│   ├── app/
│   │   ├── [slug]/                   # School-scoped routes
│   │   │   ├── dashboard/            # Relief coordinator dashboard
│   │   │   │   ├── staff/            # Teacher roster & CSV import
│   │   │   │   ├── timetable/        # Timetable grid with relief overlay
│   │   │   │   ├── relief-teachers/  # Pool teacher matching
│   │   │   │   └── settings/         # School config & algo rules
│   │   │   ├── report/               # Public absence form
│   │   │   └── reporting/            # Post-submission confirmation
│   │   ├── admin/                    # Superadmin panel
│   │   │   ├── dashboard/            # System overview
│   │   │   ├── schools/              # School management
│   │   │   ├── users/                # User management
│   │   │   ├── pool-teachers/        # Pool teacher admin
│   │   │   ├── algorithm/            # Algorithm tuning
│   │   │   ├── audit-logs/           # Audit trail
│   │   │   └── leads/                # Interest lead CRM
│   │   ├── api/                      # API routes
│   │   │   ├── auth/                 # OTPaaS, Google, Singpass flows
│   │   │   ├── absence-reports/      # Absence CRUD
│   │   │   ├── auto-assign/          # Algorithm trigger
│   │   │   ├── dashboard/            # Dashboard data + availability
│   │   │   ├── relief-assignments/   # Assignment CRUD
│   │   │   ├── pool-teachers/        # Pool teacher CRUD + import + invites
│   │   │   ├── teachers/             # Teacher CRUD + CSV import/export
│   │   │   ├── timetable/            # Timetable CRUD
│   │   │   ├── notifications/        # Outbound message management
│   │   │   ├── telegram/             # Telegram webhook
│   │   │   └── cron/                 # Scheduled jobs (daily summary, cleanup)
│   │   ├── login/                    # Auth entry point
│   │   ├── onboarding/               # School setup wizard
│   │   ├── register/pool-teacher/    # Singpass registration
│   │   ├── pool/profile/             # Pool teacher self-service
│   │   ├── teacher/absences/         # Teacher absence history
│   │   ├── interest/                 # Early access signup
│   │   ├── feedback/                 # Feedback form
│   │   └── page.tsx                  # Landing page
│   ├── components/
│   │   ├── landing/                  # Hero, features, problem/solution sections
│   │   ├── matching/                 # Pool teacher matching UI
│   │   ├── teachers/                 # Teacher roster, bulk import
│   │   ├── timetable/                # Grid views, relief overlay, period strips
│   │   └── ui/                       # Shared primitives (Combobox, Select, etc.)
│   ├── hooks/                        # React hooks (auto-refresh, etc.)
│   ├── lib/
│   │   ├── auth.ts                   # Session create/validate/destroy
│   │   ├── otpaas.ts                 # OTPaaS HMAC key derivation
│   │   ├── singpass.ts               # Singpass OAuth + MyInfo fetch
│   │   ├── autoAssign.ts             # Auto-assign orchestrator
│   │   ├── autoAssignRules.ts        # Rule registry (7 shipped rules)
│   │   ├── poolMatcher.ts            # Pool teacher candidate ranking
│   │   ├── queue.ts                  # BullMQ job enqueue helpers
│   │   ├── notifier.ts               # Notification routing
│   │   ├── telegram.ts               # Telegram bot client (grammy)
│   │   ├── timetableImport.ts        # CSV/XML → timetable pipeline
│   │   ├── parseAscXml.ts            # aSc XML parser
│   │   ├── prisma.ts                 # Prisma client (PrismaPg adapter)
│   │   ├── encryption.ts             # AES-256-GCM for NRIC storage
│   │   ├── planningAreas.ts          # Postal code → URA planning area
│   │   ├── constants/                # Feature flags, role mappings
│   │   ├── dto/                      # Data transfer objects
│   │   ├── scheduling/               # Cron-driven background tasks
│   │   ├── timetable/                # Timetable utilities (week type, availability)
│   │   └── types/                    # TypeScript type definitions
│   ├── workers/
│   │   ├── entry.ts                  # BullMQ worker init (3 queues)
│   │   └── handlers/
│   │       ├── matching.ts           # Pool teacher matching processor
│   │       ├── notification.ts       # Email/Telegram delivery processor
│   │       └── calendar.ts           # Google Calendar sync processor
│   └── __tests__/                    # Unit tests (25+ test files)
├── tests/
│   └── api/                          # API/integration tests
│       ├── setup.ts                  # Global setup (test DB creation)
│       └── helpers.ts                # Factory functions + test utilities
├── e2e/                              # Playwright E2E tests
│   ├── __screenshots__/              # Baseline screenshots (3 iPhone models)
│   └── fixtures/                     # Playwright fixtures
├── docs/
│   ├── plans/                        # Implementation plans
│   ├── research/                     # User interviews, competitive analysis
│   └── superpowers/                  # Feature specs and design docs
├── .env.example                      # Environment variable template
├── docker-compose.local.yml          # Local dev (Postgres + Redis)
├── docker-compose.yml                # Full stack (app + workers + migrate + seed)
├── Dockerfile                        # Multi-stage build
├── CLAUDE.md                         # Agent session rules and project conventions
├── vitest.config.ts                  # Unit test config
├── vitest.api.config.ts              # API test config
├── playwright.config.ts              # E2E test config
├── next.config.ts                    # Next.js configuration
└── package.json                      # Dependencies and scripts

Piloting ReliefCher

We are looking for Singapore schools to pilot ReliefCher. If you are a teacher, relief coordinator, HOD, or school leader and want to try a better way to manage relief coverage, we would love to hear from you.

Request early-beta access →

Or reach out to the DXD team directly — we will walk you through the platform and get your school set up.


License

This project is maintained by DXD. All rights reserved.

Releases

No releases published

Packages

 
 
 

Contributors

Languages