🧩 Module Spec · M1 PSPA · Patient Symptom PA · MediEco · 26 April 2026
← Modules M1 · PSPA DETAIL · Phase 1 Sprint 1.2 · 25 May - 7 Jun 2026

M1 — Patient Symptom Personal Assistant

Pesakit ngadu via app/WhatsApp. AI intake symptom dlm BM/EN, klasifikasi 3-warna triage, cadang remedi self-care atau escalate ke doktor/ED. Lifecycle continuity entry-point untuk seluruh eco.

1. 📌 Overview & Purpose

Goal: Bagi pesakit kat Malaysia satu jambatan AI yang fasih BM rojak — boleh ngadu sakit bila-bila masa, terima triage cepat, cadang self-care kalau ringan, dan auto-escalate kalau kritikal.

Why patient-first: Pesakit malu cakap depan-depan, sibuk, atau lupa simptom penting masa konsult 8-minit. Patient PA = "kawan ngadu" tanpa judgement, ingat semua, jujur cakap "ko kena pergi ED sekarang" bila perlu.

What it is NOT: Bukan diagnosis tool. Bukan ganti doktor. Bukan prescribe ubat. AI cadang self-care over-the-counter sahaja. Apa-apa yang lebih = escalate.

🟢 HIJAU
Self-care · home remedy · ~60% pesakit
🟡 KUNING
Klinik biasa · ~30% pesakit
🔴 MERAH
ED / 999 · ~10% pesakit

2. 👤 User Stories

US-1.1 (Pesakit · Hijau)

Sebagai pesakit yang sakit kepala 3 hari, saya nak ngadu kat AI dlm BM, dapat advice apa nak buat (rehat, paracetamol, hidrasi), tanpa perlu pergi klinik kalau tak teruk.

US-1.2 (Pesakit · Kuning)

Sebagai pesakit yang demam 38.5°C tak hilang 2 hari, saya nak AI cadang klinik berhampiran, book appointment, dan hantar simptom history ke doktor sebelum saya sampai.

US-1.3 (Pesakit · Merah)

Sebagai pesakit dgn sakit dada tiba-tiba + sesak nafas, saya nak AI alert saya SEKARANG telefon 999, jangan cakap "klinik berhampiran" je — auto-escalate dgn jelas.

US-1.4 (Pesakit · Recurring)

Sebagai pesakit yang dah pakai apps ni 3 kali, saya nak AI ingat history saya — alergi penicillin, asma, ubat amlodipine — tak perlu tanya balik setiap kali.

US-1.5 (Doktor · Briefing recipient)

Sebagai doktor on-duty di klinik, saya nak terima briefing packet bila pesakit book appointment — ringkasan simptom + triage + history — supaya saya sedia bila pesakit masuk bilik.

US-1.6 (Pesakit · Privacy)

Sebagai pesakit, saya nak tahu data saya selamat — dah encrypt, tak share ke klinik lain tanpa consent saya, boleh delete bila-bila.

3. ✅ Functional Requirements

MUSTFR-1.1 Auth: SMS OTP login · phone-based · session 30 hari · biometric face login optional
MUSTFR-1.2 Multi-turn intake: AI tanya 5-15 soalan ikut SOAP-style (location · onset · character · radiation · alleviation · timing · severity · associated)
MUSTFR-1.3 BM rojak NLU: handle "demam panas" · "sakit perut bawah" · "headache 3 days" · campur BM-EN tanpa friction
MUSTFR-1.4 Triage classification: output 3-warna (HIJAU/KUNING/MERAH) dgn confidence score & reasoning
MUSTFR-1.5 Red-flag auto-escalate: dada + sesak · stroke signs · paeds <3-month fever · suicide ideation · severe bleeding → IMMEDIATE 999 prompt & pre-arrival packet ke ED
MUSTFR-1.6 Self-care suggestion (HIJAU): common conditions only · cite MOH CPG · clear safety threshold (kalau X jam tak baik, ke klinik)
MUSTFR-1.7 Klinik routing (KUNING): GPS lookup → top 3 nearest clinics dlm rangkaian · queue length live · ETA · booking deep-link
MUSTFR-1.8 Pre-consult briefing: bila pesakit book appointment, hantar packet ke doctor system (triage + symptom timeline + history excerpts)
MUSTFR-1.9 History recall: per-patient pgvector store · semantic search ke past encounters · cite previous diagnosis bila relevan
MUSTFR-1.10 Allergy-aware: kalau patient ada allergy registry, AI flag dlm self-care suggestion (e.g. "elakkan ibuprofen kalau ko ada penicillin allergy" type cross-checks)
SHOULDFR-1.11 WhatsApp channel: selain in-app, accept symptom intake via WAHA · auto-fall-back kalau pesakit prefer WhatsApp
SHOULDFR-1.12 Voice input: WebRTC capture · Whisper-Large transcribe · BM/EN auto-detect
SHOULDFR-1.13 Photo symptom: ruam · luka · swelling · upload + AI describe (no diagnosis claim)
SHOULDFR-1.14 Reminder follow-up: kalau HIJAU self-care, schedule reminder ping after 24h "macam mana?" · auto re-triage
MAYFR-1.15 Family caregiver mode: single account boleh manage multiple dependents (anak, ibu bapa) dgn permission
MAYFR-1.16 Health journal export: patient boleh export own data sebagai PDF / FHIR Bundle

