ADR-001 — Webapp cá nhân: tài chính và sức khỏe


1. Kết luận


2. Bài toán và giả định

Mục Nội dung Nhãn
AS-IS Tài chính: Base "Lịch sử giao dịch" có Bank Sync, phân loại tay. Sức khỏe: chưa có hệ thống, dùng app Sức khỏe iPhone và Strava khi đi bộ. Không dùng đồng hồ đo sức khỏe. user xác nhận
TO-BE Một app trên iPhone để xem tài chính, ghi khoản chi rồi chuyển khoản nhanh, ghi sức khỏe, bệnh án, dinh dưỡng, mục tiêu, nhận gợi ý theo thời tiết, nhập bằng giọng nói. user xác nhận
Actor Một người dùng (Nguyên). user xác nhận
Thiết bị iPhone, luôn có 4G. Offline là "có thì tốt". user xác nhận
AI OpenRouter (Nguyên đã có key). user xác nhận
Phạm vi tài chính Toàn bộ Base là chi tiêu cá nhân. user xác nhận
Bệnh án Gửi ảnh cho AI qua OpenRouter được. Trích xuất có cấu trúc. user xác nhận
Calo từ ảnh món ăn Ước tính là chấp nhận được. user xác nhận
Mục tiêu Đặt theo từng giai đoạn, cảnh báo dựa trên mục tiêu đang hiệu lực. user xác nhận
Thứ tự giai đoạn Nguyên để tư vấn chọn. user xác nhận
Bảng "Chi tiết chia tiền" Giả định: không dùng ở bản đầu. giả định
Vị trí cho thời tiết Giả định: vị trí cố định (thành phố) ở bản đầu, GPS là tùy chọn sau. giả định

Không làm (ngoài phạm vi): - Webapp không chuyển tiền, không tự xác nhận giao dịch. Chỉ mở app MB với thông tin điền sẵn. - Webapp không ghi vào Base tài chính ở mọi giai đoạn trong ADR này. - Không chẩn đoán, không chỉnh thuốc, không thay tư vấn y tế hoặc dinh dưỡng chuyên môn. - Không đồng bộ tự động với Apple Health ở bản đầu. - Không đa người dùng.


3. Hard gates

Gate Kết quả Bằng chứng / việc cần kiểm chứng
Access pass Một người dùng, không public, không anonymous.
Security pass có điều kiện Dữ liệu y tế, tài chính, số tài khoản. Bắt buộc: đăng nhập phía server (passkey hoặc OTP), secret ở backend, che số tài khoản, nhật ký truy cập. Phải qua review bảo mật trước khi dùng dữ liệu thật (mục 9).
Integrity pass Một người ghi. Webapp không ghi Base. Bản ghi nhập từ máy có ID sinh ở client để gửi lại không trùng.
Runtime pass Cron nhẹ (Workers Cron) cho cache Base, Strava, thời tiết, sao lưu. Không cần worker chạy liên tục.
Device unknown Camera trong PWA iOS (quét QR, chụp ảnh), deeplink MB trên iOS, micro, offline. POC trên iPhone thật.
Scale pass Dữ liệu nhỏ. Quota API Base cho dashboard cần đo (unknown).
Platform unknown Quota và giá: Base Open API, OpenRouter (từng model), Open-Meteo, Strava API, dl.vietqr.io, Cloudflare D1/R2. Chính sách giữ dữ liệu của model AI.
Operability pass có điều kiện Bắt buộc có sao lưu tự động và đã thử khôi phục một lần trước khi nhập dữ liệu thật.

4. Kiến trúc theo lớp

Lớp Lựa chọn Ghi chú
Giao diện PWA (mobile-first), cài lên màn hình chính iPhone Một màn hình xác nhận chung cho mọi bản ghi nháp.
Backend Cloudflare Workers Giữ mọi secret: token Base, key OpenRouter, token Strava.
DB master (phần mới) Cloudflare D1 Sức khỏe, dinh dưỡng, mục tiêu, khoản chi dự kiến, người nhận, cache.
File Cloudflare R2, private Ảnh bệnh án, ảnh nhãn thành phần, ảnh món ăn. Truy cập qua link ký có hạn.
Tài chính master Lark Base hiện có Chỉ đọc qua Open API, token phía server.
AI OpenRouter, gọi từ Workers Hạn mức chi tiêu tháng. Mọi kết quả AI là nháp chờ xác nhận.
Tích hợp Strava API (OAuth), Open-Meteo, dl.vietqr.io deeplink Mỗi tích hợp có fallback khi hỏng.
Chạy nền Workers Cron Làm mới cache Base, kéo Strava, thời tiết buổi sáng, sao lưu.
Sao lưu Xuất D1 và metadata R2 định kỳ ra nơi Nguyên kiểm soát Thử khôi phục định kỳ.

