# 08. Kiến trúc tổng thể

> **Đối tượng đọc**: Tech lead, mobile + backend architect, DevOps, security reviewer.
> **Mục đích**: Cung cấp bức tranh end-to-end về cách OmiScan vận hành từ camera đến kho dữ liệu, đủ chi tiết để estimate, để thiết kế chi tiết, và để on-board kỹ sư mới.

Tài liệu này theo mô hình **C4 (Context-Container-Component-Code)** đến Level 3, kèm các **ADR** (Architecture Decision Records) cho từng quyết định kiến trúc lớn.

---

## 8.1. Nguyên tắc kiến trúc cốt lõi

| # | Nguyên tắc | Ý nghĩa cụ thể với OmiScan |
|---|---|---|
| AP1 | **On-device first** | Mọi tác vụ "có thể chạy trên thiết bị" đều chạy trên thiết bị: capture, quality gate, OCR, VLM inference, JSON merge, cross-check, validation sơ bộ. Server chỉ làm những gì on-device không làm được. Mục tiêu: cost vận hành thấp, latency thấp, hoạt động ngoài thực địa khi mạng yếu. |
| AP2 | **Serverless + managed services** | Lambda, Aurora Serverless, S3, CloudFront, Cognito, EventBridge, Amplify Hosting. KHÔNG có EC2, EKS, container long-running. Lý do: volume rất nhẹ (3.300 submission/tháng) — nuôi instance là lãng phí. |
| AP3 | **Rule pack là single source of truth** | Mọi quy tắc kiểm tra ghi nhãn nằm trong rule pack JSON, signed, versioned, phân phối qua CDN. Cả mobile (sơ bộ) và server (authoritative) áp cùng rule pack → hành vi nhất quán. |
| AP4 | **Stateless backend** | Lambda không giữ state giữa các invocation. Toàn bộ state nằm trong RDS / S3 / DynamoDB (nếu có). Cho phép scale-out tự nhiên và simple. |
| AP5 | **Audit-everything** | Mọi thay đổi data (edit OCR, publish rule, approve batch) đều ghi vào `audit_logs` với who/when/before/after/reason. Tuân thủ BVDLCN + cần thiết cho cơ quan nhà nước. |
| AP6 | **Defensive boundary cho PII** | Ranh giới rõ giữa data có PII và không có PII. Ảnh có thể chứa người vô tình → encrypt at-rest + access control + lifecycle 30 ngày. Dataset research là phiên bản đã anonymize. |
| AP7 | **Cost-conscious** | Mọi quyết định kiến trúc cân nhắc cost vận hành. Bias về free tier, serverless, on-device. Mục tiêu: ≤ 10 triệu VND/tháng AWS cho production. |
| AP8 | **Cross-platform native UX** | Flutter codebase 1, nhưng UX không "lowest common denominator" — chấp nhận MethodChannel cho native experience (camera, doc scanner) khi cần. |
| AP9 | **Versioned everything** | Rule pack semver. App semver. API endpoint versioned (`/api/v1/...`). Database migrations versioned. Cho phép rollback an toàn. |
| AP10 | **Fail-safe defaults** | Khi component fail (VLM crash, network down, rule pack invalid signature), hệ thống degrade gracefully — không bao giờ "fail open" làm mất hoặc corrupt data. |

---

## 8.2. C4 Level 1 — System Context

Bức tranh bao quát: ai dùng OmiScan, OmiScan tương tác với hệ thống ngoài nào.

```
                  ┌──────────────────┐                  ┌────────────────────┐
                  │ Cán bộ Viện DD   │                  │ Lãnh đạo / NCV     │
                  │ (P1)             │                  │ (P4)               │
                  │ Mobile iOS+Android│                 │ Web                │
                  └─────────┬────────┘                  └──────────┬─────────┘
                            │                                      │
                            │                                      │
                  ┌─────────▼────────┐                  ┌──────────▼─────────┐
                  │ Chuyên viên xử lý│                  │ Cán bộ rule pack   │
                  │ data (P2)        │                  │ (P3)               │
                  │ Web              │                  │ Web                │
                  └─────────┬────────┘                  └──────────┬─────────┘
                            │                                      │
                            └─────────────────┬────────────────────┘
                                              │
                            ╔═════════════════▼═════════════════════╗
                            ║                                       ║
                            ║          OMISCAN  SYSTEM              ║
                            ║                                       ║
                            ║  • Capture nhãn thực phẩm trên thiết  ║
                            ║    bị di động (on-device AI)          ║
                            ║  • Validate theo rule pack VN         ║
                            ║  • Lưu trữ + dashboard cho Viện DD    ║
                            ║                                       ║
                            ╚═════════╦═══════════════════╦═════════╝
                                      │                   │
                          ┌───────────▼───────────┐  ┌────▼──────────────┐
                          │ AWS                   │  │ Sentry            │
                          │ (Lambda, S3, RDS,     │  │ (error tracking)  │
                          │  Cognito, EventBridge,│  │                   │
                          │  SES, CloudFront, KMS)│  │                   │
                          └───────────────────────┘  └───────────────────┘

  ┌──────────────────────────────────────────────────────────────┐
  │ Hệ thống ngoài tương lai (Phase 2+, KHÔNG trong v1):         │
  │ • API tự công bố sản phẩm — Cục ATTP                         │
  │ • Hệ thống nhãn điện tử quốc gia — elabel.gov.vn (NĐ 37 Đ.54)│
  │ • Sở Y tế tỉnh — chia sẻ data                                │
  └──────────────────────────────────────────────────────────────┘
```

**Ranh giới hệ thống** OmiScan v1:

| In | Out |
|---|---|
| Capture, OCR, VLM, validation, lưu trữ, dashboard | Cross-check với hồ sơ tự công bố Cục ATTP |
| Rule pack VN ghi nhãn (NĐ 37, TT 29) | Chia sẻ tới Sở Y tế tỉnh |
| Notification email | Tích hợp nhãn điện tử quốc gia |
| Audit logging | SMS, OTP |

---

## 8.3. C4 Level 2 — Container Diagram