4. ⚙️ Non-Functional Requirements

AspectTargetNotes
Latency p50 turn-response<1.5sLlama 8B Q5 on-prem · streaming reply
Latency p99<5sCloud burst fallback if on-prem queue full
Availability SLA99.5%App + API · monthly
Throughput>50 concurrent sessionsPilot scale; scale linear after
BM/EN accuracy≥85% intent captureManual eval pada 200 sample
Red-flag detection sensitivity≥99%Eval pada 50 known red-flag scenarios — false negatives unacceptable
False positive escalation<15%Trade-off: rather over-escalate than miss
PII strip latency<50msPre-LLM call · regex + NER
Audit log writeasync <200msNon-blocking · queue-based
Mobile bundle size<500KB initialPWA · code-split per route
Offline capabilityResumable sessionService Worker cache last 3 sessions
AccessibilityWCAG 2.1 AAKeyboard nav · screen reader · contrast
Token cost / pesakitavg 7K tokens (HIJAU)Conservative: 17.5K full lifecycle

5. 🗄️ Data Model

5a. Tables (MariaDB)

TableKey fieldsPurpose
symptom_sessionsid, patient_id, started_at, completed_at, channel (app|wa|voice), triage_color, confidence, escalated_toPer intake conversation
symptom_messagessession_id, ts, sender (user|ai), content, content_audio_url, content_image_url, tokens_usedConversation history
symptom_extractionssession_id, chief_complaint, location_body, onset, character, severity_0_10, associated_symptoms[], red_flags_triggered[], durationStructured extract per session
selfcare_recommendationssession_id, condition_suspected, advice_text, citations[], threshold_to_seek_care, next_actionHijau output records
escalationssession_id, trigger_reason, severity, recommended_action (999|ED|GP_walk_in), patient_consent_to_share, escalated_atMerah/Kuning escalation log
briefing_packetsid, session_id, encounter_id?, doctor_id, packet_json, generated_at, viewed_atFR-1.8 Pre-consult briefing
followup_reminderssession_id, scheduled_at, channel (push|wa|sms), sent_at, response (better|same|worse), reescalatedFR-1.14 Reminder loop
patient_consentpatient_id, consent_type (intake|share_clinic|share_research), granted_at, scope, expires_at, withdrawn_atPer-action consent record

5b. Vector Store (pgvector)

memory_vector (per-patient namespace)
├── source: symptom_extraction
├── content_chunk: "Chest pain · onset 10:30 · radiating to left arm · severity 8/10"
├── embedding: vector(1024)  -- BGE-M3
├── metadata: { session_id, ts, triage_color }
└── created_at

Used for FR-1.9 history recall — semantic search top-K chunks per new session.

5c. Redis (working memory)

session:{session_id} → {
  patient_id, channel, started_at,
  conversation_history: [...],
  current_state: "intake|clarify|triage|recommend|escalate|book",
  extracted_so_far: {...},
  pending_questions: [...]
}
TTL: 24h · auto-flush ke MariaDB pada session_complete event

6. 🔌 API Endpoints

6a. REST (consumed oleh Patient PWA)

POST   /api/v1/patient/symptom/start
       Body: { channel: "app|wa|voice", initial_message?, locale: "ms|en" }
       Response: { session_id, ai_first_question }

POST   /api/v1/patient/symptom/turn
       Body: { session_id, message, audio_url?, image_url? }
       Response: {
         ai_reply, current_state, triage_so_far,
         followup_actions: [...],   // e.g. "ask_severity"
         meta: { tokens_used, latency_ms, citations: [] }
       }

GET    /api/v1/patient/symptom/{session_id}
       Response: { full session state + transcript }

POST   /api/v1/patient/symptom/{session_id}/escalate
       Body: { reason, requested_by: "user|ai" }
       Response: { triage_color: "RED", recommended_action, ed_routing }