Nguyên tắc chung cho AI: 1. AI chỉ: đọc ảnh và chữ thành bản nháp, nhận diện món ăn, diễn giải số liệu, viết gợi ý. 2. Code tính: tổng tiền, tổng calo, calo mục tiêu, ngưỡng cảnh báo, đối chiếu giao dịch. 3. Nguyên xác nhận: mọi số tiền, mọi chỉ số y tế, mọi thuốc và liều, mọi bữa ăn trước khi tính vào tổng. 4. Không gửi cho AI: số tài khoản, token, thông tin định danh trên bệnh án khi che được.


5. Hợp đồng dữ liệu

Entity Nguồn thật Ai / đâu được ghi Bản sao Sync / độ trễ Conflict / reconcile
Giao dịch tài chính Base "Lịch sử giao dịch" Bank Sync và Nguyên sửa trong Base Cache chỉ đọc trên D1 Cron vài phút và khi mở app Không sửa cache. Cache lệch thì làm mới từ Base.
Số dư tài khoản Dòng Bank Sync mới nhất theo từng tài khoản trong Base Bank Sync Cache D1 Như trên Không tự cộng dồn.
Phân loại giao dịch Base (Phân loại, Nhóm quản lý) Nguyên trong Base — — AI chỉ gợi ý vào D1, không ghi Base.
Khoản chi dự kiến D1 Webapp — — Đối chiếu với giao dịch Bank Sync (mục 6.2).
Người nhận đã lưu D1 Webapp — — Che số tài khoản khi hiển thị.
Chỉ số tự đo (cân nặng, ngủ, huyết áp...) D1 Webapp — Hàng đợi offline gửi sau ID client chống trùng.
Lần khám, đơn thuốc, xét nghiệm D1 + ảnh gốc R2 Webapp, sau khi Nguyên xác nhận — — Ảnh gốc là bằng chứng. Sửa thì giữ lịch sử.
Bữa ăn D1 Webapp, sau khi Nguyên xác nhận — — Ước tính gắn nhãn.
Bảng thực phẩm của Nguyên D1 Webapp, món đã xác nhận — — Ghi nguồn từng dòng (nhãn, nguồn tham khảo, ước tính AI đã chỉnh).
Mục tiêu D1 Nguyên — — Một mục tiêu hiệu lực mỗi loại tại một thời điểm.
Hoạt động đi bộ Strava Strava Bản chiếu D1 Cron hoặc khi mở app Upsert theo activity ID của Strava.
Apple Health App Sức khỏe iPhone iPhone Tùy chọn: nhập file thủ công Không tự động ở bản đầu Upsert theo thời điểm và loại chỉ số.
Thời tiết Open-Meteo — Cache D1 vài giờ Cron sáng Không cần.
Kết quả AI (nháp, gợi ý) D1 Webapp — — Lưu model, thời điểm, nguồn đầu vào. Nháp không tính vào tổng.

Quy ước tính báo cáo tài chính (đề xuất, Nguyên sửa nếu khác)

Nhóm quản lý Cách tính
Ăn uống, Sinh hoạt, Cá nhân, Công việc, Khác Chi tiêu
Thu nhập Thu
Chuyển khoản nội bộ Loại khỏi thu và chi
Tiết kiệm, Đầu tư Hiển thị riêng
Nợ/Vay Hiển thị riêng

6. Thiết kế từng module

6.1 Dashboard tài chính (chỉ đọc)

6.2 Ghi khoản chi, quét QR, mở MB

Luồng: 1. Quét QR của người nhận bằng camera trong webapp. Fallback: chọn ảnh QR từ thư viện, hoặc chọn người nhận đã lưu, hoặc nhập tay. 2. Code đọc QR theo chuẩn VietQR/EMV: mã ngân hàng, số tài khoản, số tiền và nội dung nếu có. QR không đúng chuẩn thì báo "không đọc được", không đoán. 3. Nguyên nhập hoặc sửa số tiền, nội dung, ghi chú, nhóm. Nội dung được bỏ dấu và cắt theo giới hạn, hiển thị bản cuối. 4. Bấm "Chuyển bằng MB": mở https://dl.vietqr.io/pay?app=mb&ba={stk}@{ma_ngan_hang}&am={so_tien}&tn={noi_dung}&url={url_tro_ve}. 5. Nguyên xem tên người nhận trong MB rồi tự bấm xác nhận. 6. Đối chiếu: khi giao dịch Bank Sync mới về Base có cùng số tiền, thời gian gần, nội dung tương tự thì đánh dấu đã khớp.