```
┌────────────────────────── User devices ────────────────────────────┐
│                                                                    │
│  ┌────────────────────────┐     ┌────────────────────────────────┐ │
│  │ Mobile App (Flutter)   │     │ Web (Browser)                  │ │
│  │ iOS 16+ / Android 12+  │     │ Chrome/Safari/Edge             │ │
│  │                        │     │                                │ │
│  │ • flutter_gemma        │     │ • Next.js Dashboard (P2,P4)    │ │
│  │ • ML Kit OCR           │     │ • Next.js CMS (P3)             │ │
│  │ • SQLite local         │     │                                │ │
│  └───────────┬────────────┘     └────────────┬───────────────────┘ │
│              │                               │                     │
└──────────────┼───────────────────────────────┼─────────────────────┘
               │ HTTPS/TLS 1.3                 │ HTTPS
               │                               │
┌──────────────▼───────────────────────────────▼─────────────────────┐
│                  AWS Production Account (ap-southeast-1)            │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                      Edge Layer                              │   │
│   │                                                              │   │
│   │   ┌───────────────────────────────────────────────────────┐ │   │
│   │   │ CloudFront distributions (2 riêng biệt)              │ │   │
│   │   │ • Amplify Hosting quản lý CF riêng cho SPA           │ │   │
│   │   │   (Dashboard + CMS) — KHÔNG chung với CDN            │ │   │
│   │   │ • cdn.omiscan.vn — CF distribution RIÊNG cho         │ │   │
│   │   │   rule pack CDN (S3 rulepacks) + WAF CloudFront       │ │   │
│   │   └───────────────────────────────────────────────────────┘ │   │
│   │                                                              │   │
│   │   ┌─────────────────────────────────────────────────────┐ │   │
│   │   │ AWS WAF                                             │ │   │
│   │   │ • 1 Web ACL (us-east-1) gắn CloudFront cdn.omiscan  │ │   │
│   │   │ • 1 Web ACL (regional) gắn API Gateway REST         │ │   │
│   │   │ • Rate limit, OWASP CRS, Geo block                  │ │   │
│   │   │   (VN, JP, KR, US — VN operational, JP/KR partner   │ │   │
│   │   │   review, US USOFA dev access)                      │ │   │
│   │   └──────────────────────────┬──────────────────────────┘ │   │
│   └─────────────────────────────┼───────────────────────────────┘   │
│                                 │                                   │
│   ┌─────────────────────────────▼───────────────────────────────┐   │
│   │                  API Gateway REST API                        │   │
│   │   /submissions, /rule-packs, /reports (NO /auth/* routes)   │   │
│   │   Auth: Cognito User Pool Authorizer (built-in, no Lambda)  │   │
│   └────────────────────────────┬────────────────────────────────┘   │
│                                │                                    │
│   ┌──────────────┬─────────────┼──────────────┬──────────────────┐  │
│   │              │             │              │                  │  │
│   ▼              ▼             ▼              ▼                  │  │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐         │  │
│ │ ingest-  │ │ submission-│ │ rule-pack│ │ report-      │         │  │
│ │ handler  │ │ list-query │ │ get      │ │ generator    │         │  │
│ │ (Lambda) │ │ (Lambda)   │ │ (Lambda) │ │ (Lambda)     │         │  │
│ └────┬─────┘ └─────┬─────┘ └─────┬────┘ └──────┬───────┘         │  │
│      │             │             │             │                 │  │
│      │             │       ┌─────▼────┐  ┌─────▼────┐            │  │
│      │             │       │ S3       │  │ S3       │            │  │
│      │             │       │ rulepacks│  │ exports  │            │  │
│      │             │       │ (signed) │  │ (signed) │            │  │
│      │             │       └──────────┘  └──────────┘            │  │
│      │             │                                              │  │
│      ▼             ▼                                              │  │
│   ┌────────────────────────────────────┐    ┌──────────────────┐ │  │
│   │ Aurora PostgreSQL Serverless v2    │    │ S3 Submissions   │ │  │
│   │ (PostgreSQL 16)                    │    │ (KMS encrypted)  │ │  │
│   │ • submissions, submission_images   │◄───┤ /<id>/front.jpg  │ │  │
│   │ • users, audit_logs, rule_packs    │    │ Lifecycle        │ │  │
│   │ • scales to min 0.5 ACU when idle  │    │ 30d→Glacier Deep │ │  │
│   │   (không có auto-pause như v1)     │    │ Archive          │ │  │
│   └──────────────┬─────────────────────┘    └──────────────────┘ │  │
│                  │ (qua RDS Proxy — required cho connection pool) │  │
│                                                                  │  │
│   ┌────────────────────────────────────────────────────────────┐ │  │
│   │ Async event bus (EventBridge)                              │ │  │
│   │  submission.created → validation-engine                    │ │  │
│   │  submission.reviewed → notify P3 (next batch)              │ │  │
│   │  rulepack.published  → invalidate CloudFront cache         │ │  │
│   └─────────┬──────────────────────────────────────────────────┘ │  │
│             ▼                                                    │  │
│   ┌─────────────────────┐    ┌────────────────────────────────┐ │  │
│   │ validation-engine   │    │ rule-pack-signer               │ │  │
│   │ (Lambda)            │    │ (Lambda)                       │ │  │
│   │ • Load rule pack    │    │ • Triggered từ CMS             │ │  │
│   │ • Apply per scope   │    │ • Sign ECDSA P-256 với KMS         │ │  │
│   │ • Update submission │    │ • Push S3 + invalidate cache   │ │  │
│   └─────────────────────┘    └────────────────────────────────┘ │  │
│                                                                  │  │
│   ┌────────────────────────────────────────────────────────────┐ │  │
│   │ Cross-cutting                                              │ │  │
│   │  • Cognito (User Pool) — auth (Hosted UI/SDK; MFA bắt buộc│ │  │
│   │    cho P2/P3/P3.5/P4)                                      │ │  │
│   │  • SES — email notification, password reset                │ │  │
│   │  • KMS — 5 CMKs: Aurora, S3 submissions, S3 rulepacks,    │ │  │
│   │    S3 backups, Secrets Manager (signing key là CMK riêng) │ │  │
│   │  • CloudWatch Logs/Metrics + X-Ray + CloudTrail (audit)   │ │  │
│   │  • Route 53 Hosted Zone — DNS cho *.omiscan.vn            │ │  │
│   │  • Secrets Manager — DB credentials, API keys (KHÔNG phải │ │  │
│   │    signing keys — signing key nằm trong KMS non-exportable)│ │  │
│   │  • API Gateway logs: log request ID, method, path, status │ │  │
│   │    — KHÔNG log request/response body. Lambda logs: dùng   │ │  │
│   │    log levels, mask PII fields trước khi emit             │ │  │
│   └────────────────────────────────────────────────────────────┘ │  │
└──────────────────────────────────────────────────────────────────┘
```

---

## 8.4. C4 Level 3 — Mobile App (Flutter) component diagram

Cấu trúc theo **Clean Architecture** với 4 layer:

