Data Flow Diagrams
Context and levelled process diagrams for how applicants, students, lecturers, and staff exchange data with Portal API, the existing CMS, planned Postgres, and the evidence file volume.
Level 0 — Context diagram
The Raisd portal control plane as a single process.
0. Portal Control Plane
Four role populations + IdP; three persistence backends.
flowchart TB
A(["Applicant"])
S(["Student"])
L(["Lecturer"])
T(["Staff / Registry / Bursary"])
IDP(["Identity Provider / CMS Login"])
P0(("0
Raisd Portal
Control Plane"))
CMS[("D1 Existing CMS
System of Record")]
PG[("D2 Postgres
planned")]
FS[("D3 Evidence / file volume")]
MAIL(["UltaCP Mail
notifications only"])
A -->|"application, evidence, accept offer"| P0
P0 -->|"status, offer, enrolment ack"| A
S -->|"registration, payments, submissions, forms"| P0
P0 -->|"timetable, invoices, results, materials"| S
L -->|"materials, attendance, marks, messages"| P0
P0 -->|"class lists, submissions, published grades"| L
T -->|"review, allocate, publish, configure"| P0
P0 -->|"queues, ledgers, case status"| T
IDP -->|"auth assertion / session subject"| P0
P0 <-->|"enrolment, marks, ledger, applications"| CMS
P0 <-->|"portal-owned durable state"| PG
P0 <-->|"file bytes + metadata refs"| FS
P0 -.->|"outbound notices"| MAIL
Level 1 — Portal system decomposition
Major processes inside the control plane boundary.
1.x processes
Web shells → Portal API → domain handlers → stores / CMS anti-corruption layer.
flowchart TB
subgraph External
A(["Applicants"])
S(["Students"])
L(["Lecturers"])
T(["Staff"])
IDP(["IdP / CMS login"])
end
subgraph ControlPlane["Control plane"]
P1(("1.1
Role portal
web shells"))
P2(("1.2
Auth &
session"))
P3(("1.3
Portal API
RPC gateway"))
P4(("1.4
Admissions
handler"))
P5(("1.5
Academic &
registration"))
P6(("1.6
Finance
handler"))
P7(("1.7
Teaching &
assessment"))
P8(("1.8
Forms / immigration
/ graduation"))
P9(("1.9
CMS ACL
CAP-53"))
end
subgraph Stores
DS[("D0 Session store
in-memory today")]
CMS[("D1 Existing CMS")]
PG[("D2 Postgres planned")]
FS[("D3 Evidence files")]
MOCK[("D4 Mock PortalRecordGraph
demo fixtures")]
end
A --> P1
S --> P1
L --> P1
T --> P1
P1 -->|"HTTPS UI calls"| P3
IDP --> P2
P2 -->|"token / subject"| P3
P2 <--> DS
P3 --> P4
P3 --> P5
P3 --> P6
P3 --> P7
P3 --> P8
P4 --> P9
P5 --> P9
P6 --> P9
P7 --> P9
P8 --> P9
P4 <--> FS
P6 <--> FS
P7 <--> FS
P8 <--> FS
P9 <--> CMS
P4 -.-> PG
P5 -.-> PG
P6 -.-> PG
P3 -.->|"stage 1"| MOCK
Process catalogue
What each Level-1 process owns.
| ID | Process | Inputs | Outputs / side effects |
|---|---|---|---|
| 1.1 | Role portal webs | User actions in apply / student / teach / staff | HTTPS to Portal API; rendered views |
| 1.2 | Auth & session | Credentials / IdP assertion | Bearer token; session record (TTL 12h today) |
| 1.3 | Portal API RPC | POST /v1/portal/:method + bearer |
{ result, cms } envelope |
| 1.4 | Admissions | Application drafts, evidence files, acceptances | CMS application / offer / first enrolment |
| 1.5 | Academic & registration | Module selections, credit overrides | Registrations, study-plan views |
| 1.6 | Finance | Invoice queries, payment proofs | Allocations (bursary), funding awards |
| 1.7 | Teaching & assessment | Materials, attendance, marks, submissions | Published results (student-visible) |
| 1.8 | Forms / immigration / graduation | Form answers, case docs | Case status, linked invoices |
| 1.9 | CMS anti-corruption layer | Canonical read/write intents; never raw SQL from handlers | Legacy↔canonical map, validation, idempotent CMS ops, DQ, reconciliation |
CMS Anti-Corruption Layer (CAP-53)
Domain handlers never contain arbitrary legacy SQL. Example:
getCurrentStudyPeriod(student) may require walking
r_student → r_stdprogram → r_stdsemester → f_term → r_stdmodule
then returning a canonical StudyPeriod.
Mapping · Validation · Operations
Full responsibility list on the architecture page.
flowchart TB
H(("Domain handler"))
ACL(("1.9 CMS ACL"))
MAP[LegacyEntityMap]
DQ[DataQualityIssue]
IOP[IntegrationOperation]
CMS[(Existing CMS)]
REC[Reconciliation reports]
H -->|"canonical command / query"| ACL
ACL --> MAP
ACL --> DQ
ACL --> IOP
ACL <-->|"translated ops"| CMS
ACL --> REC
Auth & session (current + target)
Stage 1 uses stub login and an in-memory session map.
Current Stage 1 path
POST /v1/auth/login → UUID token → every portal method.
sequenceDiagram
autonumber
actor U as Browser (role portal)
participant API as Portal API
participant SS as D0 Session store
participant Mock as D4 MockPortalApi
U->>API: POST /v1/auth/login { scenarioId, ... }
API->>SS: create Session(token, TTL 12h, scenario)
API-->>U: { token }
U->>API: POST /v1/portal/{method}
Authorization: Bearer token
API->>SS: lookup token
SS-->>API: session + scenario + studentProfileId
API->>Mock: invoke PortalApi method
Mock-->>API: result
API-->>U: { result, cms: { acknowledged, mode: stub } }
Target auth — identity mapping
IdP subject → Account → Person → Role/Scope. Legacy password/email login stays behind a temporary adapter — not canonical design. RBAC scopes: role × campus × faculty × programme × module offering × operation.
flowchart LR
U(["User"])
IDP(["IdP / CMS login"])
P2(("1.2 Auth"))
DS[("Session / token store")]
P3(("1.3 Portal API"))
RBAC{{"RBAC
role × campus × faculty
× programme × offering × op"}}
H(("Domain handler"))
U -->|"login"| IDP
IDP -->|"stable subject"| P2
P2 -->|"Person / Student / Staff map"| DS
U -->|"Bearer + method"| P3
P3 --> DS
P3 --> RBAC
RBAC --> H
Admissions data flow (Milestone 2 target)
Applicant portal → evidence volume + CMS application lifecycle.
Apply → review → offer → first enrolment
Registry reviews inside existing CMS; portals surface status.
flowchart TB
A(["Applicant"])
REG(["Registry staff"])
P1(("1.1 Applicant portal"))
P3(("1.3 Portal API"))
P4(("1.4 Admissions"))
P9(("1.9 CMS ACL"))
FS[("D3 Evidence volume")]
CMS[("D1 CMS applications / offers")]
STU[("D1 / D2 Student + ProgrammeEnrolment")]
A -->|"draft application"| P1 --> P3 --> P4
A -->|"upload evidence file"| P1 --> P3 --> P4
P4 -->|"store bytes"| FS
P4 -->|"file id + checksum + type"| P9 --> CMS
A -->|"submit application"| P1 --> P3 --> P4 --> P9 --> CMS
REG -->|"review in CMS UI"| CMS
CMS -->|"status / offer"| P9 --> P4 --> P3 --> P1 --> A
A -->|"accept offer"| P1 --> P3 --> P4 --> P9
P9 -->|"create first enrolment"| STU
P9 -->|"bind student identity"| CMS
P4 -->|"enrolment ack"| P3 --> P1 --> A
Registration & fees (Milestone 3 target)
Module selection creates CMS registration; invoices and payment proofs follow. Define transaction / workflow states so UI success is not a partial write.
Register modules → invoice → proof → allocate
Proof ≠ payment: bursary verifies and writes ledger Payment / Allocation.
flowchart TB
S(["Student"])
BUR(["Bursary / Finance staff"])
P5(("1.5 Academic & registration"))
P6(("1.6 Finance"))
P9(("1.9 CMS ACL"))
CMS_R[("D1 Module registrations")]
CMS_F[("D1 Finance ledger")]
FS[("D3 Payment proof files")]
S -->|"select modules / submit registration"| P5
P5 -->|"validate credits, prereqs, window"| P5
P5 -->|"registration transaction"| P9 --> CMS_R
P9 -->|"fee lines from FeePlan"| P6
P6 -->|"issue Invoice INV-######"| P9 --> CMS_F
P6 -->|"invoice view"| S
S -->|"upload payment proof
amount, bank ref, file"| P6
P6 -->|"store proof file"| FS
P6 -->|"proof metadata queue"| P9 --> CMS_F
BUR -->|"verify proof in CMS / staff portal"| CMS_F
BUR -->|"record Payment + Allocation"| P9
P9 --> CMS_F
P6 -->|"receipt / updated balance"| S
Finance data stores detail
What moves between student UI and ledger entities.
flowchart LR
subgraph Student_visible
IV[Invoice + lines + tax]
PP[Payment proof upload]
BA[Balance / account issues]
end
subgraph Ledger_SoR
FA[FinanceAccount]
PAY[Payment]
ALL[PaymentAllocation]
ADJ[FinancialAdjustment]
FUND[FundingAward / Benefit]
end
IV --- FA
PP -.->|"staff confirms"| PAY
PAY --> ALL
ALL --> IV
FUND --> ADJ
ADJ --> FA
BA --- FA
Registration → invoice workflow states
If registration succeeds and invoice fails, status must not report UI “success”. Use idempotency keys on retries.
flowchart LR
P[PENDING] --> PR[PROCESSING]
PR --> C[CMS_COMMITTED]
C --> F[FINANCE_PENDING]
F --> OK[COMPLETED]
PR --> FAIL[FAILED]
C --> REC[REQUIRES_RECONCILIATION]
F --> REC
Teaching, submissions & results
Lecturer publishes materials; student submits; marks publish only when released. GPA/CGPA/credits come from AcademicProgressService; ResultPublication versions official history.
Materials → submit → mark → publish
Students see published results only. ResultAccessPolicy may gate visibility (funding/balance/holds).
flowchart TB
L(["Lecturer"])
S(["Student"])
REG(["Registry"])
P7(("1.7 Teaching & assessment"))
P9(("1.9 CMS ACL"))
FS[("D3 Learning / submission files")]
CMS[("D1 Marks & attendance SoR")]
L -->|"publish ModuleLearningItem / Assessment"| P7
P7 -->|"file bytes"| FS
P7 -->|"metadata"| P9 --> CMS
S -->|"download materials"| P7 --> FS
S -->|"AssignmentSubmission"| P7
P7 -->|"submission file"| FS
P7 -->|"submission metadata"| P9 --> CMS
L -->|"record Attendance"| P7 --> P9 --> CMS
L -->|"enter AssessmentResult"| P7 --> P9 --> CMS
REG -->|"publish ModuleResult / TermResult"| P9 --> CMS
CMS -->|"publishedAt set"| P7
P7 -->|"results visible"| S
deletedAt. Learning progress statuses:
not-started | viewed | completed | locked.
Stage 1 runtime (what actually runs today)
portal-api is an HTTP façade over the student-portal mock engine.
As-built Stage 1
No live CMS adapter; cms.mode = stub on every response.
flowchart LR SP[Student portal
Vite app] API[portal-api
Fastify] SS[("In-memory
Session Map")] MOCK[MockPortalApi
from student-portal] GRAPH[("PortalRecordGraph
fixture data")] SP -->|"VITE_PORTAL_API_URL"| API API <--> SS API --> MOCK MOCK <--> GRAPH API -->|"cms: stub ack"| SP
API surface domains
OpenAPI lists ~55 PortalApi methods under these groups.
flowchart TB
GW(("POST /v1/portal/:method"))
GW --> I[immigration]
GW --> F[online-forms]
GW --> A[announcements / policies]
GW --> R[lecturer-review]
GW --> D[dashboard]
GW --> AC[academic / registration / materials]
GW --> PR[profile / services]
GW --> C[community / calendar / chat]
GW --> FI[finance]
GW --> G[getPortalRecordGraph]
Deployment data path (SDD-12)
Browser → Traefik → portal Deployments on k3s / UltaHost Singapore.
Ingress to stores
Hostnames such as apply.raisd.co / api.raisd.co.
flowchart LR
B(["Browser"])
TR[Traefik ingress]
APPLY[apply Deployment]
STUD[student Deployment]
TEACH[teach Deployment]
STAFF[staff Deployment]
API[portal-api Deployment]
PG[("Postgres StatefulSet
planned")]
PVC[("PVC evidence")]
CMS[("Existing CMS
UltaHost Premium")]
B --> TR
TR --> APPLY
TR --> STUD
TR --> TEACH
TR --> STAFF
TR --> API
APPLY --> API
STUD --> API
TEACH --> API
STAFF --> API
API --> PG
API --> PVC
API --> CMS
Cross-cutting flows
Online forms, immigration, graduation
Shared pattern: definition → submission → messages → optional invoice / case.
flowchart TB
U(["Student"])
ST(["Staff desk"])
P8(("1.8 Forms / immigration / graduation"))
FS[("D3 Attachments")]
CMS[("D1 Case / form SoR")]
INV[("Invoice if fee configured")]
U -->|"start submission"| P8 --> CMS
U -->|"answers + files"| P8
P8 --> FS
P8 --> CMS
ST -->|"message / request changes"| P8 --> CMS
P8 -->|"thread"| U
P8 -->|"open ImmigrationCase / GraduationRecord"| CMS
P8 -.->|"fee from campus config"| INV