Trạng thái khoản chi: Dự kiến → Đã mở MB → Đã khớp giao dịch | Không khớp | Hủy. Đã mở MB không có nghĩa là đã chuyển.

Fallback khi deeplink hỏng hoặc không tự điền trên iOS: tạo VietQR ngay trong webapp (không dùng dịch vụ tạo ảnh bên ngoài), lưu ảnh, quét từ ảnh trong app MB. Fallback cuối: nút sao chép số tiền và nội dung.

Bằng chứng: changelog VietQR bản 2.18 (2026-07-31) ghi MB hỗ trợ autofill qua deeplink; tài liệu deeplink tại vietqr.io (documented, chưa thử trên iPhone của Nguyên).

6.3 Sức khỏe tự đo

6.4 Bệnh án

Luồng: chụp ảnh → lưu R2 private → che định danh khi làm được → AI trích xuất nháp → Nguyên xem ảnh cạnh các ô, sửa, xác nhận → lưu D1.

Bảng: - Lần khám: ngày, nơi khám, bác sĩ, lý do, chẩn đoán, dặn dò, ngày tái khám, ảnh gốc. - Đơn thuốc: tên thuốc, hoạt chất, liều, tần suất, thời gian dùng, ghi chú. - Xét nghiệm: tên chỉ số, giá trị, đơn vị, khoảng tham chiếu, ngày.

Quy tắc: - AI chỉ chép lại nội dung trong ảnh, không suy diễn, không gợi ý liều. - Đánh dấu các ô AI không chắc. Tên thuốc, liều, đơn vị, dấu thập phân phải soát kỹ. - Chỉ số chưa xác nhận không hiện trên biểu đồ. - Có chức năng xóa hẳn ảnh và bản ghi.

6.5 Dinh dưỡng

Đầu vào Cách xử lý Độ tin cậy
Ảnh nhãn thành phần AI đọc số trên nhãn (calo, đạm, béo, đường, natri, khẩu phần) → Nguyên xác nhận Cao
Mã vạch (tùy chọn) Tra Open Food Facts Trung bình, sản phẩm Việt có thể thiếu
Ảnh món không nhãn AI nhận món và gợi ý khẩu phần → tra bảng thực phẩm → hiện khoảng calo → Nguyên chỉnh khẩu phần → code tính lại Thấp, gắn nhãn "ước tính"
Chọn món đã lưu Lấy từ bảng thực phẩm của Nguyên Theo dữ liệu đã xác nhận

Bảng thực phẩm của Nguyên lớn dần: mỗi món đã xác nhận được lưu kèm nguồn. Nguồn tham khảo ứng viên: Bảng thành phần thực phẩm Việt Nam (Viện Dinh dưỡng Quốc gia), USDA FoodData Central, Open Food Facts. Điều kiện sử dụng từng nguồn: unknown, kiểm tra khi làm.

6.6 Mục tiêu theo giai đoạn

6.7 Thời tiết và gợi ý

6.8 Strava và Apple Health

6.9 Nhập bằng giọng nói


7. Bảo mật và quyền riêng tư

Hạng mục Yêu cầu
Đăng nhập Passkey hoặc OTP, session phía server, hết hạn hợp lý.
Secret Token Base, key OpenRouter, token Strava chỉ ở Workers secret. Không có trong frontend, repo, log.
Token Base Quyền tối thiểu: chỉ đọc bảng cần dùng.
File R2 private, link ký có hạn.
Số tài khoản Che trên giao diện, không gửi AI, không ghi log.
AI Hạn mức chi tiêu tháng trên OpenRouter. Chọn model không lưu và không huấn luyện trên dữ liệu. Thu nhỏ ảnh trước khi gửi. Che định danh trên bệnh án khi làm được.
Nhật ký Ghi đăng nhập và truy cập dữ liệu nhạy cảm.
Xóa Xóa hẳn bản ghi và ảnh, kể cả lần sao lưu sau.
Review Chạy webapp-security-review trước khi nhập dữ liệu thật.