```
┌────────────────────────────── Presentation ──────────────────────────────┐
│                                                                          │
│  Screens                       Widgets                  State            │
│  ┌──────────────┐              ┌──────────┐             ┌──────────────┐ │
│  │ HomeScreen   │              │ Camera   │             │ Riverpod /   │ │
│  │ CaptureScreen│              │ Quality  │             │ BLoC providers│ │
│  │ EditJsonScrn │  ◄──uses─►   │ Gate     │  ◄──uses─►  │              │ │
│  │ HistoryScrn  │              │ overlay  │             │ • CaptureSt  │ │
│  │ SettingsScrn │              │ Field-   │             │ • SyncQueue  │ │
│  │              │              │ Editor   │             │ • AuthSt     │ │
│  └──────────────┘              └──────────┘             └──────────────┘ │
└──────────────────────────────────┬───────────────────────────────────────┘
                                   │
┌──────────────────────────────────▼───────────────────────────────────────┐
│                              Domain                                       │
│  Entities: Submission, Product, Rule, User                                │
│  UseCases: CaptureProductFlow, SubmitSubmission, ApplyRuleAdvisory,       │
│            DownloadModel, SyncQueue                                       │
└──────────────────────────────────┬───────────────────────────────────────┘
                                   │
┌──────────────────────────────────▼───────────────────────────────────────┐
│                              Data                                         │
│                                                                          │
│  Repositories (interface in domain, impl in data)                        │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────────┐    │
│  │ Submission       │  │ Rule pack        │  │ User                 │    │
│  │ Repository       │  │ Repository       │  │ Repository           │    │
│  └────────┬─────────┘  └────────┬─────────┘  └──────────┬───────────┘    │
│           │                     │                       │                │
│           ▼                     ▼                       ▼                │
│  Data sources                                                            │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────────┐    │
│  │ Local: SQLite    │  │ CDN HTTP client  │  │ Cognito SDK          │    │
│  │ + Secure Storage │  │ + signature verify│ │                      │    │
│  └──────────────────┘  └──────────────────┘  └──────────────────────┘    │
│                                                                          │
│  Device adapters                                                         │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────────┐    │
│  │ Camera (M1)      │  │ Quality Gate (M2)│  │ AI Pipeline (M3)     │    │
│  │ camera, vision_kit│ │ OpenCV FFI +     │  │ flutter_gemma +      │    │
│  │ doc_scanner       │ │ ML Kit text      │  │ google_mlkit_text    │    │
│  └──────────────────┘  └──────────────────┘  └──────────────────────┘    │
└──────────────────────────────────────────────────────────────────────────┘

Cross-cutting:
  • intl (i18n) — tiếng Việt + tiếng Anh fallback
  • dio + retry interceptor (HTTP client)
  • sentry_flutter (error tracking)
  • logger (structured logging)
```

**State management chính**:

| State | Scope | Tool |
|---|---|---|
| Auth (đã đăng nhập, JWT, role) | Global, persistent | Riverpod + secure storage |
| CaptureSession (4 ảnh, JSON merge in-progress) | Per session | Riverpod, transient |
| SyncQueue (submission đợi sync) | Global, persistent | Riverpod + SQLite |
| RulePack (advisory rules đã download) | Global, persistent | Riverpod + file cache |

---

## 8.5. C4 Level 3 — Backend services component diagram

```
┌──────────────────────────── ingest-handler Lambda ─────────────────────────┐
│ Trigger: POST /api/v1/submissions                                          │
│                                                                            │
│  Validate → Dedup check → S3 upload (parallel) → DB insert → EventBridge  │
│       │           │              │                  │              │       │
│       │           │              │                  │              │       │
│  ┌────▼──┐  ┌────▼─────┐  ┌─────▼──────┐  ┌───────▼──────┐  ┌────▼─────┐ │
│  │AjvJSON│  │PG SELECT │  │S3 putObject│  │ PG INSERT    │  │EventBridge│ │
│  │Schema │  │client_id │  │KMS encrypt │  │ submissions+ │  │ putEvents │ │
│  │Verify │  │+ user_id │  │×N images   │  │ images       │  │           │ │
│  └───────┘  └──────────┘  └────────────┘  └──────────────┘  └───────────┘ │
└────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────── validation-engine Lambda ──────────────────────────┐
│ Trigger: EventBridge submission.created                                    │
│                                                                            │
│  Load rule pack ←── 5min in-memory cache, fallback S3 fetch + sig verify   │
│       │                                                                    │
│  Apply rule per scope ←── filter rules by submission.product_category      │
│       │                                                                    │
│  Compute validation_result JSON                                            │
│       │                                                                    │
│  Update submissions.validation_result + status                             │
│       │                                                                    │
│  If has violation/warning → EventBridge submission.needs_review            │
│       │                                                                    │
│  → ingest-handler không block trên kết quả này (async)                     │
└────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────── rule-pack-signer Lambda ───────────────────────────┐
│ Trigger: API Gateway /api/v1/rule-packs/publish (P3 từ CMS)                │
│                                                                            │
│  RBAC check (P3 + 4-eyes approver) → Bump semver → Canonicalize JSON       │
│       → KMS Sign ECDSA P-256 → Append signature → Upload S3 + manifest         │
│       → CloudFront invalidation → EventBridge rulepack.published           │
│       → Audit log entry                                                    │
└────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────── report-generator Lambda ────────────────────────────┐
│ Trigger: API Gateway /api/v1/reports/generate (P4 dashboard)               │
│                                                                            │
│  Long-running (up to 15min Lambda timeout)                                 │
│  Query Postgres → Generate Excel/PDF/JSONL → Upload S3 export bucket       │
│  → Generate signed URL (24h TTL) → SES email link tới P4                   │
│  Audit log                                                                 │
└────────────────────────────────────────────────────────────────────────────┘
```

**Lambda config chuẩn**:

| Hạng mục | Giá trị |
|---|---|
| Runtime | Node.js 22 cho tất cả Lambda; Python 3.13 chỉ cho report-generator (pandas + openpyxl) |
| Memory | 512 MB (đủ cho serverless workload nhẹ) |
| Timeout | 30s cho ingest, 60s cho validation, 900s cho report |
| Reserved concurrency | None (free scale within account limit) |
| VPC | Lambda Ingest + Validation cần VPC để access RDS |
| Provisioned concurrency | 1 cho ingest-handler trong giờ hành chính (giảm cold start) |

---

## 8.6. Data flow chính

5 luồng được vẽ chi tiết để dev hiểu rõ.

### 8.6.1. Luồng 1 — Capture & Submit

```
  P1                  Mobile App                     Backend
   │                     │                              │
   │   "Quét sản phẩm"   │                              │
   ├────────────────────►│                              │
   │                     │ M1: Mở camera                │
   │                     │ M2: Quality gate (loop)      │
   │                     │ M1: User chụp Front          │
   │                     │ M3: VLM + OCR + cross-check  │
   │                     │ M3: Update CaptureSession    │
   │                     │ Suggest "chụp Back"          │
   │   ... (lặp 4 ảnh)   │                              │
   │                     │ M3: Merge JSON 4 ảnh         │
   │                     │ Hiển thị Edit Screen         │
   │  User edit + Submit │                              │
   ├────────────────────►│                              │
   │                     │ M4: Lưu SQLite (status=draft)│
   │                     │ M4: Sync POST /submissions   │
   │                     ├─────────────────────────────►│
   │                     │                              │ M5: Validate JSON
   │                     │                              │ M5: Dedup
   │                     │                              │ M5: S3 upload (×4 ảnh)
   │                     │                              │ M5: PG insert
   │                     │                              │ M5: EventBridge → M6
   │                     │       201 + server_id        │
   │                     │◄─────────────────────────────┤
   │                     │ M4: Update status=synced     │
   │   "Đã đẩy lên VDD"  │                              │
   │◄────────────────────┤                              │
   │                     │                              │ M6: Validate rule pack
   │                     │                              │ M6: Update status=
   │                     │                              │     pending_review or
   │                     │                              │     approved
```

### 8.6.2. Luồng 2 — Review by P2

