Business Logic & Dataflow
Compact reference for Digital Thai MoCA / BrainDI system behavior.
Actors & Components
| Actor | Component | Purpose |
|---|---|---|
| Clinical staff | Backoffice (braindi-backoffice-frontend) | Create tests, review results, manual grading, PDF export |
| Patient | Patient webapp (automoca-web-app) | Take MoCA test on tablet/browser |
| System workers | automoca-aiservice + global-asr-service | Auto-score sections & transcribe audio (no UI) |
Shared backend: automoca-api-services (braindi-backend).
Test Lifecycle
Key Statuses
| Status | Meaning |
|---|---|
created | Test link exists; patient has not started testing. |
testing | Patient is currently in or has started the test flow. |
predicting | Backend/AI scoring is processing. |
waiting for review | Test is ready for staff review/scoring. |
finished | All required scores are complete with no review required, or staff review is finalized; the result is downloadable. |
Backoffice progress label while a patient is testing
The backoffice progress label is based on the test's latest successfully submitted step. It is not a direct report of the patient webapp screen currently open. Consequently, the label intentionally lags behind the patient by one submitted item or domain boundary.
Progress is persisted in two locations by automoca-api-services:
- MongoDB: root field
lastStepon the test document. Backoffice test records and progress labels use this persisted test data. - Redis: JSON field
last_stepunder keytest:<testId>. Patient token validation and resume routing use this cache record. Progress updates retain the key's existing TTL.
| Patient webapp screen currently in progress | Backoffice status label |
|---|---|
| Patient details, terms/consent, Trail Making | รอทดสอบ |
| Cube, Clock, microphone check, Lion naming | Visuospatial |
| Rhino naming, Camel naming, Memory trial 1 | Naming |
Memory trial 2, forward digits 21854 | Memory |
Backward digits 742, Tap-on-1 tutorial, Tap-on-1 test, serial subtraction, sentence 1 | Attention |
| Sentence 2, Thai ก-word fluency, both train/bicycle Abstraction answer screens | Language |
| Both ruler/clock Abstraction answer screens, every Delayed Recall round, Orientation question 1 | Abstraction |
| Orientation questions 2–6 | Delayed Recall |
Thank-you screen after /end | ประมวลผล, then รอประเมิน when review is required or เสร็จสิ้น when no review is required |
Delayed Recall and Orientation have implementation-specific progress behavior:
- While Delayed Recall is running, the backend cannot determine that its dynamic free-recall/category-hint/multiple-choice flow is fully complete. It marks
lastStep = delayed_recallonly when the patient submits Orientation question 1. Therefore, the backoffice still displaysAbstractionduring Delayed Recall and Orientation question 1. - The backend treats all six Orientation questions as one aggregate test. During questions 2–6,
lastStepremainsdelayed_recall; when question 6 completes, it changes toorientationand the patient webapp immediately calls/end. Therefore,Orientationis not visible as an activetestinglabel in the backoffice. - Patient resume uses the same aggregate boundary:
last_step = delayed_recallresumes at the Orientation introduction/question 1, rather than at Orientation questions 2–6.
Auth Model
| Token | Used by | Protects |
|---|---|---|
| Staff bearer | Backoffice | /tests/*, /patients/*, /users/*, /usergroups, /web/tests/generate, /web/tests/staff-update-patient-detail |
| Test bearer | Patient webapp | /web/tests/* (submit answers, upload files, live speech) |
401 on backoffice → redirect to /login.
Staff login returns the account's role and primary organization. The backend also loads the account's accessible organization IDs into a Redis session cache. Staff JWTs contain user_id and signed_time but no JWT exp claim; request validation additionally requires the Redis entry, whose repository configuration expires after 15 hours. Normal logout deletes that entry.
Patient Webapp Test Page Flow (automoca-web-app)
Fixed 22-page sequence (excludes backoffice):
After page 22, backend runs async ASR + AI scoring on submitted media.
Scored total (MoCA): 30 pts — Memory (page 10) is learning only and does not count toward total score. Only delayed recall round 1 (free recall) counts toward MoCA total (0–5 pts).
MoCA Test Domains — Detail
1. Visuospatial / Executive — 5 pts
| Page | Task | Patient action | AI scoring | Points |
|---|---|---|---|---|
| 3 | Trail making | Draw path connecting circles in order | visuospatial_1 — OpenCV path analysis on drawing + stroke coordinates | 0 or 1 |
| 4 | Cube drawing | Copy 3D cube from reference | visuospatial_2 — Gemini multi-score + logistic regression pass/fail | 0 or 1 |
| 5 | Clock drawing (11:10) | Draw clock face with hands | visuospatial_3 — Gemini evaluates 3 components | 1 + 1 + 1 |
Clock sub-scores (visuospatial_3):
score_1— contour closed + circularscore_2— numbers 1–12 completescore_3— hands point to 11 and 2
Photo upload fallback → AI marks unsure, staff must grade manually.
Audio check (page 6) — not scored
| Page | Task | Patient action | Scoring |
|---|---|---|---|
| 6 | Audio check | Confirm can hear doctor's voice (speak any words) | No score — mic/ASR gate only |
Occurs after visuospatial, before naming. Patient must speak so real-time ASR detects words. If nothing is detected, the patient webapp loops between mic-test and retry pages — no skip path. User cannot proceed until ASR succeeds.
2. Naming — 3 pts
| Page | Task | Patient action | AI scoring | Points |
|---|---|---|---|---|
| 7 | สิงโต (lion) | See image → speak name | naming_1 — ASR + answer match | 0 or 1 |
| 8 | แรด (rhino) | See image → speak name | naming_2 | 0 or 1 |
| 9 | อูฐ (camel) | See image → speak name | naming_3 | 0 or 1 |
Live WSS speech during test; batch ASR after /end for final transcript.
3. Memory — not scored
| Page | Trial | Patient action | API | Scoring |
|---|---|---|---|---|
| 10 | memory_1 | App plays 5 words, patient recalls all 5 | PUT /web/tests/memory | No score |
| 10 | memory_2 | Second recall of the same 5 words | PUT /web/tests/memory | No score |
Words: หน้า, ผ้าไหม, วัด, มะลิ, สีแดง — same set reappears in Delayed Recall (page 20).
Learning phase only. Backoffice may display memory audio/transcripts for review but neither trial adds to MoCA total.
4. Attention — 6 pts
| Page | Task | Patient action | AI / rule scoring | Points |
|---|---|---|---|---|
| 11 | 21854 | Repeat digits forward | attention_1 — transcript match | 0 or 1 |
| 12 | 742 | Repeat digits backward | attention_2 — transcript match | 0 or 1 |
| 13 | Tap-1 (เคาะนิ้ว) | Tap when hearing "1" in digit stream | Rule-based tap detection (29 digits) | 0 or 1 |
| 14 | 100 − 7 | Answer 100 − 7, then keep subtracting 7 until timeout | attention_4 — ASR + Gemini | 0–3 cumulative |
Patient flow: User answers what 100 − 7 is (93), then continues subtracting 7 on each step until the recording times out. The patient webapp does not stop after a fixed number of answers.
Grading: Backoffice scoring uses only the first 5 answers (93 → 86 → 79 → 72 → 65). Additional answers recorded before timeout are stored but not counted toward the MoCA score.
Pages 11, 12, 14 use real-time WSS speech during the test.
5. Language — 3 pts
| Page | Task | Patient action | AI scoring | Points |
|---|---|---|---|---|
| 15 | จอม | Repeat sentence: ฉันรู้ว่าจอมเป็นคนเดียวที่มาช่วยงานวันนี้ | language_1 | 0 or 1 |
| 16 | แมว | Repeat sentence: แมวมักซ่อนตัวอยู่หลังเก้าอี้เมื่อมีหมาอยู่ในห้อง | language_2 | 0 or 1 |
| 17 | ก.ไก่ | Name words starting with ก within time limit | language_3 — word count + Thai prefix rules | 0 or 1 |
6. Abstraction — 2 pts
| Page | Task | Patient action | AI scoring | Points |
|---|---|---|---|---|
| 18 | รถไฟ + จักรยาน | State similarity (2 audio answers) | abstraction_1 — allowed-answer match | 0 or 1 |
| 19 | ไม้บรรทัด + นาฬิกา | State similarity (2 audio answers) | abstraction_2 — allowed-answer match | 0 or 1 |
7. Delayed Recall — 5 pts
One screen (page 20) with 3 sub-rounds. Words: หน้า, ผ้าไหม, วัด, มะลิ, สีแดง (same as page 10).
| Phase | Support | Patient flow | AI task | Counts toward MoCA? |
|---|---|---|---|---|
| 1 — Free recall | ตอบได้เอง | Say all 5 words at once. All correct → skip to orientation; else → phase 2 | delayed_recall_1 | Yes |
| 2 — Category hint | ใบ้หมวดหมู่ | Per missed word: play hint audio → wait for answer. After last missing word: all correct → skip; else → phase 3 | delayed_recall_2 | No |
| 3 — Multiple choice | ตัวเลือก | Per still-wrong word: MCQ via audio → answer. Always proceeds to orientation even if words remain wrong | delayed_recall_3 | No |
MoCA scoring (round 1 only):
- AI scores each word 0 or 1 (
score_1–score_5) by matching allowed answers in the transcript. - Round 1: single transcript checked for all 5 words. Rounds 2/3: per-word transcripts.
- MoCA total uses
delayed_recall_1only (sum of 5 word scores, 0–5 pts). Backendscore.Totaland backoffice grader both use round 1. - Rounds 2 and 3 are stored for staff review but do not change MoCA total.
Siriraj auxiliary metric (MisTotal): 3×DR1 + 2×DR2 + DR3 — exported separately, not used in MoCA total. DR2/DR3 scores cascade: only words still wrong from the prior round are counted.
8. Orientation — 6 pts
| # | Question | Patient action | AI task | Points |
|---|---|---|---|---|
| 1 | วันนี้วันที่เท่าไร | Speak answer | orientation_1 | 0 or 1 |
| 2 | เดือนนี้เดือนอะไร | Speak answer | orientation_2 | 0 or 1 |
| 3 | ปีนี้ปีพ.ศ.อะไร | Speak answer | orientation_3 | 0 or 1 |
| 4 | วันนี้วันอะไร | Speak answer | orientation_4 | 0 or 1 |
| 5 | ที่นี่ที่ไหน | Speak answer | orientation_5 | 0 or 1 |
| 6 | จังหวัดอะไร | Speak answer | orientation_6 | 0 or 1 |
Ground truth from test end timestamp + patient geolocation.
MoCA score summary
| Domain | Max pts | Counts toward total? |
|---|---|---|
| Visuospatial | 5 | Yes |
| Naming | 3 | Yes |
| Memory | — | No (learning only) |
| Attention | 6 | Yes |
| Language | 3 | Yes |
| Abstraction | 2 | Yes |
| Delayed Recall | 5 | Yes (round 1 free recall only) |
| Orientation | 6 | Yes |
| Total | 30 |
Patient Webapp Test Flow (automoca-web-app)
AI Scoring Tasks
automoca-aiservice routes Kafka messages by type:
| Group | Task types |
|---|---|
| Visuospatial | visuospatial_1, visuospatial_2, visuospatial_3 |
| Naming | naming_1, naming_2, naming_3 |
| Attention | attention_1, attention_2, attention_4 |
| Language | language_1, language_2, language_3 |
| Abstraction | abstraction_1, abstraction_2 |
| Delayed recall | delayed_recall_1, delayed_recall_2, delayed_recall_3 |
| Orientation | orientation_1 … orientation_6 |
Each task reads media from S3, runs scorer (rules / Gemini / ResNet), returns score payload via Kafka.
Staff Backoffice Flow (braindi-backoffice-frontend)
Backoffice grading modules: attention, naming, memory, visuospatial, language, abstraction, delayed-recall, orientation.
Staff can override AI scores per section before finalizing the report.
Backoffice Roles & Access Control
The system uses three technical role values. The backoffice displays different labels for two of them:
| Backoffice label | Technical role | Primary scope |
|---|---|---|
| Creator | user | Tests created by that account in its primary organization |
| Admin | admin | Tests and users in the Admin's assigned organizations |
| Organizer | superuser | All organizations |
Each staff account has one primary usergroupId. An Admin may also have accessibleUsergroupIds for additional organizations. The primary organization is always included in the effective access list. Admin accounts without additional IDs, including legacy accounts, fall back to their primary organization.
Organization selection
- Creator: no organization selector; the dashboard and generated tests use the account's primary organization.
- Admin with one organization: no organization selector; the primary organization is used.
- Admin with multiple organizations: the organization selector lists only assigned organizations.
- Organizer: the selector lists all organizations and is always shown.
- The backoffice dashboard displays one organization at a time. Admin requests without an organization default to the primary organization. The backend permits Organizer test search without an organization filter, but the backoffice waits for an organization selection before loading records.
- Only Organizer can replace an Admin's additional organization access through the API. That operation preserves the primary organization and refreshes an active Redis session immediately.
Test and backoffice capabilities
Unless a row says otherwise, “own” means a test whose stored creator user ID matches the logged-in account, and “assigned” means the test's organization is in the Admin's effective access list.
| Capability | Creator (user) | Admin (admin) | Organizer (superuser) |
|---|---|---|---|
| Log in to backoffice | Yes | Yes | Yes |
| List organizations | Primary only | Assigned organizations | All organizations |
| Generate a test link | Primary organization | Selected assigned organization | Selected organization |
| Search dashboard tests | Own only | Selected assigned organization | Selected organization in UI; any/all through API |
| Open a result | Own only | Assigned organizations | Any |
| Download test media | Own only | Assigned organizations | Any |
| Review and override scores | Own only | Assigned organizations | Any |
| Edit patient demographics | Own only | Assigned organizations | Any |
| Edit result-page note in UI | No | Yes, except when the Admin's primary organization is Siriraj | Yes |
| Update note through API | Own only | Assigned organizations | Any |
| Generate/download PDF | Own only | Assigned organizations | Any |
| Delete test | No | Assigned organizations except Siriraj | Any, including Siriraj |
Creator ownership remains the access boundary throughout the test lifecycle. A Creator cannot open or modify another Creator's test even when both accounts have the same primary organization. Admin access is organization-based and is not limited to tests created by that Admin.
The result action is available only when the test is waiting for review or finished. This status rule is the same for every role.
Siriraj behavior
Siriraj is the only organization with explicit role/action exceptions in the current implementation:
- The backoffice hides manual test-link generation whenever Siriraj is selected, for every role. The test-creation API has no equivalent Siriraj-specific check and otherwise applies the normal role scope.
- Creator cannot delete any test. Admin cannot delete a Siriraj test, enforced by the backend even when Siriraj is assigned to that Admin. Organizer can delete a Siriraj test.
- Result-page note editing checks the Admin's primary organization, not the result's organization. A primary-Siriraj Admin cannot edit notes in the UI. A multi-organization Admin whose primary organization is not Siriraj can edit a Siriraj note in the UI. The backend note-update endpoint applies normal assigned-organization access and has no Siriraj-specific note rule.
- Other patient fields remain editable for a role that can access the result.
- Scoring a Siriraj test automatically generates and submits its result to the Siriraj integration. Changing education on an already finished Siriraj test also triggers resubmission.
User and organization management APIs
There is no user-management screen in braindi-backoffice-frontend. The following routes are active in automoca-api-services and require a staff bearer token:
| Method and runtime path | Current authorization |
|---|---|
POST /users | Creator denied. Admin may create only Creator accounts in assigned organizations. Organizer may create Creator or Admin accounts, but not Organizer accounts. Additional accessible organizations are accepted only when the new account is an Admin. |
GET /users/:id | Creator may read self. Admin may read users in assigned organizations. Organizer may read any user. |
PUT /users | Creator may target self. Admin may target users whose primary organization matches the Admin's primary organization. Organizer may target any user. Accepted mutable fields are username, password, first name, last name, and phone number. |
GET /users/usergroup/:usergroup_id | Creator receives only self for the primary organization. Admin may list users in an assigned organization. Organizer may list users in any organization. |
PUT /users/:id/accessible-usergroups | Organizer only; target must be an Admin. Replaces additional organization access while retaining the Admin's primary organization. |
POST /usergroups | Organizer only; creates an organization/usergroup. |
GET /usergroups | Creator receives the primary organization, Admin receives assigned organizations, and Organizer receives all organizations. |
Current operations update staff information for every role directly in MongoDB rather than through a backoffice screen. Staff offboarding is also a manual operations/database process.
The active management APIs do not provide operations to:
- create another Organizer account;
- delete, deactivate, reactivate, or lock a staff account;
- change an account's role, email, or primary organization;
- administratively revoke a staff session;
- rename or delete an organization.
Manual database changes do not automatically refresh or delete an existing Redis session. The cached role and organization access remain in use until the cache expires, the user logs out, or operations remove/update the Redis entry.
Authorization implementation facts
- Backoffice route components do not have role-specific route guards. UI controls are conditionally displayed, while staff API endpoints apply their own backend authorization checks.
PUT /patientsandGET /patients/tests/:idrequire a valid staff bearer token but do not apply role, creator, or organization checks in the patient service.- The registered staff route
POST /tests/fileshas its test ownership/organization permission check commented out. Its controller obtains test-token context rather than applying the normal staff role matrix. - Tests retain the creating user ID and username, but the system has no structured audit history recording which staff account later changed scores or patient data, generated a PDF, or deleted a test.
Core Data Entities
| Entity | Description |
|---|---|
| Usergroup | Clinic / organization; scopes staff access |
| User | Staff account with a role, one primary usergroup, and optional additional accessible usergroups for Admin |
| Test | One MoCA session; linked to patient + test token |
| Patient | Demographics (education level affects interpretation) |
| Section score | Per-domain result from AI, rules, or manual grade |
Score Interpretation
Backoffice maps total score to cognitive level:
| Level | Meaning |
|---|---|
AD | Alzheimer's disease range |
MCI | Mild cognitive impairment |
NORMAL | Normal range |
Education level (Thai labels in UI) adjusts clinical context for staff review.
Async vs Real-Time Processing
| When | What runs | Why |
|---|---|---|
| During test | WebSocket ASR | Immediate feedback for patient UX |
After /end | Kafka ASR + AI | Full scoring pipeline on all media |
Cron job monitors delayed AI predictions → SNS alarm if threshold exceeded.
Multi-Brand Behavior
Same backend serves multiple frontends (DTM, BrainDI, Siriraj) via configured frontend URLs. Business logic is shared; branding and domain differ per deployment.