Rủi ro đã chấp nhận (Nguyên duyệt 2026-11-12)

  1. Ảnh bệnh án gửi cho OpenRouter và nhà cung cấp model. Đã chấp nhận (user xác nhận), với điều kiện che định danh khi làm được và chọn model có chính sách không giữ dữ liệu.
  2. Số tài khoản người nhận, số tiền và nội dung đi qua dl.vietqr.io (dịch vụ bên thứ ba) khi dùng deeplink. Chính sách dữ liệu chưa xem (unknown). Đã chấp nhận (user xác nhận, qua việc duyệt ADR). Nên đọc chính sách của họ trước giai đoạn 3.
  3. Calo từ ảnh món ăn là ước tính, có thể lệch lớn. Đã chấp nhận (user xác nhận).
  4. Phụ thuộc dịch vụ ngoài (dl.vietqr.io, Open-Meteo, Strava, OpenRouter) có thể đổi hoặc ngừng. Mỗi tích hợp có fallback.

8. POC (làm trước khi xây module tương ứng)

Mọi POC dùng dữ liệu giả hoặc số tiền nhỏ trên tài khoản của chính Nguyên. Ngưỡng pass do Nguyên chốt.

# Giai đoạn Bất định Phép thử Pass gợi ý Fallback nếu fail
P1 1 Quota và độ trễ API Base Đọc toàn bộ bảng giao dịch, tải dashboard 10 lần Tải xong trong vài giây, không bị giới hạn Cache D1 lâu hơn, chỉ kéo phần thay đổi
P2 1 Sao lưu và khôi phục Xuất D1, xóa DB thử, khôi phục Khôi phục đủ dữ liệu Không nhập dữ liệu thật cho tới khi đạt
P3 1 Giọng nói tầng 1 Nói 10 câu mẫu (số tiền, số thập phân, món Việt, tên thuốc), cho AI tách Sau khi sửa, không còn sai số tiền hay đơn vị Bắt buộc nhập số bằng bàn phím
P4 2 Đọc nhãn thành phần 10 ảnh nhãn thật Số liệu đúng sau khi soát Nhập tay từ nhãn
P5 2 Ước tính món ăn 10 món quen (bún bò, bánh mì, cơm tấm...) Khoảng calo hợp lý so với nguồn tham khảo Chỉ dùng bảng thực phẩm, chọn món bằng tay
P6 3 Quét QR trong PWA iOS Quét 5 QR chuyển khoản thật bằng camera webapp Đọc đúng ngân hàng và số tài khoản Chọn ảnh từ thư viện
P7 3 Deeplink MB trên iOS Mở deeplink app=mb với số tiền nhỏ, nội dung có dấu và dài MB mở đúng màn hình, điền đủ ba trường QR tự tạo, quét từ ảnh trong MB; sao chép
P8 3 Đối chiếu giao dịch Chuyển 3 khoản nhỏ, theo dõi Bank Sync Giao dịch về Base trong thời gian chấp nhận được, nội dung đủ để khớp Khớp theo số tiền và thời gian, Nguyên xác nhận tay
P9 4 Đọc bệnh án 5 bệnh án hoặc đơn thuốc mẫu đã xóa định danh, cả chữ in và chữ tay Tên thuốc, liều, ngày đúng sau khi soát Chỉ lưu ảnh và ghi chú tay, AI chỉ gợi ý
P10 4 Che định danh Kiểm tra ảnh đã che trước khi gửi Không còn họ tên, mã bệnh nhân rõ ràng Nguyên tự cắt ảnh trước khi gửi
P11 5 Strava OAuth và kéo 30 ngày hoạt động Đủ hoạt động, không trùng Nhập tay
P12 5 Ghi âm và offline trong PWA iOS Ghi âm, mất mạng rồi có mạng lại Không mất bản ghi, không trùng Chỉ dùng tầng 1, bỏ offline

9. Lộ trình

