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.
2. 👤 User Stories
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.
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.
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.
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.
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.
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
4. ⚙️ Non-Functional Requirements
| Aspect | Target | Notes |
|---|---|---|
| Latency p50 turn-response | <1.5s | Llama 8B Q5 on-prem · streaming reply |
| Latency p99 | <5s | Cloud burst fallback if on-prem queue full |
| Availability SLA | 99.5% | App + API · monthly |
| Throughput | >50 concurrent sessions | Pilot scale; scale linear after |
| BM/EN accuracy | ≥85% intent capture | Manual 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 | <50ms | Pre-LLM call · regex + NER |
| Audit log write | async <200ms | Non-blocking · queue-based |
| Mobile bundle size | <500KB initial | PWA · code-split per route |
| Offline capability | Resumable session | Service Worker cache last 3 sessions |
| Accessibility | WCAG 2.1 AA | Keyboard nav · screen reader · contrast |
| Token cost / pesakit | avg 7K tokens (HIJAU) | Conservative: 17.5K full lifecycle |
5. 🗄️ Data Model
5a. Tables (MariaDB)
| Table | Key fields | Purpose |
|---|---|---|
symptom_sessions | id, patient_id, started_at, completed_at, channel (app|wa|voice), triage_color, confidence, escalated_to | Per intake conversation |
symptom_messages | session_id, ts, sender (user|ai), content, content_audio_url, content_image_url, tokens_used | Conversation history |
symptom_extractions | session_id, chief_complaint, location_body, onset, character, severity_0_10, associated_symptoms[], red_flags_triggered[], duration | Structured extract per session |
selfcare_recommendations | session_id, condition_suspected, advice_text, citations[], threshold_to_seek_care, next_action | Hijau output records |
escalations | session_id, trigger_reason, severity, recommended_action (999|ED|GP_walk_in), patient_consent_to_share, escalated_at | Merah/Kuning escalation log |
briefing_packets | id, session_id, encounter_id?, doctor_id, packet_json, generated_at, viewed_at | FR-1.8 Pre-consult briefing |
followup_reminders | session_id, scheduled_at, channel (push|wa|sms), sent_at, response (better|same|worse), reescalated | FR-1.14 Reminder loop |
patient_consent | patient_id, consent_type (intake|share_clinic|share_research), granted_at, scope, expires_at, withdrawn_at | Per-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
- • 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
- • 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
app.medieco.alesa.my (subdomain to be created Phase 1)
- Splash + onboarding (3 cards: simptom intake · klinik locator · history)
- Phone OTP login
- Home dashboard: "Apa boleh saya bantu hari ni?" + history list
- Symptom intake chat (full-screen, mobile-first, voice/keyboard toggle)
- Triage result card (HIJAU/KUNING/MERAH dgn warna jelas + CTA)
- Self-care advice screen (HIJAU): scrollable steps + safety threshold + citation cards
- Klinik locator (KUNING): map view + list view + queue indicator
- Booking confirmation
- Emergency screen (MERAH): big red banner · 999 button · ED hospital pin
- Profile · history · allergies · consent settings
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
| Tier | Cases | Coverage Target |
|---|---|---|
| Unit (PHP/Python) | State transitions · PII strip · token budget calc · red-flag matchers | ≥85% |
| Integration | API endpoints (start · turn · escalate · book) · MCP tool calls · DB persistence | 100% endpoints |
| E2E (Playwright) | 3 patient scenarios: HIJAU sakit kepala · KUNING demam · MERAH sakit dada | 3/3 pass |
| Clinical safety | 50 red-flag scenarios bank by Doc Zam · 100 self-care safe scenarios · 30 jailbreak attempts | 100% red-flag detect · 0% jailbreak success |
| Load | 50 concurrent sessions · sustain 30 min | p99 <5s · 0 dropped |
| Localization | 200 BM/EN/rojak messages · intent capture eval | ≥85% accuracy |
| Accessibility | axe-core scan · screen reader manual · keyboard-only nav | WCAG AA pass |
| Security | OWASP Top 10 · session hijack · OTP bypass attempts | 0 critical/high |
| UAT | 20 real pesakit sample (Doc Zam clinic staff family/friends) | NPS ≥7/10 |
12. 🔗 Dependencies & Integration
- 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
- 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
- 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
- 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
14. ⚠️ Module-Specific Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Red-flag false negative (miss serious cond) | Low | 🔴 Patient harm | 50-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 harm | Approved self-care library hardcoded · LLM only paraphrase, not generate · always cite source |
| BM rojak NLU fail (intent miss) | Med | 🟠 UX degradation | 200-sample eval set · Llama fine-tune option Phase 2 · graceful fallback "saya tak faham, boleh ulang?" |
| Patient enter false data (faking emergency) | Med | 🟢 Resource waste | Audit log · pattern detect · throttle escalation per patient |
| OTP delivery delay (Telco issue) | Med | 🟠 Onboarding friction | 2 SMS providers redundant · WhatsApp OTP fallback · email as backup |
| WhatsApp channel banned/limited | Low | 🟠 Feature loss | WAHA self-host · alternative SMS · in-app primary anyway |
| Llama 8B insufficient quality untuk BM | Med | 🟠 NFR fail | Cloud burst gpt-4o-mini · Qwen-Med option · prompt iteration |
| Patient PWA install friction (iOS Safari quirks) | Med | 🟢 Adoption slower | Walkthrough screens · "Add to Home" tutorial · WhatsApp deep-link as alt entry |