```
  P2                Web Dashboard               Backend
   │                     │                         │
   │  Mở queue review    │                         │
   ├────────────────────►│                         │
   │                     │ GET /submissions?       │
   │                     │   status=pending_review │
   │                     ├────────────────────────►│
   │                     │  list 50 submission     │
   │                     │◄────────────────────────┤
   │                     │                         │
   │ Click submission #1 │                         │
   ├────────────────────►│                         │
   │                     │ GET /submissions/<id>   │
   │                     ├────────────────────────►│
   │                     │ Detail + signed S3 URLs │
   │                     │  cho 4 ảnh              │
   │                     │◄────────────────────────┤
   │ Sửa OCR field kcal  │                         │
   │ chọn lý do          │                         │
   ├────────────────────►│                         │
   │                     │ PATCH /submissions/<id> │
   │                     │ + audit reason          │
   │                     ├────────────────────────►│
   │                     │                         │ M5: PG update
   │                     │                         │ M5: Audit log entry
   │                     │  200                    │
   │                     │◄────────────────────────┤
   │ Duyệt batch 5 cái   │                         │
   ├────────────────────►│                         │
   │                     │ POST /submissions/      │
   │                     │   batch-approve         │
   │                     ├────────────────────────►│
   │                     │                         │ Update status=approved
   │                     │                         │ Audit log
```

### 8.6.3. Luồng 3 — Validation Engine (M6 detail)

```
  EventBridge          validation-engine Lambda            Postgres
   │ submission.created  │                                  │
   ├────────────────────►│                                  │
   │                     │ Pull rule pack từ cache hoặc S3  │
   │                     │ ─────► verify ECDSA P-256 signature  │
   │                     │ Filter rules by scope match      │
   │                     │   submission.product_category    │
   │                     │ Apply mỗi rule (JsonLogic engine)│
   │                     │ Aggregate result JSON            │
   │                     │ ────────────────────────────────►│
   │                     │ UPDATE submissions               │
   │                     │ SET validation_result, status    │
   │                     │ WHERE id = ...                   │
   │                     │ ────────────────────────────────►│
   │                     │ INSERT audit_logs                │
   │                     │                                  │
   │                     │ If has violation/warning:        │
   │                     │ EventBridge submission.needs_    │
   │                     │   review → notify P2 (SES)       │
```

### 8.6.4. Luồng 4 — Rule pack publish

```
   P3            CMS                rule-pack-signer Lambda     S3+CloudFront
    │             │                          │                        │
    │ Edit rule   │                          │                        │
    ├────────────►│                          │                        │
    │ Submit appr │                          │                        │
    ├────────────►│                          │                        │
    │             │ Notification → P3.5      │                        │
    │             │                          │                        │
    P3.5          │                          │                        │
    │ Review diff │                          │                        │
    │ Approve     │                          │                        │
    ├────────────►│                          │                        │
    │             │ POST /api/v1/rule-packs/ │                        │
    │             │   publish                │                        │
    │             ├─────────────────────────►│                        │
    │             │                          │ Bump semver            │
    │             │                          │ Canonicalize JSON      │
    │             │                          │ KMS Sign ECDSA P-256       │
    │             │                          │ Upload pack.json       │
    │             │                          ├───────────────────────►│
    │             │                          │ Update manifest.json   │
    │             │                          ├───────────────────────►│
    │             │                          │ CloudFront invalidate  │
    │             │                          ├───────────────────────►│
    │             │  200 + version           │                        │
    │             │◄─────────────────────────┤                        │
    │             │                          │ EventBridge rulepack.  │
    │             │                          │   published            │
    │ Mobile app sẽ pull manifest mỗi 6h, hoặc nhận silent push       │
```

### 8.6.5. Luồng 5 — Generate report

```
  P4              Dashboard          report-generator Lambda    SES+S3
   │                  │                       │                    │
   │ Chọn template +  │                       │                    │
   │   filter         │                       │                    │
   ├─────────────────►│                       │                    │
   │                  │ POST /reports/generate│                    │
   │                  ├──────────────────────►│                    │
   │                  │ 202 Accepted (job_id) │                    │
   │                  │◄──────────────────────┤                    │
   │  "Đang generate" │                       │                    │
   │◄─────────────────┤                       │                    │
   │                  │                       │ Query Postgres     │
   │                  │                       │ Build Excel/PDF/   │
   │                  │                       │   JSONL            │
   │                  │                       │ Upload S3 (signed) │
   │                  │                       ├───────────────────►│
   │                  │                       │ SES email link     │
   │                  │                       ├───────────────────►│
   │ Email link tải   │                       │                    │
   │◄─────────────────────────────────────────────────────────────┤
   │ Click → S3 signed URL (TTL 24h) → tải file                    │
```

---

## 8.7. Mobile architecture detail

### 8.7.1. Project structure (Flutter)

```
omiscan_mobile/
├── lib/
│   ├── main.dart                    # entry, DI bootstrap
│   ├── app.dart                     # MaterialApp + router
│   ├── core/
│   │   ├── config/                  # env, feature flags
│   │   ├── di/                      # GetIt or Riverpod providers
│   │   ├── error/                   # Failures, exceptions
│   │   ├── logger/                  # structured logging
│   │   └── theme/                   # design tokens
│   ├── domain/                      # entities + use cases (no Flutter deps)
│   │   ├── entities/
│   │   ├── repositories/            # abstract interfaces
│   │   └── usecases/
│   ├── data/                        # impl repos + data sources
│   │   ├── repositories/
│   │   ├── datasources/
│   │   │   ├── local/               # SQLite, secure storage
│   │   │   └── remote/              # HTTP client
│   │   └── models/                  # DTOs
│   ├── infrastructure/              # device adapters
│   │   ├── ai/
│   │   │   ├── gemma_service.dart   # wraps flutter_gemma
│   │   │   └── ocr_service.dart     # wraps ML Kit
│   │   ├── camera/                  # M1
│   │   ├── quality_gate/            # M2 (Laplacian, ML Kit)
│   │   └── sync/                    # M4 offline queue
│   └── presentation/
│       ├── screens/
│       │   ├── home/
│       │   ├── capture/             # M1+M2+M3 UI
│       │   ├── edit_json/
│       │   ├── history/
│       │   ├── auth/
│       │   └── settings/
│       ├── widgets/                 # reusable
│       └── routing/                 # go_router
├── ios/
│   └── Runner/
│       └── Plugins/                 # MethodChannel for VNDocumentCamera
├── android/
│   └── app/
│       └── src/main/kotlin/         # MethodChannel for ML Kit DocScan
├── assets/
│   ├── i18n/                        # vi.arb, en.arb
│   └── rule_packs/                  # bundled fallback pack
├── test/                            # unit
└── integration_test/                # e2e
```

### 8.7.2. Package dependencies chính

