Product System Design
Product architecture, data model, services, and security model.
Last updated: 2026-05-21
Status: working architecture blueprint. This is the target design for turning the current starter monorepo into the product described in the portfolio/spec notes: a mobile-first trust engine for schools, starting with smart attendance and growing into an AI-first operating system.
Inputs Read
Product inputs:
/home/shabir/Documents/Portfolio/School Management OS.md/home/shabir/Documents/Portfolio/School Management OS - Spec.md/home/shabir/Documents/Starter AI-First Smart System README.md
Repo inputs:
README.md,PROJECT.md,AGENTS.md,DESIGN.md/docs/ai-first-workflow/docs/architecture/docs/architecture/adr-0001-strong-default-overridable- App code in
apps/web,apps/mobile,apps/nest-api,apps/docs, andapps/rust - Installed Next.js docs under
apps/web/node_modules/next/dist/docs/
External research was refreshed on 2026-05-21. References are listed at the end.
Executive Direction
Starter should be built as a secure modular monolith first, with event-driven seams and clear module boundaries, then split into independent services only when load, team ownership, or compliance pressure justifies it.
The highest-value product loop is:
Student arrives -> attendance event -> parent alert -> live dashboard -> AI risk/update signalThe safest engineering loop is:
Write source-of-truth change in Postgres
-> write outbox event in same transaction
-> worker delivers notifications, realtime updates, AI jobs, and analytics
-> every side effect is idempotent and auditableDay one should not run Postgres, MySQL, Kafka, Redis, multiple API frameworks, and AI services as equal peers. That would add distributed-system cost before the product has stable workflows. The best initial production stack is:
- Next.js web dashboard.
- Expo mobile app for parents, teachers, guards, and bus conductors.
- Hono API as the primary product API.
- Postgres as the only source-of-truth database.
- Redis for cache, rate limits, short-lived locks, and lightweight queues.
- A worker process for outbox delivery, scheduled jobs, reminders, and AI pipelines.
- S3-compatible object storage for files, report cards, receipts, and media.
- Optional FastAPI AI service once model workflows need Python ML libraries.
- Kafka or Redpanda later, only after multiple independent consumers need durable event streams.
Product Boundaries
Starter is not a full ERP, LMS content marketplace, social network, or accounting system. It is a school trust, communication, attendance, operations, finance, and intelligence platform.
Primary personas:
| Persona | Primary surface | Core need |
|---|---|---|
| School owner/principal | Web dashboard, mobile summaries | Trust, admissions, finance, risk visibility |
| Admin staff | Web dashboard | Fees, admissions, documents, parent follow-ups |
| Teacher | Mobile first, web secondary | Attendance, homework, grading, parent communication |
| Gate guard | Mobile scanner | Fast and reliable arrival/departure scan |
| Bus conductor | Mobile scanner + GPS | Bus check-in/out and route safety |
| Parent/guardian | Mobile app + WhatsApp | Child safety, homework, fees, communication |
| Student | Mobile/web later | Timetable, homework, feedback |
High-Level Architecture
+----------------------+
| apps/docs |
| product docs |
+----------+-----------+
|
+----------------+ +-------v--------+ +------------------+
| apps/web | | apps/hono-api | | apps/mobile |
| Next dashboard |<--->| Product API |<---->| Expo app |
+----------------+ +-------+--------+ +------------------+
|
+---------------+----------------+
| |
+-------v--------+ +--------v---------+
| Postgres | | Redis |
| source of truth| | cache/locks/jobs |
+-------+--------+
+--------+---------+
| |
+-------v-------------------------------v---------+
| Worker runtime |
| outbox, schedules, notifications, AI jobs |
+---+------------+----------------+----------------+
| | |
+-------v--+ +------v------+ +------v------+ +----------------+
| WhatsApp | | Email/SMS | | Payments | | Object storage |
| provider | | providers | | gateway | | S3/R2 |
+----------+ +-------------+ +-------------+ +----------------+
Later:
apps/fastapi AI service, Kafka/Redpanda event bus, dedicated realtime gateway.Service Design
Stage 1: MVP Production Services
| Runtime | Repo location | Responsibility | Notes |
|---|---|---|---|
| Web app | apps/web | Principal/admin/teacher web workflows | Keep route composition in src/app, feature UI in src/modules |
| Mobile app | apps/mobile | Parent, teacher, guard, bus workflows | Use role-specific route groups and secure token storage |
| Product API | apps/hono-api | Auth, tenancy, domain APIs, webhooks | Primary backend for production |
| Worker | apps/hono-api/src/workers | Outbox, reminders, notification dispatch, AI jobs | Same codebase at first to avoid a premature service split |
| Docs app | apps/docs | Internal/product docs | Publish architecture and API docs here later |
| Postgres | Docker/cloud managed | Primary data store | Tenant-scoped relational source of truth |
| Redis | Docker/cloud managed | Cache, rate limit, locks, queue buffer | Never source of truth for business records |
| Object storage | S3/R2 | Files, PDFs, media | Private buckets, signed URLs only |
Stage 2: Services To Split Only When Needed
| Service | When to split | Responsibility |
|---|---|---|
| AI service | Python ML/RAG workflows become non-trivial | Feature computation, model calls, scoring, RAG, evals |
| Notification service | WhatsApp/SMS/email volume grows | Provider routing, retries, templates, delivery status |
| Realtime gateway | Live dashboard/bus tracking gets high traffic | WebSocket/SSE fanout |
| Analytics service | Reports affect API latency | Materialized rollups, cohort reports, BI exports |
| Import/integration service | Many schools import legacy data | CSV/Excel/SIS connectors, MySQL import adapters |
Stage 3: Microservice Candidates
Only split after clear operational pressure:
identity-service: users, sessions, memberships, roles, permissions.attendance-service: attendance sessions, scans, daily summaries.communication-service: announcements, chat, notification commands.finance-service: invoices, payments, ledger, receipts.academics-service: timetable, homework, assessments, report cards.transport-service: routes, trips, locations, bus attendance.ai-intelligence-service: student profiles, risk signals, actions, agent runs.
Until then, keep them as modules inside apps/hono-api/src/modules.
Internal Communication
Synchronous Calls
Use HTTP JSON for direct request/response flows:
- Browser/mobile -> Hono API.
- Hono API -> payment provider, WhatsApp provider, map provider, object storage.
- Hono API -> FastAPI AI service later, only through a typed internal client.
Rules:
- Every request has
x-request-id. - Authenticated requests derive
tenantId,userId, androlefrom the session, not from the body. - Service-to-service calls use service JWTs or mTLS once split.
- Public contracts use OpenAPI. TypeScript internal clients may use Hono RPC where useful.
Asynchronous Events
Start with a transactional outbox table in Postgres:
domain write + outbox_events insert in one transaction
worker polls pending outbox events
worker marks processing/sent/failed with retry count
consumer side effects use idempotency keysThis is enough for the MVP and protects against lost notifications without Kafka.
Add Kafka/Redpanda when all are true:
- More than two independent consumers need the same events.
- Replaying historical events becomes useful.
- Worker polling becomes a bottleneck.
- Analytics, AI, notification, and audit pipelines need independent scaling.
Kafka does not remove the need for idempotency. External APIs such as WhatsApp, SMS, email, and payment providers are still at-least-once from our perspective.
Database Strategy
Primary Decision
Use Postgres as the only source-of-truth database.
Do not add MySQL as a second operational database for the core product. It increases migration, query, backup, consistency, and team complexity without improving the MVP. Use MySQL only as an integration target/source when a school or acquirer has legacy MySQL data.
Use Redis as cache and coordination, not as a database for records that must survive or be audited.
Use pgvector in Postgres for early AI/RAG search. Move to a dedicated vector database only if Postgres vector indexes cannot meet recall, latency, memory, or filtering requirements.
Tenancy Model
Use a shared Postgres database and shared tables with tenant_id on every tenant-owned table.
Why:
- Affordable private schools are small to mid-size.
- Shared tables simplify migrations and cross-tenant product analytics.
- Tenant isolation can be enforced with app authorization and Postgres row-level security.
- Enterprise isolation can still be offered later with dedicated DB/schema deployments.
Tenant rules:
- Every business table has
tenant_id. - Every query includes tenant scope at the repository layer.
- Postgres Row Level Security should be enabled for high-risk tables before production.
tenant_idfrom the client body is ignored for authenticated writes.- Cross-tenant admin access requires explicit platform role and audit trail.
Shared Columns
Most tenant-owned tables should have:
| Column | Type | Notes |
|---|---|---|
id | UUID or stable text ID | Prefer one standard ID generator before production |
tenant_id | UUID/text FK | Required for tenant-owned records |
created_at | timestamptz | Default now |
updated_at | timestamptz | Updated on mutation |
created_by | user FK nullable | Null for system imports/jobs |
updated_by | user FK nullable | Null for system imports/jobs |
deleted_at | timestamptz nullable | Soft delete for business records |
Hard-delete only ephemeral records such as expired verification codes, sessions, temporary files, and completed idempotency keys after retention.
Core Database Tables
This is a product-level data model, not a final Prisma migration. Final schema should be added in bounded modules with tests.
Identity, Tenancy, And Authorization
| Table | Purpose | Key columns and constraints |
|---|---|---|
tenants | One school organization or chain | id, name, slug unique, status, timezone, default_locale |
campuses | Physical campuses under a tenant | tenant_id, name, address, geo_lat, geo_lng |
academic_years | School year boundary | tenant_id, name, starts_on, ends_on, unique active year |
terms | Academic terms | tenant_id, academic_year_id, name, dates |
users | Login identity | Existing table, add phone, status, last_login_at later |
user_profiles | Person-specific profile data | user_id, display_name, avatar_file_id, locale |
memberships | User access to tenant/campus | tenant_id, user_id, campus_id nullable, status; unique active membership |
roles | Tenant role definitions | tenant_id nullable, code, name; platform defaults plus tenant custom roles |
permissions | Permission catalog | code unique, module, description |
role_permissions | Permission assignment | role_id, permission_id; unique pair |
membership_roles | Role assignment | membership_id, role_id; unique pair |
sessions | Auth sessions | Existing table; add device metadata, IP hash, revoked_at |
verification_codes | Email, reset, 2FA codes | Existing table; TTL cleanup |
consents | Parent/school consent records | tenant_id, subject_type, subject_id, purpose, status, source |
Authorization model:
- RBAC for coarse permissions.
- ABAC for record-level checks: teacher sees assigned sections, parent sees linked students, conductor sees assigned route, principal sees campus/tenant.
- Policy files live beside each module:
attendance.policy.ts,finance.policy.ts, etc.
People And School Structure
| Table | Purpose | Key columns and constraints |
|---|---|---|
students | Student profile | tenant_id, campus_id, student_code, names, dob, gender, status; unique (tenant_id, student_code) |
guardians | Parent/guardian person | tenant_id, names, phone, email, preferred_channel |
student_guardians | Link students to guardians | student_id, guardian_id, relationship, can_pickup, is_primary; unique pair |
staff | Teachers/admin/guards/drivers | tenant_id, user_id nullable, employee_code, role_type, status |
classes | Grade level | tenant_id, name, sort_order |
sections | Class section | tenant_id, class_id, academic_year_id, name, homeroom_teacher_id |
enrollments | Student in section/year | tenant_id, student_id, section_id, academic_year_id, status; unique active enrollment |
subjects | Subject catalog | tenant_id, code, name |
section_subjects | Subject taught in section | section_id, subject_id, teacher_staff_id |
Attendance
| Table | Purpose | Key columns and constraints |
|---|---|---|
attendance_sessions | Daily/class/gate/bus attendance window | tenant_id, session_type, section_id nullable, campus_id, date, starts_at, ends_at |
attendance_marks | Current attendance state per student/session | tenant_id, session_id, student_id, status, marked_at, marked_by, unique (session_id, student_id) |
attendance_events | Append-only scan/status event log | tenant_id, session_id, student_id, event_type, source, source_event_id, unique source id |
attendance_daily_summaries | Dashboard counters | tenant_id, campus_id, date, counts, generated_at |
student_qr_tokens | Rotating QR token metadata | tenant_id, student_id, token_hash, expires_at, revoked_at |
attendance_exceptions | Manual corrections | tenant_id, mark_id, reason, approved_by, audit fields |
Statuses:
present,absent,late,excused,left_early,unknown.
Event types:
arrival_scanned,departure_scanned,manual_marked,absence_detected,parent_confirmed_absence,correction_approved.
Communication And Notifications
| Table | Purpose | Key columns and constraints |
|---|---|---|
notification_templates | WhatsApp/SMS/email/push templates | tenant_id nullable, channel, code, locale, provider_template_id |
notifications | Logical notification command | tenant_id, audience_type, priority, template_code, status |
notification_deliveries | Per-recipient delivery attempts | notification_id, recipient_type, recipient_id, channel, status, provider_message_id, idempotency key |
announcements | School/class announcements | tenant_id, author_user_id, audience, title, body, priority, published_at |
conversations | Teacher-parent/admin chat threads | tenant_id, conversation_type, student_id nullable, status |
conversation_participants | Chat participants | conversation_id, user_id, role |
messages | Chat messages | tenant_id, conversation_id, sender_user_id, body, status |
message_attachments | Files attached to messages | message_id, file_id |
calendar_events | Events, PTMs, exams, reminders | tenant_id, title, starts_at, ends_at, audience |
Academics
| Table | Purpose | Key columns and constraints |
|---|---|---|
timetable_slots | Recurring timetable slots | tenant_id, section_id, subject_id, teacher_staff_id, weekday, times |
substitutions | Teacher substitution | slot_id, original_teacher_id, substitute_teacher_id, date |
homework_assignments | Homework assigned by teacher | tenant_id, section_subject_id, title, due_at, status |
homework_recipients | Student-specific assignment variant | homework_id, student_id, variant_level, status |
homework_submissions | Student submission | homework_id, student_id, submitted_at, status, score |
assessments | Exams/quizzes/micro-assessments | tenant_id, section_subject_id, type, title, date |
assessment_items | Questions/concepts | assessment_id, concept_id, max_score |
assessment_results | Scores per student/item | assessment_id, student_id, item_id, score |
concepts | Learning concept map | tenant_id, subject_id, name, parent_concept_id |
student_concept_mastery | Live learning gap data | tenant_id, student_id, concept_id, mastery_score, confidence |
report_cards | Generated term reports | tenant_id, student_id, term_id, status, file_id |
report_card_comments | Teacher/AI comments | report_card_id, subject_id nullable, author_type, body, approval status |
Finance And Admin
| Table | Purpose | Key columns and constraints |
|---|---|---|
fee_plans | Fee structure | tenant_id, name, academic_year_id, class/section scope |
fee_components | Tuition, transport, exam, etc. | fee_plan_id, name, amount, frequency |
student_fee_assignments | Fee plan assigned to student | student_id, fee_plan_id, effective dates |
invoices | Amount due | tenant_id, student_id, invoice_no, due_on, status, unique invoice number |
invoice_lines | Invoice detail | invoice_id, fee_component_id, amount, discount |
payments | Provider payment record | tenant_id, student_id, provider, amount, status, provider_payment_id unique |
payment_allocations | Payment applied to invoice lines | payment_id, invoice_line_id, amount |
ledger_entries | Append-only finance ledger | tenant_id, entry_type, amount, currency, source_type, source_id |
receipts | Generated receipts | payment_id, receipt_no, file_id |
admission_leads | Admissions CRM | tenant_id, lead contact, source, stage, score, assigned_to |
lead_interactions | Follow-ups | lead_id, channel, notes, next_follow_up_at |
Finance rules:
- Payments are append-only; corrections use adjustment entries.
- Verify payment webhooks with provider signatures.
- Use idempotency keys for all payment create/capture/webhook flows.
- Do not store card data.
Transport
| Table | Purpose | Key columns and constraints |
|---|---|---|
transport_routes | Bus route | tenant_id, name, campus_id, status |
vehicles | Bus/van | tenant_id, registration_no, capacity, status |
drivers | Driver profile | tenant_id, staff_id, license metadata |
bus_stops | Stop points | route_id, name, sequence, geo_lat, geo_lng |
student_transport_assignments | Student to route/stop | student_id, route_id, stop_id, effective dates |
bus_trips | One route run | route_id, vehicle_id, driver_id, trip_date, direction, status |
bus_trip_events | Boarding/deboarding scan | trip_id, student_id, event_type, scanned_at, scanned_by |
bus_locations | GPS samples | trip_id, geo_lat, geo_lng, speed, captured_at |
eta_predictions | Cached ETA per stop | trip_id, stop_id, eta_at, confidence |
Location retention should be short. Store detailed GPS samples for operational windows, then roll up or delete according to policy.
AI, Intelligence, And Automation
| Table | Purpose | Key columns and constraints |
|---|---|---|
student_intelligence_profiles | Current computed profile | tenant_id, student_id, scores JSON, generated_at, model_version |
student_signals | Atomic facts used by AI/rules | tenant_id, student_id, signal_type, severity, occurred_at, source |
risk_scores | Historical score snapshots | tenant_id, subject_type, subject_id, score_type, score, reason_codes |
ai_actions | Human-reviewable suggested actions | tenant_id, agent_type, title, risk_level, status, reasoning JSON |
ai_action_feedback | Approval/rejection/edits | ai_action_id, reviewer_user_id, decision, feedback |
agent_runs | AI execution trace metadata | tenant_id, agent_type, status, input_hash, output_ref, cost, latency |
knowledge_documents | RAG documents | tenant_id, file_id, document_type, status |
knowledge_chunks | Searchable chunks | document_id, chunk_index, text, embedding vector, metadata |
prompt_versions | Versioned system prompts | agent_type, version, body, active_from |
model_evals | Regression/eval results | agent_type, dataset_version, model, score, run_at |
AI actions never directly change high-risk records without either deterministic policy approval or human review. High-risk records include attendance corrections, finance actions, student risk flags, discipline notes, emergency alerts, and parent-facing academic summaries.
Platform And Operations
| Table | Purpose | Key columns and constraints |
|---|---|---|
files | Object metadata | tenant_id, bucket, key, mime, size, checksum, visibility |
document_signatures | Permission slips/forms | file_id, signer_user_id, signed_at, ip_hash |
audit_logs | Append-only security/business audit | tenant_id, actor, action, resource, before/after redacted JSON, request_id |
outbox_events | Reliable async event staging | tenant_id, event_type, aggregate_type, aggregate_id, payload, status, retries |
webhook_events | Provider webhook inbox | provider, event_id unique, payload, signature_valid, processed_at |
idempotency_keys | Request/side-effect idempotency | tenant_id, key, scope, request_hash, response_hash, expires_at |
rate_limit_buckets | Optional persisted rate-limit state | subject, route, window, count |
feature_flags | Tenant/platform flags | tenant_id nullable, flag, enabled, rules JSON |
API Route Design
Expose versioned product APIs under /api/v1. The current /auth/* Hono routes can remain during
transition, but new product routes should be versioned.
Response envelope:
{
"success": true,
"code": 200,
"message": "OK",
"data": {},
"requestId": "req_..."
}Error envelope:
{
"success": false,
"code": 400,
"message": "Validation failed",
"errorCode": "VALIDATION_ERROR",
"errors": [{ "field": "email", "message": "Invalid email" }],
"requestId": "req_..."
}Core Routes
| Area | Routes |
|---|---|
| Health | GET /health, GET /ready |
| Auth | POST /api/v1/auth/register, POST /api/v1/auth/login, POST /api/v1/auth/logout, POST /api/v1/auth/refresh, GET /api/v1/auth/me |
| Sessions/2FA | GET /api/v1/auth/sessions, DELETE /api/v1/auth/sessions/:id, POST /api/v1/auth/2fa/setup, POST /api/v1/auth/2fa/enable, POST /api/v1/auth/2fa/disable |
| Tenancy | GET /api/v1/tenants/current, PATCH /api/v1/tenants/current, GET /api/v1/campuses, POST /api/v1/campuses |
| Roles | GET /api/v1/roles, POST /api/v1/roles, GET /api/v1/permissions, PUT /api/v1/memberships/:id/roles |
Product Routes
| Area | Routes |
|---|---|
| Students | GET /api/v1/students, POST /api/v1/students, GET /api/v1/students/:id, PATCH /api/v1/students/:id, GET /api/v1/students/:id/feed |
| Guardians | GET /api/v1/guardians, POST /api/v1/guardians, PUT /api/v1/students/:id/guardians |
| Staff | GET /api/v1/staff, POST /api/v1/staff, PATCH /api/v1/staff/:id |
| Classes | GET /api/v1/classes, POST /api/v1/classes, GET /api/v1/sections, POST /api/v1/sections, PUT /api/v1/sections/:id/students |
| Attendance | POST /api/v1/attendance/sessions, GET /api/v1/attendance/sessions, POST /api/v1/attendance/scan, PUT /api/v1/attendance/marks/:id, GET /api/v1/attendance/daily, GET /api/v1/attendance/reports |
| Communication | POST /api/v1/announcements, GET /api/v1/announcements, POST /api/v1/conversations, GET /api/v1/conversations/:id/messages, POST /api/v1/conversations/:id/messages |
| Notifications | GET /api/v1/notifications, POST /api/v1/notifications/test, GET /api/v1/notification-deliveries/:id |
| Academics | GET /api/v1/timetable, POST /api/v1/homework, GET /api/v1/homework, POST /api/v1/homework/:id/submissions, POST /api/v1/assessments, PUT /api/v1/assessments/:id/results |
| Report cards | POST /api/v1/report-cards/generate, GET /api/v1/report-cards, POST /api/v1/report-cards/:id/share |
| Finance | GET /api/v1/fee-plans, POST /api/v1/fee-plans, GET /api/v1/invoices, POST /api/v1/invoices, POST /api/v1/payments/checkout, POST /api/v1/webhooks/payments/:provider |
| Admissions | GET /api/v1/admissions/leads, POST /api/v1/admissions/leads, POST /api/v1/admissions/leads/:id/interactions, PATCH /api/v1/admissions/leads/:id/stage |
| Transport | GET /api/v1/transport/routes, POST /api/v1/transport/routes, POST /api/v1/transport/trips, POST /api/v1/transport/trips/:id/events, POST /api/v1/transport/trips/:id/locations |
| AI | GET /api/v1/ai/briefings/today, GET /api/v1/ai/students/:id/profile, GET /api/v1/ai/actions, POST /api/v1/ai/actions/:id/approve, POST /api/v1/ai/actions/:id/reject, POST /api/v1/ai/chat |
| Files | POST /api/v1/files/presign-upload, GET /api/v1/files/:id/download-url, DELETE /api/v1/files/:id |
| Audit | GET /api/v1/audit-logs |
Route rules:
- Use plural nouns for resources.
- Use path params only for identity, not filters.
- Use query params for filters:
?sectionId=&date=&status=. - Use commands for non-CRUD actions:
/approve,/reject,/generate,/share. - Every mutating route accepts an optional
Idempotency-Keyheader. - Pagination uses cursor-based pagination:
?limit=50&cursor=....
Frontend And Mobile Route Map
Next.js Web
Use App Router route groups:
apps/web/src/app
+-- (public)
| +-- page.tsx
| +-- login/page.tsx
| +-- register/page.tsx
+-- (dashboard)
| +-- dashboard/page.tsx
| +-- attendance/page.tsx
| +-- students/page.tsx
| +-- communication/page.tsx
| +-- academics/page.tsx
| +-- finance/page.tsx
| +-- transport/page.tsx
| +-- admissions/page.tsx
| +-- ai-actions/page.tsx
| +-- settings/page.tsx
+-- api/...Next.js guidance from the installed docs supports route groups for organization without changing URLs, route handlers for backend-for-frontend endpoints, and a dedicated data access layer for server-side authorization and minimal DTOs. For Starter, use direct Hono API calls through a small DAL/client and do not pass full database objects into client components.
Expo Mobile
Use role-oriented route groups:
apps/mobile/src/app
+-- (auth)/login.tsx
+-- (teacher)/home.tsx
+-- (teacher)/attendance-scan.tsx
+-- (teacher)/homework.tsx
+-- (parent)/home.tsx
+-- (parent)/children/[studentId].tsx
+-- (parent)/fees.tsx
+-- (parent)/messages.tsx
+-- (guard)/scan.tsx
+-- (bus)/trip.tsxMobile storage rules:
- Store refresh/session handles in
expo-secure-store, not plain async storage. - Do not treat device storage as the source of truth.
- Sync write actions through server idempotency keys.
- Queue offline scans locally with signed scan payloads and expire them quickly.
Event And Kafka Design
Domain Event Names
Use past-tense names for facts:
attendance.arrival_scanned.v1attendance.departure_scanned.v1attendance.absence_detected.v1notification.delivery_requested.v1notification.delivery_succeeded.v1finance.invoice_issued.v1finance.payment_received.v1academics.homework_assigned.v1transport.bus_location_recorded.v1ai.student_signal_created.v1ai.action_suggested.v1audit.security_event_recorded.v1
Event envelope:
{
"eventId": "evt_...",
"eventType": "attendance.arrival_scanned.v1",
"occurredAt": "2026-05-21T09:00:00Z",
"tenantId": "ten_...",
"actor": { "type": "user", "id": "usr_..." },
"aggregate": { "type": "student", "id": "stu_..." },
"requestId": "req_...",
"payload": {}
}Kafka Topics Later
If Kafka/Redpanda is introduced:
| Topic | Key | Consumers |
|---|---|---|
school.attendance.events.v1 | tenantId:studentId | notifications, AI, dashboards, audit |
school.notification.commands.v1 | tenantId:recipientId | notification service |
school.notification.deliveries.v1 | tenantId:notificationId | dashboard, audit, analytics |
school.finance.events.v1 | tenantId:studentId | receipts, AI, ledger analytics |
school.transport.locations.v1 | tenantId:tripId | realtime, ETA, parent app |
school.ai.actions.v1 | tenantId:actionId | approval queue, audit |
school.audit.events.v1 | tenantId:resourceId | immutable audit storage |
Partition by tenant plus aggregate when ordering matters. Keep consumers idempotent with unique processed-event records.
Cache, Queue, And Memory Strategy
Use cache-aside for read-heavy data:
| Cache key | TTL | Invalidated by |
|---|---|---|
tenant:{tenantId}:settings | 10 minutes | tenant updated |
user:{userId}:memberships | 5 minutes | role/membership changed |
section:{sectionId}:students | 5 minutes | enrollment changed |
attendance:{tenantId}:{date}:summary | 30 seconds | attendance event |
timetable:{sectionId}:{week} | 1 hour | timetable/substitution changed |
student:{studentId}:profile-summary | 5 minutes | student/guardian/AI signal changed |
Rules:
- Do not cache payment mutation results as authority.
- Do not use write-behind for attendance, finance, or consent records.
- Use Redis locks only for short critical sections, not long business workflows.
- Use process memory only for static config, compiled templates, and permission maps with short TTLs.
Core Algorithms
QR Attendance
Use signed, rotating QR payloads:
payload = tenantId + studentId + tokenId + expiresAt + nonce
signature = HMAC(payload, tenantAttendanceSecret)Rules:
- QR tokens expire.
- Do not expose raw student IDs without signature.
- Scans are idempotent by
(session_id, student_id, direction, source_event_id). - Late/absent classification is deterministic from bell schedule and grace period.
Attendance State Machine
unknown -> present
unknown -> absent
unknown -> late
present -> left_early
absent -> excused
any -> corrected, with approval/auditAutomated absence job:
for each active attendance session after grace window:
find enrolled students without present/late mark
create absence_detected event
create absence mark
enqueue parent notification
update daily summary
create student signal if pattern threshold reachedBus ETA
Start simple:
- Haversine distance from current bus location to ordered stops.
- Rolling average speed by route segment.
- Geofence arrival/departure when bus enters/leaves stop radius.
Improve later:
- Map matching with provider APIs.
- Kalman smoothing for noisy GPS.
- Route-specific historical ETA model.
Timetable Scheduling
Phase 1:
- Manual timetable editor.
- Deterministic conflict detection: teacher overlap, room overlap, section overlap, working hours.
Phase 2:
- Constraint solver such as OR-Tools CP-SAT for auto-scheduling.
- Hard constraints: teacher availability, class hours, room capacity, subject frequency.
- Soft constraints: avoid teacher overload, distribute hard subjects, minimize gaps.
Student Intelligence Score
Start with explainable deterministic scoring, not black-box prediction.
Inputs:
- Attendance trend.
- Late frequency.
- Homework completion.
- Assessment trajectory.
- Concept mastery decline.
- Parent engagement.
- Fee stress signal, used carefully and never as an academic punishment.
- Behavior notes.
Output:
{
"studentId": "stu_...",
"lifeScore": 84,
"attendanceRisk": "medium",
"learningRisk": "low",
"parentEngagement": 72,
"reasonCodes": [
"absent_2_mondays_in_4_weeks",
"math_score_declined_12_percent"
],
"confidence": 0.74
}Principles:
- Show reason codes.
- Track score version.
- Allow human feedback.
- Monitor false positives.
- Do not use risk score as punishment or exclusion.
Notification Routing
Algorithm:
choose channel from priority + consent + parent preference + message urgency
dedupe by recipient + template + aggregate + time window
send through provider
retry with exponential backoff
fail over channel when allowed
record provider statusPriority order:
- Emergency: app push + WhatsApp + SMS, with admin confirmation.
- Attendance safety: WhatsApp first, SMS fallback.
- Finance: WhatsApp/email, payment link signed and expiring.
- General announcements: app push + WhatsApp/email based on preference.
Parent Channel Preference
Phase 1:
- Rule-based preference from explicit settings and recent response history.
Phase 2:
- Thompson sampling or another conservative multi-armed bandit for send-time/channel choice.
- Optimize response probability, not pressure.
- Respect consent and quiet hours.
AI And RAG
Use AI as a supervised assistant:
- Generate report card comments for teacher approval.
- Draft parent messages.
- Summarize daily/Friday briefings.
- Explain student risk signals.
- Generate homework/test variants from approved curriculum context.
- Answer parent questions only from tenant-approved knowledge.
Implementation:
- Use structured outputs for any AI result consumed by code.
- Use tenant-filtered retrieval from Postgres/pgvector.
- Store prompt versions and model versions.
- Run evals before changing prompts/models.
- Use human approval for high-risk actions.
RAG retrieval:
query -> tenant filter -> vector search -> permission filter -> rerank -> answer with citationsDo not let AI tools call unrestricted product APIs. Each tool must have:
- A typed schema.
- Permission checks.
- Tenant scope.
- Rate limits.
- Audit logging.
- Dry-run mode for sensitive actions.
Security And Privacy Design
Starter handles minors, location, attendance, education records, parent contact data, and payment events. Treat it as high-sensitivity software from the start.
Security Baseline
| Area | Required design |
|---|---|
| Authentication | Passwords/passkeys, email verification, TOTP/WebAuthn for admins, session revocation |
| Password storage | Argon2id preferred or bcrypt with reviewed cost; breached password screening before production |
| Sessions | HttpOnly, Secure, SameSite cookies; no production auth tokens in localStorage |
| CSRF | CSRF token or signed double-submit cookie plus origin/fetch metadata checks for cookie APIs |
| Authorization | RBAC + ABAC + object-level checks in every service method |
| Tenant isolation | Repository tenant scope plus Postgres RLS on high-risk tables |
| Input validation | Zod schemas at route boundary; reject unknown dangerous fields |
| Output filtering | DTOs only; never return raw ORM rows to clients |
| Rate limiting | Per IP, account, tenant, and route group |
| Security headers | CSP, frame denial, HSTS, no x-powered-by, strict CORS allowlist |
| Secrets | Environment/KMS, never checked in, rotated provider credentials |
| Files | Private object storage, signed URLs, MIME validation, malware scan later |
| Webhooks | Signature verification, idempotency, replay window |
| Audit | Append-only audit for auth, permissions, attendance corrections, payments, AI actions |
The current web auth context supports Hono cookie auth but still supports Python/Rust bearer tokens stored in localStorage. Before production, standardize on cookie/BFF style auth for the web and SecureStore-backed sessions for mobile.
Privacy And Compliance
Build for these obligations even before formal legal review:
- Data minimization: collect only what is required for school operations.
- Purpose limitation: student data is used for the school service, not advertising.
- Parental/school consent tracking for minors.
- Guardian access controls: guardians only see linked students.
- Location privacy: short retention for bus GPS samples.
- Biometric caution: face recognition should be optional, consented, region-aware, and off by default. Avoid it in the MVP unless a pilot specifically requires it.
- Export/delete workflows for legal requests.
- Vendor registry for WhatsApp/SMS/email/payment/AI processors.
- Regional data residency option later for enterprise deals.
AI Safety
Rules:
- AI cannot silently mark students risky without evidence and human-visible reason codes.
- AI cannot send high-impact parent messages without review unless it is a pre-approved template filled with deterministic facts.
- AI cannot make payment, discipline, enrollment, or exclusion decisions.
- Every AI output used in the product stores prompt version, model, input references, output, and reviewer decision where applicable.
- RAG answers must be tenant-scoped and cite internal sources.
- Prompt injection tests become part of evals for parent chatbot and admin assistant.
Observability And Operations
Use OpenTelemetry for traces, metrics, and logs once services move beyond simple local development.
Minimum telemetry:
- Request count, latency, and error rate by route.
- DB query latency by module.
- Outbox queue depth and retry count.
- Notification send latency, provider status, and delivery success.
- Attendance scan latency.
- Payment webhook processing time and duplicate count.
- AI cost, latency, refusal/error rate, approval rate, and edit rate.
- Tenant-level rate limit events.
Operational targets for the attendance MVP:
| Flow | Target |
|---|---|
| QR scan API response | p95 under 300 ms inside target region |
| Parent alert enqueue | under 1 second after scan commit |
| Parent alert delivery to provider | p95 under 5 seconds, provider dependent |
| Principal dashboard update | under 2 seconds |
| Outbox event loss | zero, verified by retry/idempotency |
Backups:
- Postgres point-in-time recovery.
- Daily logical backup drill.
- Object storage versioning for important generated documents.
- Restore test before production pilots.
Naming Conventions
Database
- Table names:
snake_case, plural nouns. - Columns:
snake_case. - Foreign keys:
`<entity>_id`. - Join tables:
`<left>_<right>`or domain phrase such asstudent_guardians. - Enum values: lowercase snake case in product docs; map to ORM enum style as needed.
- Indexes:
`idx_<table>_<columns>`. - Unique constraints:
`uniq_<table>_<columns>`.
API
- Paths: lowercase kebab-case or simple plural nouns.
- JSON fields: camelCase.
- Route resources: plural nouns.
- Commands: verb suffix routes only when not plain CRUD, for example
/approve,/generate. - Error codes: uppercase snake case, for example
AUTH_TOKEN_NOT_FOUND.
TypeScript
- Components: PascalCase file and export names.
- Hooks:
useThing. - Services:
ThingService. - Repositories:
ThingRepository. - Validators:
thing.validator.ts. - Policies:
thing.policy.ts. - Events:
thing.events.ts. - Tests: colocated
*.test.ts.
Recommended module layout:
apps/hono-api/src/modules/attendance
+-- attendance.routes.ts
+-- attendance.controller.ts
+-- attendance.service.ts
+-- attendance.repository.ts
+-- attendance.validator.ts
+-- attendance.policy.ts
+-- attendance.events.ts
+-- attendance.test.tsShared packages to add when implementation starts:
| Package | Purpose |
|---|---|
@school-os/contracts | Shared API/event DTO types and schemas |
@school-os/authz | Permission constants and policy helpers |
@school-os/events | Event envelope, outbox helpers, event names |
@school-os/config | Typed environment/config helpers |
@school-os/id | Standard ID generation |
Build Roadmap
Phase 0: Architecture Foundation
- Pick one production API: Hono.
- Keep FastAPI/Rust/Nest as labs until a concrete service split is justified.
- Add product schema plan and module boundaries.
- Add shared contracts package.
- Add tenant/membership/role model.
- Add request ID, audit logging, rate limit, and secure headers.
Phase 1: Smart Attendance MVP
- Tenants, campuses, academic years, classes, sections, students, guardians.
- Auth and role-based access.
- QR token generation and scanner.
- Attendance sessions, marks, events, summaries.
- WhatsApp/SMS/email notification pipeline through outbox worker.
- Principal live dashboard.
- Parent mobile attendance feed.
Phase 2: Communication And Parent Trust
- Announcements.
- Teacher-parent conversations.
- Documents and signed permission slips.
- Event calendar and reminders.
- Emergency alert workflow with step-up authentication.
Phase 3: Academics
- Timetable.
- Homework.
- Assessments and marks.
- Report card generation.
- AI-drafted comments with teacher approval.
- Learning gap concept map.
Phase 4: Finance And Admissions
- Fee plans, invoices, payments, receipts.
- Payment gateway webhooks and reconciliation.
- Due reminders.
- Basic expense ledger.
- Admission inquiry CRM and next-best-action reminders.
Phase 5: AI-First Intelligence Layer
- Student intelligence profiles.
- Daily and Friday briefings.
- AI action approval queue.
- Parent chatbot with tenant-scoped RAG.
- Early warning system with reason codes and fairness review.
- Intervention playbook.
Phase 6: Scale And Enterprise Readiness
- Kafka/Redpanda if event volume requires it.
- Dedicated notification service.
- Dedicated AI service.
- Enterprise tenant isolation options.
- Data residency controls.
- Security review, penetration test, and compliance package.
Key Technical Decisions
| Decision | Recommendation |
|---|---|
| Primary database | Postgres |
| MySQL | Integration/import target only, not core product DB |
| Cache | Redis cache-aside plus rate limits, locks, and lightweight queues |
| Kafka | Not MVP; add after outbox/worker is insufficient |
| API framework | Hono for primary product API |
| Python/FastAPI | Use later for AI/ML service, not core CRUD |
| Rust | Use later for high-throughput edge scanner/realtime/infra work if needed |
| Vector search | pgvector first |
| AI orchestration | Server-owned orchestration with typed tools, guardrails, approvals, evals |
| Auth storage web | HttpOnly cookies/BFF style |
| Auth storage mobile | SecureStore-backed session handles |
| Tenancy | Shared tables with tenant_id, policy layer, and RLS on sensitive tables |
| Service style | Modular monolith first, microservices later |
Open Decisions Before Implementation
- Standard ID format: migrate from current string CUID-style IDs to UUIDv7, or standardize CUID2 text IDs across all services.
- WhatsApp provider: Meta WhatsApp Cloud API directly, Twilio, or a regional BSP.
- Payment provider priority by market: Stripe, Razorpay, Paymob, Flutterwave, or local PSPs.
- AI provider policy: OpenAI first with abstraction, or provider-neutral from day one.
- Biometric attendance: explicitly out of MVP unless consent/legal review and a pilot demand it.
- Data residency target for first market.
- Whether to put generated report cards in object storage only or also store normalized snapshots.
Research References
- OWASP API Security Top 10 2023: https://owasp.org/API-Security/editions/2023/en/0x00-header/
- OWASP Application Security Verification Standard: https://owasp.org/www-project-application-security-verification-standard/
- OWASP CSRF Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- OWASP Session Management Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- PostgreSQL Row Security Policies: https://www.postgresql.org/docs/17/ddl-rowsecurity.html
- Apache Kafka design and transactions: https://kafka.apache.org/41/design/design/
- Redis cache-aside documentation: https://redis.io/docs/latest/develop/use-cases/cache-aside/
- OpenTelemetry documentation: https://opentelemetry.io/docs/
- NIST SP 800-63B: https://pages.nist.gov/800-63-4/sp800-63b.html
- FTC COPPA statute and guidance: https://search.ftc.gov/legal-library/browse/statutes/childrens-online-privacy-protection-act and https://www.ftc.gov/business-guidance/resources/complying-coppa-frequently-asked-questions
- FERPA directory information guidance: https://studentprivacy.ed.gov/content/directory-information
- Hono secure headers middleware: https://hono.dev/docs/middleware/builtin/secure-headers
- Expo SecureStore documentation: https://docs.expo.dev/versions/latest/sdk/securestore/
- OpenAI Responses API and Agents SDK: https://developers.openai.com/api/reference/overview and https://developers.openai.com/api/docs/guides/agents
- pgvector: https://github.com/pgvector/pgvector