GET    /api/v1/patient/clinics/nearby
       Query: lat, lng, radius_km (default 10), service_type (gp|specialist)
       Response: [{ clinic_id, name, distance_km, queue_count, eta_min, booking_url }, ...]

POST   /api/v1/patient/appointments
       Body: { session_id, clinic_id, slot_at, share_briefing: true }
       Response: { appointment_id, confirmation_url, briefing_packet_id }

GET    /api/v1/patient/history
       Response: { encounters[], allergies[], current_meds[], chronic_conditions[] }

POST   /api/v1/patient/consent
       Body: { consent_type, granted: true, scope: { ... } }

DELETE /api/v1/patient/data/{session_id}
       Response: 204 (right to erasure)

6b. MCP Tools (used oleh Patient PA agent)

Tool                       Purpose
─────────────────────────────────────────────────────────
symptom_classifier         Classify intake → 3-warna + confidence
red_flag_detector          Match against 30+ red-flag patterns
cpg_lookup                 Retrieve relevant MOH CPG excerpt
selfcare_db                Query approved self-care advice library
clinic_finder              GPS-based nearest clinic in network
slot_find                  Available appointment slots
briefing_compose           Generate briefing packet for doctor
patient_history_search     pgvector semantic search per-patient
allergy_lookup             Check patient allergy registry
waha_send                  WhatsApp message dispatch
audit_write                Log every action

6c. Webhook Events Emitted

patient.symptom.started        { session_id, patient_id, channel, ts }
patient.symptom.turn           { session_id, turn_no, sender, ts }
patient.symptom.completed      { session_id, triage_color, summary }
patient.escalated              { session_id, severity, action_recommended }
patient.briefing.ready         { briefing_id, encounter_id?, doctor_id }
patient.followup.scheduled     { session_id, reminder_at }
patient.followup.responded     { session_id, response, reescalated }

7. 🔁 State Machine

            ┌─────────┐
            │ START   │ user enters app · auth OTP
            └────┬────┘
                 ▼
        ┌────────────────┐
        │ INTAKE         │ AI greets · asks chief complaint
        │ (FR-1.2)       │
        └────┬───────────┘
             │ user replies
             ▼
        ┌────────────────────┐         ┌─────────────────────┐
        │ RED_FLAG_CHECK     │────yes──▶│ ESCALATE_RED        │
        │ (FR-1.5)           │         │ (auto-999 prompt)   │
        └────┬───────────────┘         └─────────┬───────────┘
             │ no                                │
             ▼                                   ▼
        ┌────────────────┐                  ┌─────────────┐
        │ CLARIFY        │ ◀── loop        │ EXIT_RED    │
        │ ask 5-15 Qs    │     (max 15)     │ pre-arrival │
        │ extract SOAP   │                  │ packet → ED │
        └────┬───────────┘                  └─────────────┘
             │ confidence ≥ threshold
             ▼
        ┌────────────────┐
        │ TRIAGE         │ classify HIJAU | KUNING | MERAH
        │ (FR-1.4)       │
        └────┬───────────┘
             │
   ┌─────────┼─────────────┬──────────────┐
   ▼         ▼             ▼              ▼
┌──────┐ ┌──────┐    ┌──────────┐    ┌──────────┐
│HIJAU │ │KUNING│    │MERAH     │    │UNCERTAIN │
│self- │ │klinik│    │(escalate │    │→ default │
│care  │ │route │    │as RED)   │    │KUNING+   │
│      │ │+book │    │          │    │advisory  │
└──┬───┘ └──┬───┘    └──┬───────┘    └──┬───────┘
   │        │           │               │
   ▼        ▼           ▼               ▼
┌─────────────────────────────────────────────┐
│ EXIT · session_complete → emit events       │
│ + schedule followup reminder if HIJAU       │
└─────────────────────────────────────────────┘

States persisted di Redis (working) + flushed ke MariaDB (long-term) on EXIT.
Implementation: LangGraph dgn typed state object.

8. 🤖 Agent Specification

Model Routing
  • • Default: Llama 3.1 8B Q5 (on-prem, fast)
  • • Complex/uncertain: Llama 70B (on-prem Hi-End)
  • • Cloud burst: gpt-4o-mini (peak overflow)
  • • ASR: Whisper-Large v3 (on-prem)
  • • Embedding: BGE-M3 multilingual
Memory Tier
  • • Working: Redis (24h TTL)
  • • Long-term: pgvector per-patient namespace
  • • Procedural: MOH CPG library (vector + structured)
  • • Episodic: NOT used in M1 (M4 doctor only)

8a. System Prompt (template)

