1. Bối cảnh: adjudication bồi thường ở quy mô doanh nghiệp
Adjudication là bước một hãng bảo hiểm quyết định một yêu cầu bồi thường (claim) sẽ được DUYỆT, TỪ CHỐI hay CHỜ bổ sung, và nếu duyệt thì trả bao nhiêu. Ở một hãng bảo hiểm sức khoẻ tầm trung, engine này xử lý hàng trăm nghìn claim mỗi ngày, gắn với SLA nội bộ (ví dụ 95% claim tự động trong 24 giờ) và ràng buộc tuân thủ pháp lý. Một quyết định sai không chỉ là bug: nó là tiền trả nhầm, là khiếu nại của khách, là rủi ro pháp lý và có thể là phạt từ cơ quan quản lý. Vì thế kiểm thử engine này phải khẳng định ĐÚNG QUYẾT ĐỊNH NGHIỆP VỤ, không phải chỉ 'trang không lỗi'.
Bài này xây dựng một AI test agent quanh engine đó. Agent KHÔNG phải là oracle: nó không được tự quyết đúng/sai. Oracle là BẢNG QUYẾT ĐỊNH (decision table) do nghiệp vụ và định phí duyệt — một artifact có thể kiểm tra bằng mắt người. Agent chỉ làm việc nó giỏi: đọc bảng quyết định, tự sinh ra tổ hợp ca kiểm thử (đặc biệt là các ca BIÊN mà con người hay bỏ sót), rồi để harness so kết quả thực tế với quyết định kỳ vọng suy ra từ bảng. Con người review các ca rủi ro và guardrail chặn agent tự 'sáng tác' quy tắc nghiệp vụ.
- Quy mô: ~300k claim/ngày, 40+ sản phẩm, ~1.2k quy tắc nghiệp vụ hoạt động.
- SLA: 95% claim đơn giản tự động ≤ 24h; claim phức tạp chuyển thẩm định viên.
- Tuân thủ: lưu vết quyết định (audit trail), giải thích được lý do từ chối.
- Rủi ro: trả nhầm tiền, từ chối sai (bad faith), rò rỉ dữ liệu y tế (PHI).
2. Bảng quyết định là oracle, không phải screenshot
Một bảng quyết định gồm các ĐIỀU KIỆN (condition) ở cột trái và các HÀNH ĐỘNG/kết quả (action) ở cột phải; mỗi hàng là một luật (rule) map một tổ hợp điều kiện thành một quyết định. Với bảo hiểm sức khoẻ, điều kiện điển hình gồm: loại dịch vụ, hợp đồng còn hiệu lực, đã qua thời gian chờ chưa, chẩn đoán có nằm trong danh sách loại trừ không, số tiền so với hạn mức, quyền lợi còn lại trong năm. Bảng này là tài sản của nghiệp vụ; nó viết bằng ngôn ngữ con người đọc được và là điểm quy chiếu duy nhất cho 'đúng'.
Sai lầm kinh điển là assert bằng UI: 'thấy chữ Approved màu xanh'. Nó giòn (đổi màu là hỏng), nó không kiểm được số tiền, và nó không phân biệt được 'duyệt đúng' với 'duyệt nhầm claim lẽ ra phải từ chối'. Oracle đúng phải suy ra quyết định kỳ vọng từ bảng, so cả TRẠNG THÁI (approve/deny/pend), LÝ DO (reason code) và SỐ TIỀN (payable). Ta mã hoá bảng quyết định thành dữ liệu để cả agent lẫn harness cùng đọc.
// oracle/decision-table.ts — bảng quyết định là DỮ LIỆU, con người review được
export type Decision = "APPROVE" | "DENY" | "PEND";
export interface ClaimInput {
serviceType: "OUTPATIENT" | "INPATIENT" | "DENTAL" | "MATERNITY";
policyInForce: boolean;
daysSinceEffective: number; // để so với thời gian chờ
diagnosisCode: string; // ICD-10
billedAmount: number; // số tiền yêu cầu
annualLimit: number;
usedThisYear: number; // đã dùng trong năm
coinsuranceRate: number; // vd 0.2 => khách trả 20%
}
export interface ExpectedDecision {
decision: Decision;
reasonCode: string; // vd DENY_EXCLUSION, PEND_DOC
payable: number; // số tiền hãng phải trả
}
// Thời gian chờ theo loại dịch vụ (ngày). Nghiệp vụ sở hữu con số này.
export const WAITING_PERIOD: Record<ClaimInput["serviceType"], number> = {
OUTPATIENT: 30, INPATIENT: 30, DENTAL: 180, MATERNITY: 270,
};
// Danh sách loại trừ (rút gọn) — chẩn đoán không được chi trả.
export const EXCLUSIONS = new Set(["Z41.1" /*thẩm mỹ*/, "F17.2" /*cai thuốc*/]);3. Bất biến nghiệp vụ dùng làm oracle
Ngoài từng luật riêng lẻ, có những BẤT BIẾN đúng với MỌI claim bất kể tổ hợp điều kiện. Chúng là mạng lưới an toàn: nếu một luật mới vô tình phá vỡ bất biến, test sẽ bắt được ngay cả khi ta chưa nghĩ ra ca cụ thể. Đây chính là chỗ oracle-first phát huy: ta không liệt kê hết mọi input, ta khẳng định tính chất bất biến.
- Xác định (deterministic): cùng input → cùng quyết định (không phụ thuộc giờ chạy).
- payable không bao giờ âm và không vượt (billedAmount, hạn mức còn lại).
- Loại trừ áp đảo: chẩn đoán trong danh sách loại trừ ⇒ luôn DENY, không APPROVE.
- Thời gian chờ: trong thời gian chờ ⇒ không APPROVE cho quyền lợi tương ứng.
- Mọi DENY/PEND đều có reasonCode giải thích được (không có từ chối 'trơn').
- Đồng nhất tiền: payable = min(billed, còn lại) × (1 − coinsurance) khi APPROVE.
// oracle/oracle.ts — suy ra quyết định kỳ vọng THUẦN TỪ bảng, thứ tự luật rõ ràng
import { ClaimInput, ExpectedDecision, WAITING_PERIOD, EXCLUSIONS } from "./decision-table";
export function expectedDecision(c: ClaimInput): ExpectedDecision {
// Thứ tự luật là một phần của oracle — loại trừ áp đảo, rồi hiệu lực, rồi chờ...
if (!c.policyInForce) return { decision: "DENY", reasonCode: "DENY_POLICY_INACTIVE", payable: 0 };
if (EXCLUSIONS.has(c.diagnosisCode)) return { decision: "DENY", reasonCode: "DENY_EXCLUSION", payable: 0 };
if (c.daysSinceEffective < WAITING_PERIOD[c.serviceType])
return { decision: "DENY", reasonCode: "DENY_WAITING", payable: 0 };
const remaining = Math.max(0, c.annualLimit - c.usedThisYear);
if (remaining === 0) return { decision: "DENY", reasonCode: "DENY_LIMIT_EXHAUSTED", payable: 0 };
if (c.billedAmount > remaining) return { decision: "PEND", reasonCode: "PEND_OVER_LIMIT", payable: 0 };
const covered = Math.min(c.billedAmount, remaining);
const payable = Math.round(covered * (1 - c.coinsuranceRate) * 100) / 100;
return { decision: "APPROVE", reasonCode: "APPROVE_OK", payable };
}
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!