| Package | Vai trò |
|---|---|
| `flutter_riverpod` | State management |
| `go_router` | Navigation |
| `flutter_gemma: ^0.14.5` | Gemma 4 inference |
| `google_mlkit_text_recognition` | OCR |
| `google_mlkit_document_scanner` | Auto crop Android |
| `camera` | Camera preview + capture |
| `image` | Image processing helpers |
| `dart_opencv` (hoặc tự ffi) | Laplacian, image quality |
| `sqflite` + `sqlcipher_flutter_libs` | SQLite encrypted |
| `flutter_secure_storage` | Token storage |
| `dio` + `dio_smart_retry` | HTTP + retry |
| `sentry_flutter` | Error tracking |
| `intl` | i18n |
| `connectivity_plus` | Network state |
| `cached_network_image` | Cache thumbnails |

### 8.7.3. Model download lifecycle

```
First launch:
  1. App launch → Splash screen
  2. Check if Gemma 4 E2B model exists in app sandbox
  3. If not:
     a. Show modal "Cần tải mô hình AI 1.3GB. Hãy kết nối WiFi"
     b. Detect WiFi → bắt đầu download via NetworkSource (resumable)
     c. Progress bar + cancel option
     d. Verify SHA256 sau khi xong
     e. Move to permanent location
  4. Init flutter_gemma model
  5. Đến Home screen

Per-session:
  1. Khi mở Capture screen: lazy init nếu model chưa load vào RAM
  2. Khi user rời capture screen 10 phút: unload model
  3. Khi background: hold reference 30s rồi unload (free RAM cho OS)

Model update:
  1. App check version manifest mỗi 24h
  2. Nếu có version mới + critical → prompt user update qua WiFi
  3. Optional: update không critical, làm lúc app đang charge + WiFi
```

---

## 8.8. Backend architecture detail

### 8.8.1. AWS account setup

| Account | Purpose |
|---|---|
| **omiscan-prod** | Production workload |
| **omiscan-staging** | Pre-prod, mirror smaller scale |
| **omiscan-dev** | Dev sandbox cho engineers |
| **omiscan-shared** | Cross-account: Route 53 Hosted Zone, IAM Identity Center (Lambda dùng zip deployment — không cần ECR) |

→ 4 account, separate billing, IAM Identity Center cross-account access. Region chính: `ap-southeast-1` (Singapore — gần VN nhất, không có HCM region tại 2026-05). Region backup: `ap-southeast-3` (Jakarta).

#### 8.8.1.b — Plan B: Hosting tại Việt Nam (data residency option)

**Khi nào trigger Plan B**: Workshop tuần 1 với Viện DD chốt yêu cầu data residency tại VN (Q-WS-14). Theo Luật BVDLCN 91/2025 + Luật ANTM, dữ liệu công dân VN có thể bị yêu cầu lưu tại VN; AWS chưa có region VN tại 2026-05 (sớm nhất 2027–2028 theo announcement).

**Service mapping AWS → VN providers**:

| AWS service | VNG Cloud equivalent | Viettel Cloud equivalent | On-prem option |
|---|---|---|---|
| Lambda + API Gateway | VNG FaaS + API Gateway (beta) | Viettel Function (limited) | Containerized API trên K8s tại Viện DD |
| Aurora Postgres Serverless | VNG TiDB Cloud (managed) hoặc PostgreSQL VPS | Viettel DB-as-a-Service Postgres | Postgres self-managed |
| S3 | VNG Object Storage (S3-compatible API) | Viettel Object Storage | MinIO on-prem |
| Cognito | Auth0 region VN, hoặc Keycloak self-host | Keycloak self-host | Keycloak |
| CloudFront | VNG CDN | Viettel CDN | None (tự build) |
| KMS | VNG KMS hoặc HashiCorp Vault | HashiCorp Vault self-host | Hardware HSM tại Viện DD |
| EventBridge | RabbitMQ self-host hoặc VNG Message Queue | RabbitMQ self-host | RabbitMQ |
| **Pricing basis** | VNG Cloud pricing page (cập nhật Q1/2026) + quote nội bộ OmiGroup | Viettel IDC pricing 2026 + B2G discount estimate | OmiGroup procurement reference |

**3 phương án Plan B chi tiết**:

| | **B1 — VNG Cloud** | **B2 — Viettel Cloud** | **B3 — On-prem Viện DD** |
|---|---|---|---|
| **Datacenter** | HCM + HN, Tier 3 | HN + HCM + ĐN, Tier 3 | Tại Viện DD HN |
| **Cost / tháng prod** (~) | 12 triệu VND (≈$480) | 9 triệu VND (≈$360) | OPEX ~3 triệu (điện+bảo trì) |
| **CAPEX** | $0 | $0 | 300–500 tr (server, switch, UPS) |
| **Migration timeline** | +3–4 tuần | +3–4 tuần | +6–8 tuần |
| **Migration breakdown** | CDK rewrite Pulumi/Terraform: 5 ngày · Data migration: 2 ngày · DNS cutover: 0.5 ngày · Smoke test: 3 ngày · Hardening + DR test: 5 ngày | Tương tự B1 | Procurement: 4 tuần · Setup K8s + Postgres + monitoring: 2 tuần · Migration + cutover: 1 tuần · Test + acceptance: 1 tuần |
| **Compliance B2G** | Đã serve VTV, Vingroup, một số Sở Y tế | Hỗ trợ B2G rất mạnh, tích hợp Bộ KH&CN dễ | Tuyệt đối — dữ liệu không rời cơ quan |
| **Pros** | Familiarity (managed services tương tự AWS); SLA 99.9% | Mạng quốc gia rộng; backed by Bộ Quốc phòng/Viettel — credibility cao | Compliance tuyệt đối; OPEX rất thấp |
| **Cons** | Documentation chưa đầy đủ như AWS; ecosystem chưa rộng | Managed services ít hơn VNG | Viện DD phải tự DR/backup; tăng SRE workload OmiGroup; CAPEX lớn |
| **Recommendation rank** | **#1 (recommended)** | #2 | #3 (chỉ khi Viện DD ép) |

**Cost impact tổng** nếu trigger Plan B (so với baseline AWS Year 1 = 191tr core + 105tr hidden):

| Phương án | Year 1 vận hành (tr VND) | TCO 3 năm (tr VND) |
|---|---|---|
| Baseline AWS | 191 + 105 = 296 | ~1,8 tỷ |
| **B1 VNG Cloud** | 240 + 105 = **345** | **~1,95 tỷ** |
| **B2 Viettel** | 215 + 105 = **320** | **~1,9 tỷ** |
| **B3 On-prem** | 80 + 350 (CAPEX year 1) + 105 = **535** | **~2,2 tỷ** (CAPEX khấu hao) |

→ Plan B sẽ tăng TCO 3 năm từ 1,8 tỷ → **1,9–2,2 tỷ** (tăng 5–22%). Có ngân sách để absorb nếu cần.

**Action item**: Workshop tuần 1 phải clarify Q-WS-14 → **quyết định data residency (AWS region vs VN hosting) phải confirm trước khi CDK infra setup Sprint 2**. Em đã prepared plan switch nhanh, không cần research lại.

**Lưu ý**: Pricing trên là estimate dựa benchmark Q1/2026. Trước khi commit Plan B, OmiGroup procurement cần lấy quote chính thức từ VNG/Viettel với volume thực tế (3.300 sub/tháng + 5 user). Có thể nhận **B2G discount 10–20%** với cơ quan nhà nước.