You are M1 Patient Symptom PA dlm MediEco hospital agentic eco-system.
Anda adalah jambatan AI yang fasih BM rojak antara pesakit dgn doktor.

ROLE:
- Tanya simptom secara empati, BM rojak natural
- Klasifikasi 3-warna triage: HIJAU (self-care) · KUNING (klinik) · MERAH (ED/999)
- Cadang remedi self-care HANYA untuk kondisi ringan + cite MOH CPG
- Auto-escalate kalau red-flag detected (lihat senarai bawah)

STRICT BOUNDARIES:
- NEVER prescribe ubat preskripsi (controlled, antibiotik, hipertensi, diabetes etc.)
- NEVER buat keputusan diagnosis muktamad
- NEVER cakap "you have X disease" — selalu cakap "ini boleh jadi X, sila konfirm dgn doktor"
- NEVER ignore red-flag — kalau ada keraguan, escalate

RED FLAGS (auto MERAH, no negotiation):
- Sakit dada + sesak nafas / radiate ke tangan kiri / diaphoresis
- Stroke signs: muka senget · lengan lemah · cakap pelik (FAST)
- Bayi <3-bulan demam
- Ruam non-blanching + demam (meningitis suspect)
- Suicide ideation atau severe depression
- Severe bleeding · trauma · loss of consciousness
- Severe allergic reaction · breathing difficulty · throat swelling
- Acute abdominal pain + rigid abdomen
- Stoke / heat stroke

CONTEXT:
{{patient_history_top_5}}
{{patient_allergies}}
{{patient_chronic_conditions}}
{{conversation_so_far}}

OUTPUT (strict JSON):
{
  "ai_message": "BM/EN reply to user",
  "next_state": "INTAKE | CLARIFY | TRIAGE | ESCALATE_RED | EXIT_HIJAU | EXIT_KUNING",
  "triage_so_far": "HIJAU | KUNING | MERAH | UNCERTAIN",
  "confidence": 0.85,
  "extractions": { "chief_complaint": "...", "onset": "...", "severity": 7, ... },
  "red_flags_detected": ["chest_pain_with_dyspnea"],
  "citations": ["MOH CPG Headache 2018"],
  "followup_actions": ["ask_severity", "ask_associated_symptoms"]
}

REMEMBER: Patient malu, pesakit Malaysia. Empati dulu, baru clinical. BM rojak natural.
JANGAN robotic. JANGAN over-medicalise. Kalau tak pasti, escalate.

8b. Guardrails Active (M9 cross-cut)

  • PII strip pre-LLM (IC, full name, address replaced with tokens)
  • Citation mandatory pada self-care advice (MOH CPG)
  • Red-flag bypass: tiada human approval needed untuk escalate (always allowed)
  • HITL on prescribe attempt: REFUSE silently + log incident
  • Audit log per turn (request_id traceable)
  • Feature flag: red-flag_bypass · self-care_advice · sharing_consent

9. 🎨 UI/UX

Platform: Standalone Laravel + Livewire PWA (separate dari ops admin) di app.medieco.alesa.my (subdomain to be created Phase 1)
Key screens:
  1. Splash + onboarding (3 cards: simptom intake · klinik locator · history)
  2. Phone OTP login
  3. Home dashboard: "Apa boleh saya bantu hari ni?" + history list
  4. Symptom intake chat (full-screen, mobile-first, voice/keyboard toggle)
  5. Triage result card (HIJAU/KUNING/MERAH dgn warna jelas + CTA)
  6. Self-care advice screen (HIJAU): scrollable steps + safety threshold + citation cards
  7. Klinik locator (KUNING): map view + list view + queue indicator
  8. Booking confirmation
  9. Emergency screen (MERAH): big red banner · 999 button · ED hospital pin
  10. Profile · history · allergies · consent settings
Design references: Doc Zam mock screen "Patient App & AI Triage" (slide 1 · workflow diagram). Style: clean blue/teal, soft cards, chat bubble pattern, big triage colour blocks.
Mobile-first: 360-768-1024px breakpoints. Tap-friendly (≥44px targets). Reduced motion respect.
Accessibility: WCAG 2.1 AA. Screen reader labels untuk triage result cards (aria-live). Voice input fallback untuk pesakit yang tak boleh taip.