Giai đoạn Nội dung Điều kiện bắt đầu Nghiệm thu
0 Duyệt ADR, chốt rủi ro mục 7, chọn tên miền, tạo tài khoản dịch vụ Nguyên duyệt ADR chuyển sang Accepted
1 Đăng nhập, dashboard tài chính đọc Base, nhập nhanh sức khỏe, mục tiêu, lớp nhập liệu chung và giọng nói tầng 1, sao lưu P1, P2 đạt Xem được tài chính tháng này đúng với Base; ghi được cân nặng bằng giọng nói; khôi phục sao lưu thành công; review bảo mật không có lỗi nghiêm trọng
2 Dinh dưỡng: nhãn thành phần, bảng thực phẩm, ảnh món ăn, calo mục tiêu và cảnh báo P4, P5 đạt Ghi một ngày ăn đầy đủ, tổng calo khớp tính tay
3 Khoản chi, quét QR, deeplink MB, đối chiếu giao dịch P6, P7, P8 đạt hoặc chọn fallback Chuyển một khoản nhỏ từ quét QR đến khớp giao dịch
4 Bệnh án có cấu trúc P9, P10 đạt Nhập một lần khám đầy đủ, xóa được hẳn
5 Strava, thời tiết, gợi ý AI tổng hợp, giọng nói tầng 2, offline, Apple Health (tùy chọn) P11, P12 Theo từng tính năng

Effort chưa ước lượng thành giờ. Sẽ ước lượng theo từng giai đoạn sau khi POC tương ứng có kết quả.

Chi phí cần kiểm tra (chưa có số)

Cloudflare (Workers, D1, R2 theo dung lượng ảnh), OpenRouter (theo model, số ảnh mỗi ngày), tên miền, các API ngoài (Open-Meteo, Strava, dl.vietqr.io theo điều kiện miễn phí). Không có con số nào trong ADR này là báo giá.


10. Khi nào đổi hướng

Nếu Thì
Dashboard chậm hoặc chạm giới hạn API Base Cache dài hơn và kéo phần thay đổi, vẫn giữ Base là master
Muốn ghi giao dịch từ webapp vào Base Viết ADR riêng cho luồng ghi: chống trùng với Bank Sync, field nào webapp sở hữu
Deeplink MB không tự điền trên iOS hoặc dl.vietqr.io ngừng Chuyển sang QR tự tạo hoặc sao chép
Đọc bệnh án chữ tay quá kém Chỉ lưu ảnh và ghi chú tay
Chi phí AI vượt hạn mức Dùng model rẻ hơn cho tác vụ đơn giản, thu nhỏ ảnh, ưu tiên bảng thực phẩm đã lưu
Muốn cho người khác dùng ADR mới cho đa người dùng và phân quyền

11. Quyết định duyệt

Nguyên duyệt toàn bộ ADR ngày 2026-11-12 (user xác nhận). Các mục dưới đây được duyệt đúng như đề xuất:

  1. ✅ Chấp nhận rủi ro dl.vietqr.io (mục 7, rủi ro 2).
  2. ✅ Quy ước tính báo cáo tài chính (mục 5).
  3. ✅ Không dùng bảng "Chi tiết chia tiền" ở bản đầu.
  4. ✅ Thời tiết theo vị trí cố định.
  5. ✅ Thứ tự 5 giai đoạn (mục 9).
  6. ✅ Ngưỡng pass của POC lấy theo cột "Pass gợi ý" (mục 8). Các ngưỡng còn định tính ("vài giây", "thời gian chấp nhận được") sẽ chốt thành con số khi chạy POC đó.

Thay đổi về sau ghi thành ADR mới hoặc thêm mục "Sửa đổi" có ngày, không sửa lặng lẽ nội dung đã duyệt.

Quyết định giai đoạn 0 (2026-11-12, user xác nhận)

Hạng mục Quyết định
Tên miền Dùng tạm tên miền *.pages.dev / *.workers.dev của Cloudflare. Tên miền riêng để sau.
Tài khoản Cloudflare Dùng tài khoản đang đăng nhập trong wrangler trên máy của Nguyên.
OpenRouter Nguyên tự đặt hạn mức chi tiêu tháng trên OpenRouter. Key không gửi qua chat; Nguyên tự nạp vào Workers secret khi đến bước cần.

12. Nguồn

Nguồn Phạm vi đã đọc Nhãn
Lark Base (đã ẩn), bảng "Lịch sử giao dịch" Chỉ schema 20 field, đọc ngày 2026-11-12 bằng lark-cli base +url-resolve (user identity). Không đọc dòng dữ liệu. Không đọc các bảng khác. runtime đã xác minh (schema)
vietqr.io: trang deeplink và changelog 2.18 Cú pháp deeplink, danh sách app autofill có MB, giới hạn 5 lần mỗi phút mỗi IP documented
Trao đổi với Nguyên trong phiên ngày 2026-11-12 Toàn bộ yêu cầu và quyết định ở mục 2 user xác nhận
Open-Meteo, Strava API, Open Food Facts, USDA FoodData Central, Bảng thành phần thực phẩm Việt Nam Chưa đọc trong phiên này unknown