### 8.8.2. VPC design

```
omiscan-prod-vpc (10.0.0.0/16)
├── Public subnets (3 AZ)
│   • NAT Gateway (1 cho cost; 3 nếu cần HA cao hơn)
│   (không có ALB — architecture không dùng ALB)
├── Private subnets — Application (3 AZ)
│   • Lambda functions (VPC-attached cho RDS access)
│   • RDS Proxy (required cho Lambda-to-Aurora connection pooling)
├── Private subnets — Database (3 AZ)
│   • Aurora PostgreSQL Serverless v2 cluster (PostgreSQL 16)
└── VPC endpoints (Interface)
    • S3 (Gateway — free), KMS, Secrets Manager
    • EventBridge Interface Endpoint (~$7.30/tháng/AZ)
    • Tránh đi ra Internet cho internal traffic
    (Lambda dùng zip deployment — không cần ECR VPC endpoint)
```

### 8.8.3. RDS Aurora PostgreSQL Serverless v2

- **Engine**: Aurora PostgreSQL Serverless v2, PostgreSQL 16
- **Min ACU**: 0.5 — scales to minimum capacity when idle (Aurora Serverless v2 KHÔNG có auto-pause như v1)
- **Max ACU**: 2 (đủ cho 100 concurrent connection)
- **Multi-AZ**: Yes (mặc định Aurora Serverless v2)
- **Backup**: 7 ngày automated, manual snapshot trước mọi migration
- **Encryption at-rest**: KMS CMK (`omiscan-prod-rds-cmk`)
- **Network**: Private subnet, Lambda access qua **RDS Proxy** (connection pooling), security group chỉ allow RDS Proxy SG inbound
- **Submission images**: serve qua S3 Presigned URLs trực tiếp — KHÔNG qua CloudFront

### 8.8.4. S3 buckets

Quy ước bucket name: `omiscan-{env}-<purpose>` (env = `prod`, `staging`, `dev`).

| Bucket | Mục đích | Lifecycle | Encryption | Ghi chú |
|---|---|---|---|---|
| `omiscan-{env}-submissions` | Ảnh submission gốc | 30 ngày → Glacier Deep Archive | KMS CMK | Serve qua S3 Presigned URLs (không qua CloudFront) |
| `omiscan-{env}-rulepacks` | Rule pack JSON signed | Versioning ON, vĩnh viễn | KMS | **S3 Object Lock COMPLIANCE mode** (không xóa trước retention) |
| `omiscan-{env}-exports` | File báo cáo P4 | 7 ngày → delete | KMS | |
| `omiscan-{env}-static` | SPA build artifacts (Amplify backup) | None | SSE-S3 | |
| `omiscan-{env}-logs` | CloudFront/API GW access logs | 90 ngày → Glacier Deep Archive → 1 năm delete | KMS | Không có ALB logs (không dùng ALB) |
| `omiscan-{env}-backups` | DB manual snapshot, audit cold | 7 năm (audit retention) | KMS | |

### 8.8.5. CloudFront distributions

**Amplify Hosting** quản lý CloudFront riêng cho SPA (dashboard, cms) — distribution nội bộ Amplify, không cần cấu hình thủ công.

**CloudFront distribution `cdn.omiscan.vn`** là distribution RIÊNG, tách biệt hoàn toàn với Amplify, dùng cho rule pack CDN và static assets.

| Distribution | Origin | TTL | WAF |
|---|---|---|---|
| `app.omiscan.vn` | **Amplify Hosting** (tự quản lý CF nội bộ cho Dashboard + CMS) | Amplify-managed | Amplify-managed |
| `api.omiscan.vn` | API Gateway REST | 0 (no cache) | 1 Web ACL regional + OWASP |
| `cdn.omiscan.vn` | S3 `omiscan-{env}-rulepacks` | **5 phút cho manifest** (hoặc invalidation-driven); 1 năm cho pack JSON + assets | 1 Web ACL us-east-1 + OWASP CRS + rate limit |
| `models.omiscan.vn` | S3 (Gemma 4 model) | 1 năm | rate limit |

---

## 8.9. Network & Security architecture

### 8.9.1. Authentication flow

Auth được handle bởi **Cognito Hosted UI / SDK** trực tiếp — API Gateway KHÔNG có `/auth/*` Lambda routes. API Gateway dùng **Cognito User Pool Authorizer (built-in)**, KHÔNG dùng custom Lambda Authorizer.

```
Mobile App                    Cognito Hosted UI/SDK        API Gateway
   │                                     │                       │
   │ Cognito SDK login (user, pass)      │                       │
   ├────────────────────────────────────►│                       │
   │                                     │ JWT (id, access, refr)│
   │◄────────────────────────────────────┤                       │
   │                                     │                       │
   │ Lưu vào Keychain/EncryptedSharedPref│                       │
   │                                     │                       │
   │ Subsequent API requests:            │                       │
   │ Authorization: Bearer <access JWT>  │                       │
   ├─────────────────────────────────────────────────────────────►│
   │                                     │ API GW Cognito        │
   │                                     │ Authorizer (built-in) │
   │                                     │ verify JWT → claims   │
   │                                     │ (user_id, role, exp)  │
   │                                     │◄──────────────────────┤
   │                                     │                       │
```

### 8.9.2. Security control matrix

| Control | Implementation |
|---|---|
| TLS in transit | TLS 1.3 client-server, API GW redirect HTTP→HTTPS |
| Encryption at rest | KMS CMK cho S3, RDS, Secrets Manager. Mỗi bucket có riêng (5 CMKs total) |
| Secret management | AWS Secrets Manager cho DB credentials, API tokens (KHÔNG phải signing keys — signing key là KMS CMK non-exportable) |
| IAM | Least-privilege policies, no `*` resource hoặc action; SCP cấp Org |
| Access logging | CloudFront logs, API GW logs, S3 access logs. KHÔNG log request/response body |
| WAF | 2 Web ACLs: 1 CloudFront (us-east-1), 1 API Gateway (regional). Rate limit 1000 req/5min/IP, OWASP CRS, geo block (VN, JP, KR, US — VN operational, JP/KR partner review, US USOFA dev access. Override available for Apple/Google review IPs) |
| MFA | TOTP bắt buộc cho P2/P3/P3.5/P4; TOTP + hardware key cho P5 |
| Audit log | Bảng `audit_logs` PG + ship sang S3 + CloudTrail |
| Pen test | Annual external pen test (ngoài scope v1, plan Phase 2) |
| Vulnerability scan | Snyk cho dependencies + Lambda zip scan (Trivy) |

### 8.9.3. PII handling

| PII | Where | Protection |
|---|---|---|
| Email user | Cognito + RDS users table | Hashed username, encrypted attributes |
| Tên user | RDS users.full_name | KMS encryption, access via Lambda only |
| Ảnh có thể chứa người (vô tình) | S3 submissions | KMS, lifecycle 30d, access log, download requires reason |
| IP address | CloudWatch logs | TTL 90 ngày, rồi shred |
| Geo location ảnh | submissions.metadata | Optional, user opt-in, anonymize ở mức tỉnh thay vì exact |