10. ✔️ Acceptance Criteria

  • AC-1.1: Pesakit boleh complete intake → triage dlm ≤5 minit untuk symptom biasa
  • AC-1.2: 50 known red-flag scenarios → 100% detected sebagai MERAH dgn 999/ED escalation
  • AC-1.3: 100 mixed BM/EN messages → ≥85% intent capture accuracy
  • AC-1.4: Self-care advice setiap kali ada cite MOH CPG / authoritative source
  • AC-1.5: AI tidak pernah prescribe controlled/Rx-only ubat (test 30 jailbreak attempts)
  • AC-1.6: Briefing packet generated <3s post booking, contain triage + symptom + history
  • AC-1.7: p99 latency turn-response <5s pada peak 50 concurrent
  • AC-1.8: Audit log capture setiap turn dgn request_id, patient_id (hashed), tokens, latency
  • AC-1.9: Patient boleh delete own data via API → all rows soft-deleted, audit trail preserved per MOH retention
  • AC-1.10: Doc Zam personal review 20 sample sessions → ≥18/20 clinical pathway approved

11. 🧪 Test Plan

TierCasesCoverage Target
Unit (PHP/Python)State transitions · PII strip · token budget calc · red-flag matchers≥85%
IntegrationAPI endpoints (start · turn · escalate · book) · MCP tool calls · DB persistence100% endpoints
E2E (Playwright)3 patient scenarios: HIJAU sakit kepala · KUNING demam · MERAH sakit dada3/3 pass
Clinical safety50 red-flag scenarios bank by Doc Zam · 100 self-care safe scenarios · 30 jailbreak attempts100% red-flag detect · 0% jailbreak success
Load50 concurrent sessions · sustain 30 minp99 <5s · 0 dropped
Localization200 BM/EN/rojak messages · intent capture eval≥85% accuracy
Accessibilityaxe-core scan · screen reader manual · keyboard-only navWCAG AA pass
SecurityOWASP Top 10 · session hijack · OTP bypass attempts0 critical/high
UAT20 real pesakit sample (Doc Zam clinic staff family/friends)NPS ≥7/10

12. 🔗 Dependencies & Integration

Hard dependencies (must be built first):
  • M9 AUCM (audit log infra · PII filter · feature flags) — Sprint 1.1
  • Patient data model (11-section table) — Sprint 1.1
  • Auth/RBAC + Sanctum tokens — Sprint 1.1
Soft dependencies (better with, but not blocker):
  • M3 Clinic Locator (untuk klinik routing) — fallback: hardcoded list pilot klinik
  • M4 Doctor PA (untuk briefing packet recipient) — fallback: email PDF briefing
  • WAHA running (untuk WhatsApp channel) — defer: in-app only di Sprint 1.2
External integration:
  • MOH CPG library (PDF library + vector index)
  • Twilio/SMS gateway (OTP)
  • Push notification service (FCM via Firebase)
  • Maps API (klinik locator) — Google Maps atau Mapbox

13. 🏃 Sprint Allocation

Sprint 1.2 · 25 May - 7 Jun 2026 (2 minggu)
  • Day 1-3: PWA scaffold · auth flow · OTP integration
  • Day 4-7: LangGraph state machine · agent runtime · Llama 8B serving
  • Day 8-10: Triage classifier + red-flag detector + CPG retrieval
  • Day 11-12: Self-care + klinik locator stub + booking flow
  • Day 13: Internal QA + Doc Zam clinical review (50 scenarios)
  • Day 14: Demo · sprint review · merge ke develop
Capacity: 2 backend (Laravel + Python) · 1 frontend (PWA) · 1 prompt engineer · Doc Zam advisory 4h/week

14. ⚠️ Module-Specific Risks

RiskLikelihoodImpactMitigation
Red-flag false negative (miss serious cond)Low🔴 Patient harm50-scenario regression suite · Doc Zam personal review · over-escalate bias · monitor false negative rate
Self-care advice harmful (e.g. wrong dose paracetamol)Low🔴 Patient harmApproved self-care library hardcoded · LLM only paraphrase, not generate · always cite source
BM rojak NLU fail (intent miss)Med🟠 UX degradation200-sample eval set · Llama fine-tune option Phase 2 · graceful fallback "saya tak faham, boleh ulang?"
Patient enter false data (faking emergency)Med🟢 Resource wasteAudit log · pattern detect · throttle escalation per patient
OTP delivery delay (Telco issue)Med🟠 Onboarding friction2 SMS providers redundant · WhatsApp OTP fallback · email as backup
WhatsApp channel banned/limitedLow🟠 Feature lossWAHA self-host · alternative SMS · in-app primary anyway
Llama 8B insufficient quality untuk BMMed🟠 NFR failCloud burst gpt-4o-mini · Qwen-Med option · prompt iteration
Patient PWA install friction (iOS Safari quirks)Med🟢 Adoption slowerWalkthrough screens · "Add to Home" tutorial · WhatsApp deep-link as alt entry