### 8.9.4. Compliance với BVDLCN + Luật AI

- DPIA (Data Protection Impact Assessment) — chuẩn bị 1 tài liệu trước go-live (sẽ đính trong Section 10)
- AI register theo Luật AI 134/2025 — đăng ký hệ thống "AI hỗ trợ cơ quan nhà nước" với cơ quan chuyên trách
- Đăng ký xử lý dữ liệu cá nhân — chuẩn bị form theo NĐ 356/2025

---

## 8.10. Deployment & Environments

### 8.10.1. 3 môi trường

| Env | Mục đích | URL | Resource scale |
|---|---|---|---|
| `dev` | Engineer cá nhân | `*.dev.omiscan.vn` | Aurora min 0.5 ACU (scales to min when idle — no auto-pause); Lambda 256MB |
| `staging` | Pre-prod, UAT | `*.staging.omiscan.vn` | Aurora min 0.5, max 1; mirror prod data subset |
| `prod` | Production cho Viện DD | `*.omiscan.vn` | Aurora min 0.5, max 2; full HA |

### 8.10.2. CI/CD

```
GitHub repo: omigroup/omiscan
├── .github/workflows/
│   ├── mobile-ci.yml         # PR: lint, test, build APK debug
│   ├── mobile-release.yml    # tag v*: build IPA + APK release, upload TestFlight + Play Internal
│   ├── backend-ci.yml        # PR: lint, test, build Lambda zip
│   ├── backend-deploy-dev.yml    # push main: deploy dev via CDK
│   ├── backend-deploy-staging.yml # tag rc-*: deploy staging
│   ├── backend-deploy-prod.yml   # tag v*: manual approval → deploy prod
│   ├── web-ci.yml            # PR: lint, test, build
│   ├── web-deploy.yml        # tag → Amplify
│   ├── infra-iac.yml         # CDK synth + diff + apply
│   └── security-scan.yml     # Snyk + Trivy + secret scan
```

### 8.10.3. IaC (AWS CDK in TypeScript)

```
infra/
├── bin/
│   └── omiscan.ts            # entrypoint, env wiring
├── lib/
│   ├── stacks/
│   │   ├── network-stack.ts       # VPC, subnets, NAT, endpoints
│   │   ├── data-stack.ts          # S3 buckets, KMS, RDS Aurora
│   │   ├── auth-stack.ts          # Cognito User Pool
│   │   ├── api-stack.ts           # API GW + Lambda fns + WAF
│   │   ├── frontend-stack.ts      # CloudFront + Amplify
│   │   ├── observability-stack.ts # CloudWatch alarms, dashboards
│   │   └── ci-cd-stack.ts         # GitHub OIDC role
│   └── constructs/
│       └── lambda-with-vpc.ts     # reusable
└── test/
```

### 8.10.4. Mobile release pipeline

```
develop branch    → CI: build debug APK, run unit + integration tests
PR to main        → CI: full test, generate review APK, upload Firebase
Tag rc-*          → CI: build release IPA + AAB, upload TestFlight + Play Internal
Tag v*            → Manual: phase rollout 5% → 25% → 100% trên Play Console
```

---

## 8.11. Observability

### 8.11.1. Logging

- **Backend**: structured JSON logs, fields chuẩn (`timestamp`, `level`, `service`, `request_id`, `user_id`, `action`, `latency_ms`, `error`)
- **Mobile**: Sentry breadcrumb + custom events
- **Web**: Sentry + Console với mức INFO+

### 8.11.2. Metrics (CloudWatch + custom)

| Metric | Threshold alarm |
|---|---|
| Lambda error rate | > 1% trong 5 phút |
| Lambda duration p95 | > timeout 80% |
| API GW 5xx | > 1% trong 5 phút |
| Aurora CPU | > 70% trong 10 phút |
| Aurora connections | > 70 (max 100) |
| S3 4xx put | > 0.1% trong 1h |
| Cost daily | > $10/ngày prod (early warning) |

### 8.11.3. Tracing

X-Ray đầu cuối: API GW → Lambda → RDS Proxy → Aurora → S3. Sample rate 10% prod.

### 8.11.4. Dashboards

- **SRE dashboard** (P5): Lambda metrics, Aurora, S3 cost, API latency, top errors
- **Business dashboard** (P4): Submission/day, top categories, validation pass rate
- **Cost dashboard**: AWS Cost Explorer view per service, daily anomaly alert

### 8.11.5. Alerting

PagerDuty integration cho on-call (P5 + tech lead). Severity:
- **SEV-1**: Hệ thống down, < 15 phút response
- **SEV-2**: Degraded, < 1h response
- **SEV-3**: Edge case, business hours

---

## 8.12. Scalability & Performance

Volume hiện tại 3.300 submission/tháng → ~5/giờ peak. Gấp 10× = 33k/tháng → ~50/giờ peak. Cấu hình hiện tại **dư đáp ứng 100×**.

| Component | Limit | Khi nào hit limit |
|---|---|---|
| Lambda concurrency | 1000 default account | > 1000 req đồng thời (chưa tới) |
| Aurora 2 ACU max | ~100 connection | > 100 user concurrent |
| S3 | Unlimited | — |
| CloudFront | Unlimited | — |
| API GW | 10k req/sec default | Volume nhỏ, không relevant |

→ **Không cần optimization sớm**. Khi user base scale > 50 cán bộ, review lại.

---

## 8.13. Disaster Recovery & Business Continuity

| Hạng mục | Strategy | RTO | RPO |
|---|---|---|---|
| Aurora Postgres | Automated backup 7 ngày + manual snapshot trước migration | 4h | 1h |
| S3 submissions | Versioning + Cross-region replication tới `ap-southeast-3` | 1h | 0 (replicated) |
| Rule pack | Signed history vĩnh viễn trên S3 | 0 (CDN cached) | 0 |
| User data Cognito | Auto backup, tự khôi phục trong vùng | 4h | 1h |
| Audit logs | RDS + ship sang S3 + CloudTrail (vĩnh viễn — audit trail cho infra changes + data ops) | — | 0 |

**Runbook DR** (Phase 2 sẽ chi tiết):
1. AZ outage: Aurora multi-AZ tự failover < 60s
2. Region outage: failover sang `ap-southeast-3`, manual DNS change, RTO 2h
3. Data corruption: restore từ snapshot gần nhất, RTO 4h

---

## 8.14. Architecture Decision Records (ADR)

10 ADR quan trọng được lock cho v1. Mỗi ADR theo format `Context → Decision → Consequences → Alternatives considered`.

### ADR-001: Flutter cho mobile

- **Context**: Cần app iOS + Android, timeline 8 tuần, 1 mobile dev
- **Decision**: Flutter 3.x stable, Dart 3.x
- **Consequences (+)**: 1 codebase, plugin sẵn cho ML Kit, Riverpod ecosystem mạnh
- **Consequences (–)**: Cần MethodChannel cho native camera/doc scanner; bundle size lớn hơn native
- **Alternatives considered**: Native (loại — chậm), KMP+Compose Multi (loại — team chưa có expertise), React Native (loại — camera/AI plugin yếu)

### ADR-002: AWS over GCP/Azure

- **Context**: OmiGroup đã có AWS expertise, ISO 27001 audit trên AWS
- **Decision**: AWS region `ap-southeast-1` (Singapore)
- **Consequences (+)**: Tận dụng existing tooling, IAM, billing
- **Consequences (–)**: Latency Singapore→VN ~30ms (acceptable)
- **Alternatives**: GCP (tiếng Việt OCR Vision tốt nhưng không dùng — đã chốt on-device); Azure (không có lợi thế)

### ADR-003: Serverless Lambda over EKS

- **Context**: Volume 3.300/tháng, cost-conscious
- **Decision**: 100% Lambda + managed services
- **Consequences (+)**: Cost gần 0 ở idle, không cần SRE care infra
- **Consequences (–)**: Cold start 1-2s với Java/Python (mitigate: Node.js 22 + provisioned concurrency cho ingest)
- **Alternatives**: EKS (loại — overkill, cost cao), App Runner (loại — chưa mature như Lambda)

### ADR-004: Aurora Serverless Postgres over DynamoDB

- **Context**: Quan hệ phức tạp (submission, image, user, audit, rule), cần JOIN, full-text search trên product name
- **Decision**: Aurora Postgres Serverless v2
- **Consequences (+)**: SQL chuẩn, JSONB cho flexible schema, GIN index cho flags array
- **Consequences (–)**: Phải VPC-attach Lambda (cold start +500ms, mitigate bằng provisioned concurrency)
- **Alternatives**: DynamoDB (loại — query phức tạp khó, GSI giới hạn), RDS instance-based (loại — pay 24/7)

### ADR-005: Cognito over Auth0/Keycloak

- **Context**: Cần auth managed, MFA TOTP, cost rẻ
- **Decision**: AWS Cognito User Pool
- **Consequences (+)**: Free tier 50k MAU, native AWS integration, MFA ready
- **Consequences (–)**: UI customization hơi ràng buộc (mitigate: tự build UI, dùng Cognito chỉ cho API)
- **Alternatives**: Auth0 ($23+/user lần đầu), Keycloak (self-host, ops cost cao)

### ADR-006: flutter_gemma over native MethodChannel + MediaPipe SDK

- **Context**: Cần Gemma 4 inference trên Flutter
- **Decision**: flutter_gemma 0.14.5+ làm primary, native MethodChannel + MediaPipe SDK làm Plan B
- **Consequences (+)**: Time-to-market nhanh, plugin maintained active
- **Consequences (–)**: Single maintainer, có thể abandoned (mitigate: lưu fork nội bộ; smoke test định kỳ)
- **Alternatives**: Native (Plan B nếu plugin fail), MLC LLM (loại — Flutter binding kém)

### ADR-007: JsonLogic + extension over Rego/CEL

- **Context**: Rule pack DSL cần (a) dễ cho non-coder edit qua CMS, (b) chạy được cả mobile và server, (c) verify được
- **Decision**: JsonLogic + extension (`exists`, `regex_match`, `unit_convert`, `nutrition_ratio`, `category_match`)
- **Consequences (+)**: JSON thuần, có lib JS/Dart/Kotlin/Swift, form-based editor dễ
- **Consequences (–)**: Logic phức tạp khó express (mitigate: nếu cần, bổ sung extension)
- **Alternatives**: Rego/OPA (loại — runtime ~5MB nặng cho mobile), CEL (loại — non-coder khó), Lua/JS embed (loại — sandbox security khó)

### ADR-008: Per-image inference over multi-image-1-prompt

- **Context**: Multi-shot 4 ảnh / sản phẩm; muốn budget latency ≤ 8s trên Tier B
- **Decision**: Default phương án per-image (4 inference riêng + Dart-side merge)
- **Consequences (+)**: Latency mỗi inference ~1-2s GPU, Dart-side merge linh hoạt, retry per-image
- **Consequences (–)**: Mất context chéo giữa ảnh (mitigate: pass JSON output ảnh trước vào prompt ảnh sau)
- **Alternatives**: Multi-image trong 1 prompt (option Tier A premium); benchmark sẽ quyết)

### ADR-009: Compact JSON schema (≤ 250 tokens output)

- **Context**: Output 500 tokens = ~10s decode trên Tier A → vượt budget
- **Decision**: Schema compact (key viết tắt, chỉ emit field có data, dùng speculative decode)
- **Consequences (+)**: Latency ~5s/ảnh trên Tier A
- **Consequences (–)**: Schema khó đọc bằng mắt (mitigate: Dart-side normalize ra schema đầy đủ)
- **Alternatives**: Output 500 tokens (loại — vượt budget); streaming token (xét sau Phase 2)

### ADR-010: ML Kit OCR cross-check ground truth

- **Context**: VLM hallucinate là rủi ro lớn nhất với numeric field dinh dưỡng
- **Decision**: Chạy song song ML Kit OCR và VLM cho mỗi ảnh, regex-match numeric field VLM↔OCR; flag hallucinated nếu mismatch
- **Consequences (+)**: Anchor chống hallucinate, free, on-device
- **Consequences (–)**: 2× latency (mitigate: chạy song song, không tuần tự)
- **Alternatives**: Trust VLM (loại — quá rủi ro); chỉ OCR + heuristic (loại — không hiểu cấu trúc bảng)

---

## 8.15. Open architecture questions

| # | Câu hỏi | Cần giải quyết |
|---|---|---|
| Q1 | Có cần data residency tại VN (không lưu ngoài VN) không? | Tuần 1 với Viện DD |
| Q2 | Backup S3 cross-region — sang Jakarta hay sang VN region nếu AWS có sau? | Theo dõi AWS roadmap |
| Q3 | Mobile app có cần MDM (Mobile Device Management) khi cấp cho cán bộ Viện DD không? | Tuần 1 |
| Q4 | Sentry self-host hay SaaS? (PII concern) | Quyết định trong Section 10 |
| Q5 | Có cần tích hợp SSO với hệ thống nội bộ Bộ Y tế không? | Phase 2 |
| Q6 | App có cần "kiosk mode" (lock vào 1 app) khi cán bộ đi field không? | Workshop với Viện DD |
| Q7 | Cần encrypt SQLite local bằng SQLCipher hay native EncryptedFile đủ? | Section 10 quyết |

---

## 8.16. Cross-references

- Modules chi tiết: [05_functional_spec.md](05_functional_spec.md)
- AI strategy: [07_on_device_ai.md](07_on_device_ai.md)
- Khung pháp lý → input rule pack: [04_legal_framework.md](04_legal_framework.md)
- Persona dùng kiến trúc này: [03_personas_usecases.md](03_personas_usecases.md)
- Rule engine chi tiết: 06_rule_engine.md (sẽ viết tiếp)
- Roadmap triển khai: 11_roadmap.md (sẽ viết tiếp)
