Bài 5: Đánh giá kinh tế với mô hình lây truyền động

Từ đầu ra của mô hình đến chi phí, DALY và ICER; so sánh mô hình tĩnh và động cho cùng một vắc xin

Vì sao bạn nên quan tâm?

Giả sử một hội đồng tư vấn của Bộ Y tế được hỏi: có nên tiêm Qdenga miễn phí cho mọi trẻ 9 tuổi? Mỗi năm có khoảng 1,351,000 trẻ bước sang tuổi này (ước tính thô từ dân số và tuổi thọ (Cục Thống kê (Bộ Tài chính) 2026b; World Health Organization 2026)); tiêm 80% với giá VNVC cộng khám sàng lọc tốn khoảng 2,898 tỷ VNĐ mỗi năm (Hệ thống tiêm chủng VNVC 2026; Bộ Y tế 2024b). Mốc mà các phân tích ở Việt Nam hay dùng là 125.5 triệu VNĐ cho mỗi năm sống điều chỉnh theo bệnh tật (DALY) tránh được, bằng một lần GDP bình quân đầu người (Cục Thống kê (Bộ Tài chính) 2026a; Ong và c.s. 2026). Bài này cho bạn biến đầu ra của mô hình động thành chi phí, DALY và ICER, tìm mức giá vắc xin đáng tiền, và đo xem mô hình tĩnh làm lệch câu trả lời bao nhiêu.

Mục tiêu học tập

Sau bài này, bạn có thể:

  1. Lấy dòng sự kiện (ca nhiễm, ca bệnh, nhập viện, tử vong, lượt tiêm) từ đầu ra của mô hình ODE bằng các ngăn đếm cộng dồn.
  2. Gắn chi phí (giá dịch vụ BHYT và giá vắc xin hiện hành, VNĐ) và năm sống điều chỉnh theo bệnh tật (DALY) cho từng sự kiện, chiết khấu (discounting) theo thời gian liên tục.
  3. Phân biệt thời gian chạy chương trình với khung thời gian phân tích (time horizon), rồi chọn và báo cáo khung phù hợp.
  4. Chạy phiên bản tĩnh (static model) của cùng một mô hình và đo độ lệch của nó so với mô hình động.
  5. Xác minh (verification) một phân tích chi phí-hiệu quả bằng các kiểm tra chạy lại được, trong đó mỗi kiểm tra có thể thất bại.

Ý chính

  • Mô hình động mô phỏng cả quần thể theo lịch, kể cả những đoàn hệ chưa sinh. Kết quả là của chương trình trong một khung thời gian, không phải của một đoàn hệ.
  • Chi phí và DALY gắn với dòng (ca mới, lượt tiêm), lấy từ hiệu của các ngăn đếm cộng dồn và chiết khấu theo ngày với \((1+r)^{-t/365}\). Số năm sống mất đi (years of life lost, YLL) của ca tử vong cũng được chiết khấu.
  • Phiên bản tĩnh công bằng nhất là cùng mô hình với lực lây nhiễm (force of infection) “đóng băng” theo quỹ đạo khi không tiêm. Khác biệt duy nhất còn lại là hiệu quả gián tiếp.
  • Nếu vắc xin giảm lây và chương trình chạy liên tục, mô hình tĩnh đánh giá thấp ca và DALY tránh được, nên ICER cao hơn. Nếu vắc xin chỉ giảm bệnh, hiệu quả gián tiếp xấp xỉ 0 và chênh lệch này biến mất.
  • Chương trình chạy bao lâu và theo dõi bao lâu là hai lựa chọn khác nhau. Khi bảo vệ suy giảm, tiêm chủng một phần chỉ hoãn nhiễm; cắt khung lúc chương trình còn chạy tính ca bị hoãn là “tránh được”. Hãy báo cáo nhiều khung.
  • Chọn mức bao phủ bằng ICER gia tăng giữa các chiến lược, không chỉ ICER trung bình so với không tiêm.
  • Với giá thật (vắc xin theo giá VNVC, điều trị theo giá BHYT), câu trả lời hữu ích nhất cho người ra quyết định thường là mức giá vắc xin tối đa để chương trình đạt chi phí-hiệu quả ở ngưỡng đã chọn.

Nội dung

Kết quả của chương trình, không của một đoàn hệ

Ở M02, ta theo một đoàn hệ từ lúc điều trị đến khi tử vong. Mô hình động mô phỏng cả tỉnh theo lịch: mỗi năm có một lứa trẻ mới đến tuổi tiêm, và lợi ích của một lứa tràn sang các lứa khác qua lây truyền. Đơn vị phân tích vì vậy là chương trình trong một khung thời gian (Pitman và c.s. 2012; Jit và Brisson 2011).

Từ trạng thái đến sự kiện

Mỗi ngày \(d\), số ca nhiễm mới là \(C(d) - C(d-1)\), với \(C\) là ngăn đếm cộng dồn có đạo hàm bằng tốc độ nhiễm mới. Từ đó:

\[ \begin{aligned} \text{ca có triệu chứng} &= p_s \times \text{ca nhiễm}, &\quad \text{ca đến cơ sở y tế} &= p_a \times \text{ca có triệu chứng},\\ \text{nhập viện} &= p_h \times \text{ca đến cơ sở y tế}, &\quad \text{tử vong} &= f \times \text{ca đến cơ sở y tế}. \end{aligned} \]

Ở đây \(p_s\) là tỷ lệ có triệu chứng (ước tính toàn cầu (Bhatt và c.s. 2013)), \(p_a\) = 66% là tỷ lệ ca có triệu chứng đến cơ sở y tế (Shepard và c.s. 2016), \(p_h\) = 74% là tỷ lệ nhập viện trong số đó (Bộ Y tế, nửa đầu năm 2022 (VietTimes 2022)), và \(f\) là số tử vong trên mỗi ca được báo cáo của Việt Nam, gộp ba năm 2023–2025: 110 ca tử vong trên 498,347 ca, tức 22.1 trên 100,000 ca (VOV 2024; Bộ Y tế 2024a; Báo Pháp luật Việt Nam 2026; Herriman 2026). Mô hình coi ca đến cơ sở y tế là ca “được báo cáo”, nên \(f\) áp cho nhóm này chứ không cho mọi ca có triệu chứng. Ngăn đếm \(CV\) cho số trẻ được tiêm mỗi ngày.

Chi phí tính theo quan điểm hệ thống y tế (health-system perspective), từ hai bảng giá có thật. Vắc xin: giá bán lẻ một liều Qdenga tại VNVC (1,290,000 VNĐ, giá năm 2026) (Hệ thống tiêm chủng VNVC 2026), cộng một lượt khám sàng lọc trước mỗi liều, tính bằng giá khám BHYT (50,600 VNĐ). Điều trị: tính từ dưới lên bằng giá dịch vụ BHYT của bệnh viện hạng đặc biệt (Bộ Y tế 2024b). Một ca nhập viện nằm 5.4 ngày (Recker và c.s. 2024), mỗi ngày một ngày giường khoa Truyền nhiễm và một xét nghiệm công thức máu, cộng một test NS1 và một lượt khám lúc vào viện. Một ca ngoại trú tái khám và xét nghiệm hằng ngày trong giai đoạn nguy hiểm, khoảng 5 lượt (Sở Y tế Hải Phòng 2024). Đây là giá dịch vụ (charge) quỹ BHYT và người bệnh trả, không phải chi phí nguồn lực, và nghiêng về cận trên vì bệnh viện hạng thấp hơn có giá thấp hơn. Để đối chiếu, ta quy chi phí trực tiếp từ các nghiên cứu ở Việt Nam (trung vị do người soạn tính từ Bảng 1 của Hung và cộng sự, USD năm 2016 (Hung và c.s. 2018)) về VNĐ năm 2025 bằng tỷ giá và CPI dùng chung của khóa học (World Bank 2026b, 2026a):

\[ c_{\text{VNĐ 2025}} = c_{\text{USD 2016}} \times \text{tỷ giá}_{2016} \times \frac{\text{CPI}_{2025}}{\text{CPI}_{2016}}. \]

DALY từ đầu ra của mô hình

Số năm sống với bệnh tật (years lived with disability, YLD) của mỗi ca theo cách tính GBD 2013: phần lớn ca là đợt cấp trung bình, một phần nhỏ là đợt cấp nặng, và một tỷ lệ bị mệt mỏi kéo dài (Stanaway và c.s. 2016; Salomon và c.s. 2015; Hung và c.s. 2018). YLL của một ca tử vong là số năm sống còn lại \(L\), đọc từ bảng sống WHO 2023 của Việt Nam (World Health Organization 2026) ở hai tuổi đại diện: 75% ca tử vong là người lớn (Báo Sài Gòn Giải Phóng 2022), lấy tuổi 38.7 của người lớn nhập viện vì SXHD (Recker và c.s. 2024), còn lại là trẻ em ở tuổi 8 (minh họa). Kết quả là \(L\) = 45.8 năm. Con số 75% mâu thuẫn với phát biểu của Cục Quản lý Khám, chữa bệnh cùng năm 2022, rằng đa số ca tử vong là trẻ từ 15 tuổi trở xuống (VietTimes 2022). Nếu đảo tỷ lệ, chỉ 25% là người lớn, thì \(L\) dài hơn: 60.4 năm. Ta giữ nguồn thứ nhất và coi đây là tham số đáng thử trong phân tích độ nhạy. GBD dùng bảng sống tham chiếu chung cho mọi nước (M01); ở đây ta theo bảng sống của chính Việt Nam, nên DALY của bài không so thẳng được với DALY của GBD. YLL được chiết khấu với \(\rho = \ln(1 + r)\):

\[ \text{YLL} = \int_0^L e^{-\rho s}\, ds = \frac{1 - e^{-\rho L}}{\rho}. \]

Tỷ lệ chiết khấu 3% mỗi năm cho cả chi phí và DALY là giá trị dùng chung của khóa học, như nhiều phân tích ở Việt Nam (Ong và c.s. 2026); hướng dẫn quốc tế bàn thêm về lựa chọn này (World Health Organization 2019; Wilkinson và c.s. 2016).

Mô hình tĩnh và thời gian theo dõi

Cách so sánh tĩnh và động sạch nhất là dùng chính mô hình động, nhưng thay lực lây nhiễm lên người bằng quỹ đạo \(\lambda_h(t)\) của lần chạy không tiêm. Vắc xin vẫn bảo vệ người được tiêm và bảo vệ vẫn suy giảm; chỉ vòng phản hồi lây truyền bị cắt (tham số foi_fun của dengue_ode()). Mọi khác biệt giữa hai kết quả vì thế là hiệu quả gián tiếp (Brisson và Edmunds 2003).

Thời gian chạy chương trình và khung thời gian là hai lựa chọn khác nhau. Vì bảo vệ suy giảm, một trẻ tiêm vào năm cuối của chương trình vẫn có thể bị nhiễm, nhưng chỉ sau khi khung đã kết thúc. Nếu khung dừng khi chương trình còn chạy, ca bị hoãn rơi ra ngoài khung vẫn được tính là “tránh được”. Ta báo cáo cả hai cách: chương trình vẫn chạy khi hết khung (cắt cụt), và chương trình dừng sau 20 năm rồi theo dõi đến 50 năm; tỷ số chi phí-hiệu quả của chương trình tiêm có thời hạn thật sự thay đổi theo thời gian (Edmunds, Medley, và Nokes 1999).

Thực hành

Phần này chạy code/l05-dynamic-cea.R (khoảng nửa phút). Đoạn đầu chạy 150 năm không tiêm để đạt trạng thái lưu hành, rồi chạy năm mô hình trong 50 năm với bao phủ 80% ở tuổi 9: không tiêm; động và tĩnh với chương trình chạy liên tục; động và tĩnh với chương trình dừng sau 20 năm. Kiểm tra: số ngày mô phỏng bằng 50 × 365.

if (!isTRUE(l10n_info()[["UTF-8"]])) invisible(Sys.setlocale("LC_CTYPE", "en_US.UTF-8"))
source("../_shared/R/helpers.R")
source("../_shared/R/vn_data.R")
source("code/model.R")
library(ggplot2)
p <- m05_params()                             # params/dengue.csv + shared values from kb/_shared
ptab <- attr(p, "table")
h_low <- ptab$low[ptab$name == "horizon_years"]
h_high <- ptab$high[ptab$name == "horizon_years"]
H <- p$horizon_years                          # base horizon = years the programme runs (base case)
h_mid <- p$horizon_mid_years
cov_low <- ptab$low[ptab$name == "coverage"]
inp <- dengue_inputs(p)                       # 80% coverage at age 9, waning protection
inp0 <- dengue_inputs(p, coverage = 0)
y0 <- dengue_burnin(inp, p$burnin_years)
base <- dengue_run(inp0, y0, h_high)          # no vaccination
dyn <- dengue_run(inp, y0, h_high)            # dynamic model: FOI responds to vaccination
sta <- dengue_run_static(inp, y0, h_high, base)  # static model: FOI frozen at `base`
# Same programmes, but vaccination STOPS after H years and people are followed until h_high
dyn_stop <- dengue_run(inp, y0, h_high, programme_years = H)
sta_stop <- dengue_run_static(inp, y0, h_high, base, programme_years = H)
c(days_simulated = nrow(dyn) - 1)
days_simulated 
         18250 

Đoạn sau tính chi phí đơn vị và tổn thất sức khỏe của mỗi sự kiện, rồi đặt chi phí điều trị cạnh con số quy đổi từ các nghiên cứu chi phí ở Việt Nam. Kiểm tra: YLL có chiết khấu nhỏ hơn số năm sống còn lại, và cột ratio gần 1.

# Unit costs (VND) from the shared BHYT price list and the VNVC vaccine price, and health
# losses per event. Cross-check: the same costs from Vietnamese cost studies (USD 2016 -> VND 2025).
uc <- treatment_costs(p)
c_hosp_vnd <- uc[["hosp"]]
c_amb_vnd <- uc[["amb"]]
cost_per_vaccinee <- p$doses * (p$c_dose + p$c_delivery)
unit_tab <- c(c_hosp_vnd = c_hosp_vnd, c_amb_vnd = c_amb_vnd,
              cost_per_vaccinee = cost_per_vaccinee,
              yld_per_case = yld_per_case(p),
              le_death = p$le_death,
              yll_per_death_undisc = yll_per_death(p$le_death, 0),
              yll_per_death_disc = yll_per_death(p$le_death, p$disc_rate))
round(unit_tab, 4)
          c_hosp_vnd            c_amb_vnd    cost_per_vaccinee 
         2.22782e+06          6.44000e+05          2.68120e+06 
        yld_per_case             le_death yll_per_death_undisc 
         1.04000e-02          4.57804e+01          4.57804e+01 
  yll_per_death_disc 
         2.50887e+01 
stopifnot(yll_per_death(p$le_death, p$disc_rate) < p$le_death)
# Cross-check against published Vietnamese cost studies (medians, Hung et al. 2018)
cross_tab <- data.frame(case = c("hospitalised", "ambulatory"),
                        price_list = c(c_hosp_vnd, c_amb_vnd),
                        cost_studies = usd2016_to_vnd2025(c(p$c_hosp_usd, p$c_amb_usd)))
cross_tab$ratio <- cross_tab$price_list / cross_tab$cost_studies
cross_tab[, -1] <- round(cross_tab[, -1], 2)
cross_tab
          case price_list cost_studies ratio
1 hospitalised    2227820    1778551.5  1.25
2   ambulatory     644000     703889.3  0.91

Một ca nhập viện tốn 2,227,820 VNĐ và một ca ngoại trú 644,000 VNĐ theo giá BHYT, bằng 1.25 và 0.91 lần con số từ các nghiên cứu chi phí: hai cách tính độc lập cho cùng cỡ số. Tiêm đủ liều cho một trẻ tốn 2,681,200 VNĐ, gấp 1.2 lần một ca nhập viện. Mỗi ca có triệu chứng mất 0.0104 năm sống khỏe mạnh, 90% trong đó do mệt mỏi kéo dài; mỗi ca tử vong mất 25.1 năm sống sau chiết khấu.

Tiếp theo, chuyển đầu ra hằng ngày thành dòng sự kiện, chi phí và DALY. Kiểm tra: mọi dòng không âm (cân bằng giữa ngăn đếm và các ngăn người nằm ở bộ xác minh cuối bài). Bảng cho thấy chi phí tiêm gần như không đổi, còn số ca dao động theo giai đoạn trăng mật.

# Daily flows (increments of the counters) -> annual summary for the dynamic model
f_base <- dengue_flows(base, p)
f_dyn <- dengue_flows(dyn, p)
f_sta <- dengue_flows(sta, p)
f_dyn_stop <- dengue_flows(dyn_stop, p)
f_sta_stop <- dengue_flows(sta_stop, p)
for (f in list(f_base, f_dyn, f_sta, f_dyn_stop, f_sta_stop)) {
  stopifnot(all(f$infections > -1e-6), all(f$cost >= -1e-6), all(f$daly > -1e-9))
}
yr <- ceiling(f_dyn$day / DAYS_PER_YEAR)
annual_dyn <- data.frame(
  year = 1:5,
  symptomatic = as.vector(tapply(f_dyn$symptomatic, yr, sum))[1:5],
  vaccinated = as.vector(tapply(f_dyn$vaccinated, yr, sum))[1:5],
  cost_vacc_million = as.vector(tapply(f_dyn$cost_vacc, yr, sum))[1:5] / 1e6,
  cost_treat_million = as.vector(tapply(f_dyn$cost_treat, yr, sum))[1:5] / 1e6,
  daly = as.vector(tapply(f_dyn$daly, yr, sum))[1:5])
round(annual_dyn, 1)
  year symptomatic vaccinated cost_vacc_million cost_treat_million daly
1    1      2662.3     9545.4           25593.1             3191.0 37.4
2    2      2051.2     9545.4           25593.1             2458.6 28.8
3    3      1814.7     9545.4           25593.1             2175.0 25.5
4    4      2115.2     9545.4           25593.1             2535.3 29.7
5    5      2814.9     9545.4           25593.1             3373.8 39.5

Bây giờ tính kết quả gia tăng ở khung cơ sở 20 năm (chương trình chạy liên tục) cho hai mô hình. Kiểm tra: cột status phải là ICER; nếu là dominant hay dominated, ICER không được tính và phải đọc INMB.

# Base case: the programme runs for H years and outcomes are counted for those H years
# (horizon and discount rate come from the parameter file)
t_base <- dengue_totals(f_base, H)
t_dyn <- dengue_totals(f_dyn, H)
t_sta <- dengue_totals(f_sta, H)
inc_dyn <- dengue_icer(t_dyn, t_base, p$wtp)
inc_sta <- dengue_icer(t_sta, t_base, p$wtp)
cea_tab <- rbind(dynamic = inc_dyn, static = inc_sta)
cea_show <- data.frame(model = rownames(cea_tab),
                       cases_averted = cea_tab$cases_averted,
                       deaths_averted = cea_tab$deaths_averted,
                       d_cost_billion = cea_tab$d_cost / 1e9,
                       dalys_averted = cea_tab$dalys_averted,
                       icer_million = cea_tab$icer / 1e6,
                       inmb_billion = cea_tab$inmb / 1e9,
                       status = cea_tab$status)
cea_show[, 2:7] <- round(cea_show[, 2:7], 2)
cea_show                                   # read the status column first: is an ICER defined?
    model cases_averted deaths_averted d_cost_billion dalys_averted
1 dynamic       5783.06           0.84         380.65         67.86
2  static       3602.54           0.52         383.21         37.89
  icer_million inmb_billion status
1      5609.23      -372.13   ICER
2     10113.90      -378.45   ICER

Mô hình động cho ICER 5,609 triệu VNĐ cho mỗi DALY tránh được; mô hình tĩnh cho 10,114 triệu. So với ngưỡng một lần GDP bình quân đầu người (125.5 triệu VNĐ/DALY (Cục Thống kê (Bộ Tài chính) 2026a)), ICER động cao gấp 45 lần và ICER tĩnh cao gấp 81 lần. Mốc này là thông lệ của các phân tích ở Việt Nam, không phải ngưỡng do cơ quan quản lý ban hành (Ong và c.s. 2026); ngưỡng theo chi phí cơ hội còn thấp hơn, khoảng 62.75 triệu (Woods và c.s. 2016). Đọc kết quả đúng mức: giá VNVC là giá bán lẻ, không phải giá một chương trình công sẽ đàm phán, và mô hình tái tạo được số ca được báo cáo nhưng thấp hơn nhiều ước tính gánh nặng có hiệu chỉnh báo cáo thiếu (Bài 4).

Đoạn sau đo độ lệch của mô hình tĩnh bằng các tỷ số và bằng mức chi tối đa cho mỗi trẻ được tiêm (cả hai liều) mà chương trình vẫn đạt chi phí-hiệu quả ở ngưỡng một lần GDP bình quân đầu người: (giá trị DALY tránh được + chi phí điều trị tiết kiệm) chia cho số trẻ được tiêm đã chiết khấu. Đây là kết quả để đọc, không phải điều kiện bắt buộc.

# How wrong is the static model? Ratios of results, and the most the programme could
# spend per vaccinated child (both doses, vaccine + delivery, discounted) and still be
# cost-effective at the threshold: (wtp * DALYs averted - change in treatment cost) /
# discounted number of children vaccinated.
max_spend_per_child <- function(f_prog, t_prog, t_ref, horizon, wtp) {
  f <- f_prog[f_prog$day <= horizon * DAYS_PER_YEAR, ]
  vacc_disc <- sum(f$vaccinated * f$disc)
  d_treat <- t_prog[["cost_treat"]] - t_ref[["cost_treat"]]
  d_daly <- t_ref[["daly"]] - t_prog[["daly"]]
  (wtp * d_daly - d_treat) / vacc_disc
}
spend_dyn <- max_spend_per_child(f_dyn, t_dyn, t_base, H, p$wtp)
spend_sta <- max_spend_per_child(f_sta, t_sta, t_base, H, p$wtp)
bias <- c(cases_averted_ratio = inc_dyn$cases_averted / inc_sta$cases_averted,
          dalys_averted_ratio = inc_dyn$dalys_averted / inc_sta$dalys_averted,
          icer_ratio_static_vs_dynamic = inc_sta$icer / inc_dyn$icer,
          max_spend_dynamic = spend_dyn, max_spend_static = spend_sta,
          actual_spend = cost_per_vaccinee)
round(bias, 2)                              # results to read, not assertions: the sign is not guaranteed
         cases_averted_ratio          dalys_averted_ratio 
                        1.61                         1.79 
icer_ratio_static_vs_dynamic            max_spend_dynamic 
                        1.80                     99296.79 
            max_spend_static                 actual_spend 
                    55440.92                   2681200.00 

Trong giả định vắc xin giảm nhiễm, mô hình động cho số ca tránh được gấp 1.61 lần và DALY tránh được gấp 1.79 lần mô hình tĩnh; ICER tĩnh cao gấp 1.80 lần.

Mức chi tối đa cho mỗi trẻ (cả hai liều và khám sàng lọc) là 99,297 VNĐ theo mô hình động nhưng chỉ 55,441 VNĐ theo mô hình tĩnh, trong khi giá VNVC hiện là 2,681,200 VNĐ, gấp 27 lần mức chi tối đa của mô hình động. Thử nghiệm chỉ đo bệnh có xác nhận virus, nên các tỷ số này là cận trên có điều kiện; một kịch bản vắc xin chỉ ngăn bệnh, không có hiệu quả gián tiếp, nên đưa vào phân tích độ nhạy (M03).

Đoạn sau tính ICER ở bốn mốc thời gian, cho chương trình chạy liên tục và cho chương trình dừng sau 20 năm. Kiểm tra: hai kịch bản trùng nhau ở mốc 20 năm, vì lúc đó chương trình chưa kịp dừng.

# Results by time horizon, two ways of reading "horizon":
#   continuous: the programme is still running when we stop counting (truncation)
#   stop:       the programme runs H years, then everyone is followed until the horizon
horizons <- c(h_low, H, h_mid, h_high)
hz <- NULL
add_rows <- function(hz, label, flows_dyn, flows_sta, h) {
  tb <- dengue_totals(f_base, h)
  for (m in c("dynamic", "static")) {
    fm <- if (m == "dynamic") flows_dyn else flows_sta
    r <- dengue_icer(dengue_totals(fm, h), tb, p$wtp)
    hz <- rbind(hz, data.frame(scenario = label, horizon = h, model = m,
                               cases_averted = r$cases_averted, dalys_averted = r$dalys_averted,
                               icer = r$icer))
  }
  hz
}
for (h in horizons) hz <- add_rows(hz, "continuous", f_dyn, f_sta, h)
for (h in horizons[horizons >= H]) hz <- add_rows(hz, "stop", f_dyn_stop, f_sta_stop, h)
hz_show <- hz
hz_show$icer_million <- round(hz_show$icer / 1e6, 1)
hz_show$icer <- NULL
hz_show[, c("cases_averted", "dalys_averted")] <- round(hz_show[, c("cases_averted", "dalys_averted")], 1)
hz_show
     scenario horizon   model cases_averted dalys_averted icer_million
1  continuous      10 dynamic        4876.9          60.2       3595.1
2  continuous      10  static        1869.6          22.1       9942.4
3  continuous      20 dynamic        5783.1          67.9       5609.2
4  continuous      20  static        3602.5          37.9      10113.9
5  continuous      30 dynamic        6233.2          70.5       7134.4
6  continuous      30  static        4614.7          44.7      11292.5
7  continuous      50 dynamic        7342.1          75.1       8811.8
8  continuous      50  static        5890.2          50.4      13179.1
9        stop      20 dynamic        5783.1          67.9       5609.2
10       stop      20  static        3602.5          37.9      10113.9
11       stop      30 dynamic        2331.6          43.1       8887.8
12       stop      30  static        2745.1          32.5      11800.5
13       stop      50 dynamic        1443.2          38.1      10052.5
14       stop      50  static        1275.5          25.6      15004.2

Đoạn sau đặt cạnh nhau ICER cắt cụt (chương trình và khung cùng 20 năm) và ICER khi theo dõi đến 50 năm.

# The base-case ICER (programme and horizon both H years) against the same programme
# followed until h_high after it stops. Postponed infections now fall inside the horizon.
pick <- function(sc, h, m) hz[hz$scenario == sc & hz$horizon == h & hz$model == m, ]
follow_tab <- NULL
for (m in c("dynamic", "static")) {
  a <- pick("continuous", H, m); b <- pick("stop", h_high, m)
  follow_tab <- rbind(follow_tab, data.frame(
    model = m,
    cases_truncated = a$cases_averted, cases_followed = b$cases_averted,
    dalys_truncated = a$dalys_averted, dalys_followed = b$dalys_averted,
    icer_truncated_million = a$icer / 1e6, icer_followed_million = b$icer / 1e6,
    icer_change_pct = 100 * (b$icer / a$icer - 1)))
}
follow_tab[, -1] <- round(follow_tab[, -1], 1)
follow_tab
    model cases_truncated cases_followed dalys_truncated dalys_followed
1 dynamic          5783.1         1443.2            67.9           38.1
2  static          3602.5         1275.5            37.9           25.6
  icer_truncated_million icer_followed_million icer_change_pct
1                 5609.2               10052.5            79.2
2                10113.9               15004.2            48.4

Khi theo dõi đủ lâu, ICER của mô hình động tăng từ 5,609 lên 10,052 triệu VNĐ/DALY (tăng 79%), vì số ca tránh được giảm từ 5,783 xuống 1,443: phần lớn ca “tránh được” ở cách cắt cụt chỉ bị hoãn. Ở mô hình tĩnh ICER tăng 48%. Con số cắt cụt vì vậy thiên về phía có lợi cho chương trình.

Đoạn sau vẽ ca tránh được cộng dồn (chương trình chạy liên tục) và ICER theo mốc thời gian ở cả hai kịch bản. Kiểm tra: hai đường tách nhau trong giai đoạn trăng mật, thanh ICER cao dần theo mốc.

# Cumulative cases averted over time: static vs dynamic (same colours in every lesson)
model_cols <- c("Mô hình động" = pal_course[1], "Mô hình tĩnh" = pal_course[2])
cum_df <- rbind(
  data.frame(year = f_dyn$day / DAYS_PER_YEAR, model = "Mô hình động",
             averted = cumsum(f_base$symptomatic - f_dyn$symptomatic)),
  data.frame(year = f_sta$day / DAYS_PER_YEAR, model = "Mô hình tĩnh",
             averted = cumsum(f_base$symptomatic - f_sta$symptomatic)))
cum_df <- cum_df[cum_df$year %% 0.25 < 1 / DAYS_PER_YEAR, ]     # thin to quarterly points
fig_cum <- ggplot(cum_df, aes(year, averted, colour = model)) +
  geom_line(linewidth = 1) +
  geom_vline(xintercept = H, linetype = "dashed", colour = "grey40") +
  scale_colour_manual(values = model_cols, name = NULL) +
  scale_y_continuous(labels = function(x) fmt_num(x)) +
  labs(x = "Năm sau khi bắt đầu tiêm chủng", y = "Ca tránh được (cộng dồn)",
       title = "Cùng một vắc xin, hai câu trả lời",
       subtitle = "Chương trình chạy liên tục; đường đứt: khung thời gian phân tích cơ sở; một triệu dân") +
  theme_course()
save_fig(fig_cum, "l05-static-dynamic.png", width = 7, height = 4.2)

hz_plot <- hz
hz_plot$scenario_vi <- ifelse(hz_plot$scenario == "continuous",
                              "Chương trình vẫn chạy khi hết khung thời gian",
                              paste0("Chương trình dừng sau ", H, " năm, rồi theo dõi"))
hz_plot$scenario_vi <- factor(hz_plot$scenario_vi, levels = unique(hz_plot$scenario_vi))
fig_hz <- ggplot(hz_plot, aes(factor(horizon), icer / 1e6,
                              fill = ifelse(model == "dynamic", "Mô hình động", "Mô hình tĩnh"))) +
  geom_col(position = position_dodge(width = 0.8), width = 0.7) +
  geom_hline(yintercept = p$wtp / 1e6, linetype = "dashed", colour = "grey30") +
  facet_wrap(~scenario_vi, scales = "free_x") +
  scale_fill_manual(values = model_cols, name = NULL) +
  scale_y_continuous(labels = function(x) fmt_num(x)) +
  labs(x = "Thời gian tính kết quả (năm)", y = "ICER (triệu VNĐ/DALY tránh được)",
       title = "ICER phụ thuộc mô hình, khung thời gian và việc chương trình có dừng không",
       subtitle = paste0("Đường đứt: ngưỡng 1 × GDP bình quân đầu người; giá VNVC và giá BHYT, chiết khấu ",
                         fmt_pct(p$disc_rate, 0), "/năm")) +
  theme_course()
save_fig(fig_hz, "l05-horizon.png", width = 8, height = 4.4)
Figure 32.1: Số ca có triệu chứng tránh được cộng dồn (chương trình chạy liên tục). Khoảng cách giữa hai đường là hiệu quả gián tiếp.
Figure 32.2: ICER theo thời gian tính kết quả, loại mô hình và việc chương trình có dừng không (triệu VNĐ/DALY tránh được). Đường đứt là ngưỡng một lần GDP bình quân đầu người.

Hiệu quả gián tiếp tập trung trong giai đoạn trăng mật, nên khung ngắn cho ICER động thấp nhất (3,595 triệu ở 10 năm, 8,812 triệu ở 50 năm). Mô hình tĩnh cũng đổi theo khung (từ 9,942 lên 13,179 triệu) vì suy giảm bảo vệ hoãn nhiễm ở đó; nó chỉ không thấy hiệu quả gián tiếp và đợt bật lại. Khi chương trình đã dừng, ở mốc theo dõi 30 năm mô hình động tránh được 2,332 ca, ít hơn mô hình tĩnh (2,745 ca), vì đợt bật lại chỉ có ở mô hình động; nhưng DALY tránh được của mô hình động vẫn nhiều hơn, vì ca tránh được sớm (trong trăng mật) có hệ số chiết khấu lớn hơn ca bị hoãn về sau.

Khi chọn mức bao phủ, câu hỏi là tăng bao phủ có đáng không. Đoạn sau so ba chiến lược trong khung cơ sở bằng ICER trung bình (so với không tiêm) và ICER gia tăng của bao phủ cao so với thấp (xem đường biên hiệu quả ở M01 Bài 3), cho cả mô hình động lẫn tĩnh. Kiểm tra: ở hàng cuối hai cột ICER khác nhau.

# Three strategies compared within the base horizon (dynamic model): no vaccination,
# low coverage, base coverage. Average ICERs are each versus "no vaccination";
# the incremental ICER of base vs low coverage is what a decision maker needs.
run_lo <- dengue_run(dengue_inputs(p, coverage = cov_low), y0, H)
t_lo <- dengue_totals(dengue_flows(run_lo, p), H)
step_lo <- dengue_icer(t_lo, t_base, p$wtp)        # low coverage vs none
step_hi <- dengue_icer(t_dyn, t_lo, p$wtp)         # base coverage vs low coverage
frontier <- data.frame(
  strategy = c("Không tiêm", paste0("Bao phủ ", fmt_pct(cov_low, 0)), paste0("Bao phủ ", fmt_pct(p$coverage, 0))),
  cost_billion = c(t_base[["cost"]], t_lo[["cost"]], t_dyn[["cost"]]) / 1e9,
  dalys_averted = c(0, step_lo$dalys_averted, inc_dyn$dalys_averted),
  average_icer_million = c(NA, step_lo$icer, inc_dyn$icer) / 1e6,
  incremental_icer_million = c(NA, step_lo$icer, step_hi$icer) / 1e6)
frontier[, -1] <- round(frontier[, -1], 2)
frontier
     strategy cost_billion dalys_averted average_icer_million
1  Không tiêm        53.18          0.00                   NA
2 Bao phủ 50%       291.20         41.01              5803.72
3 Bao phủ 80%       433.82         67.86              5609.23
  incremental_icer_million
1                       NA
2                  5803.72
3                  5312.14
# Extended dominance: if the step up costs LESS per DALY than the first step, the lower
# coverage is not on the efficiency frontier (M01 Lesson 3)
extended_dominated <- step_hi$icer < step_lo$icer
extended_dominated
[1] TRUE
# The same two steps with the STATIC model (frozen force of infection): with no herd effect,
# benefits grow almost linearly with coverage, so the incremental ICER stays close to the average ICER
sta_lo <- dengue_run_static(dengue_inputs(p, coverage = cov_low), y0, H, base)
t_sta_lo <- dengue_totals(dengue_flows(sta_lo, p), H)
step_lo_s <- dengue_icer(t_sta_lo, t_base, p$wtp)
step_hi_s <- dengue_icer(t_sta, t_sta_lo, p$wtp)
static_icers <- c(average_low_million = step_lo_s$icer, incremental_high_vs_low_million = step_hi_s$icer) / 1e6
round(static_icers, 1)
            average_low_million incremental_high_vs_low_million 
                        10113.9                         10113.9 

ICER gia tăng của việc nâng bao phủ từ 50% lên 80% là 5,312 triệu VNĐ/DALY, thấp hơn ICER của bước đầu (5,804 triệu).

Vì vậy bao phủ thấp bị lấn át mở rộng (extended dominance) và không nằm trên đường biên: hiệu quả gián tiếp làm bước tăng cuối rẻ hơn bước đầu. Mô hình tĩnh không có vòng phản hồi, nên chi phí và lợi ích tuyến tính theo bao phủ và ICER gia tăng (10,114) bằng ICER trung bình (10,114 triệu VNĐ/DALY).

Ở Bài 4, suy giảm bảo vệ được khớp bằng hai mốc hiệu lực. Đoạn sau thay bằng cách khớp một tham số (chỉ khớp mốc 4.5 năm). Kiểm tra: cách này cho bảo vệ kéo dài hơn nên ICER động phải thấp hơn.

# Sensitivity: use the one-parameter waning fit (E0 = first-year efficacy) instead of the
# two-trial-average fit. Dynamic model only, base horizon.
inp_single <- dengue_inputs(p, ve_fit = "single")
dyn_single <- dengue_run(inp_single, y0, H)
inc_single <- dengue_icer(dengue_totals(dengue_flows(dyn_single, p), H), t_base, p$wtp)
round(c(icer_joint_million = inc_dyn$icer / 1e6, icer_single_million = inc_single$icer / 1e6,
        change_pct = 100 * (inc_single$icer / inc_dyn$icer - 1)), 1)
 icer_joint_million icer_single_million          change_pct 
             5609.2              4965.8               -11.5 
# Check: the one-parameter fit protects longer (Lesson 4), so its ICER must be lower
stopifnot(inc_single$icer < inc_dyn$icer)

Cách khớp một tham số cho ICER động thấp hơn 11%, vì nó coi hiệu lực trung bình năm đầu là hiệu lực lúc tiêm và đánh giá quá cao thời gian bảo vệ.

Cuối cùng là bộ kiểm tra xác minh. Mỗi kiểm tra khẳng định một điều phải đúng với mọi giá trị tham số, nên đều có thể thất bại; nếu có FALSE, script dừng. Điều “mô hình động tránh nhiều ca hơn mô hình tĩnh” không nằm trong bộ này vì nó là kết quả, không phải sự thật bắt buộc.

# Verification tests for the dynamic CEA. Each returns TRUE/FALSE and each CAN fail:
# it asserts something we know must hold, whatever the parameter values.
tests <- list()

# 1. A vaccine with efficacy 0 changes no infection but still costs money
inp_e0 <- inp
inp_e0$eff <- 0
run_e0 <- dengue_run(inp_e0, y0, h_low)
n0 <- nrow(run_e0)
gap_inf <- max(abs((run_e0$C1 + run_e0$C2) - (base$C1 + base$C2)[seq_len(n0)]))
tests[["Hiệu lực 0: ca nhiễm như không tiêm, nhưng chi phí tiêm > 0"]] <-
  gap_inf < 1e-3 && sum(dengue_flows(run_e0, p)$cost_vacc) > 0

# 2. Doubling the vaccine price doubles vaccination cost and leaves treatment cost unchanged
p_2x <- p
p_2x$c_dose <- 2 * p$c_dose
p_2x$c_delivery <- 2 * p$c_delivery
f_2x <- dengue_flows(dyn, p_2x)
tests[["Giá tiêm gấp đôi: chi phí tiêm gấp đôi, chi phí điều trị không đổi"]] <-
  abs(sum(f_2x$cost_vacc) / sum(f_dyn$cost_vacc) - 2) < 1e-9 &&
  abs(sum(f_2x$cost_treat) - sum(f_dyn$cost_treat)) < 1e-6

# 3. Discount timing: delaying every flow by one year multiplies discounted totals by 1/(1+r)
dyn_late <- dyn
dyn_late$time <- dyn$time + DAYS_PER_YEAR
t_now <- dengue_totals(dengue_flows(dyn, p), H)
t_late <- dengue_totals(dengue_flows(dyn_late, p), H + 1)
tests[["Dịch dòng chậm một năm: tổng chiết khấu nhân 1/(1+r)"]] <-
  abs(t_late[["cost"]] / t_now[["cost"]] - 1 / (1 + p$disc_rate)) < 1e-9 &&
  abs(t_late[["daly"]] / t_now[["daly"]] - 1 / (1 + p$disc_rate)) < 1e-9

# 4. Elimination: a perfect, lifelong vaccine above the critical coverage drives infections
#    towards 0; below the critical coverage the infection persists. R0 is lowered so that
#    the critical coverage is below 100% when vaccinating at age 9.
p_t <- p
p_t$r0 <- p$r0_alt_high
p_t$ve_initial <- 1
inp_t <- dengue_inputs(p_t, coverage = 0, waning = FALSE)
crit_cov_t <- (1 - 1 / p_t$r0) / (inp_t$n_groups[["n2"]] / p$n_h)
y_t <- dengue_burnin(inp_t, p$burnin_years)
last_year_infections <- function(cov_t) {
  run <- dengue_run(dengue_inputs(p_t, coverage = cov_t, waning = FALSE), y_t, h_high)
  a <- annual_totals(run)
  tail(a$infections_1 + a$infections_2, 1)
}
endemic_t <- last_year_infections(0)
above_t <- last_year_infections(min(1, 1.1 * crit_cov_t))
below_t <- last_year_infections(0.7 * crit_cov_t)
tests[["Phủ trên ngưỡng tới hạn: ca nhiễm về gần 0; dưới ngưỡng: vẫn còn"]] <-
  above_t < 0.01 * endemic_t && below_t > 0.05 * endemic_t

# 5. Static model at coverage 0 reproduces the baseline infections of the dynamic model
run0 <- dengue_run(inp0, y0, h_low)
sta0 <- dengue_run_static(inp0, y0, h_low, base)
tests[["Mô hình tĩnh, bao phủ 0, tái tạo ca nhiễm nền"]] <-
  abs(sta0$C1[nrow(sta0)] + sta0$C2[nrow(sta0)] - run0$C1[nrow(run0)] - run0$C2[nrow(run0)]) /
  (run0$C1[nrow(run0)] + run0$C2[nrow(run0)]) < 1e-3

# 6. Population is conserved in every run, including across the joint of the two-leg runs
tests[["Dân số người không đổi trong mọi lần chạy"]] <-
  max(abs(c(dyn$Nh, sta$Nh, dyn_stop$Nh, sta_stop$Nh) - p$n_h)) < 1e-3

# 7. When the programme stops, vaccinations stop; before that they happen every day
dv <- diff(dyn_stop$CV)
tests[["Sau khi dừng chương trình không còn lượt tiêm; trước đó có mỗi ngày"]] <-
  max(abs(dv[-seq_len(H * DAYS_PER_YEAR)])) < 1e-9 && min(dv[seq_len(H * DAYS_PER_YEAR)]) > 0

# 8. Mass balance of susceptibles in the no-vaccination run: the change in S equals births minus
#    deaths of susceptibles minus new infections. This ties the infection COUNTERS to the compartments.
s_cols <- c(paste0("S1_", seq_len(inp0$k)), "S2")
S_tot <- rowSums(base[, s_cols])
trapz <- function(x, y) sum(diff(x) * (head(y, -1) + tail(y, -1)) / 2)
infections_total <- base$C1[nrow(base)] + base$C2[nrow(base)]
balance_gap <- (S_tot[nrow(base)] - S_tot[1]) -
  (inp0$mu * p$n_h * max(base$time) - inp0$mu * trapz(base$time, S_tot) - infections_total)
tests[["Cân bằng người cảm nhiễm: đổi S = sinh - tử vong - nhiễm mới"]] <-
  abs(balance_gap) < 1e-4 * infections_total

verify_tab <- data.frame(test = names(tests), pass = unlist(tests), row.names = NULL)
verify_tab
                                                                 test pass
1         Hiệu lực 0: ca nhiễm như không tiêm, nhưng chi phí tiêm > 0 TRUE
2  Giá tiêm gấp đôi: chi phí tiêm gấp đôi, chi phí điều trị không đổi TRUE
3                Dịch dòng chậm một năm: tổng chiết khấu nhân 1/(1+r) TRUE
4    Phủ trên ngưỡng tới hạn: ca nhiễm về gần 0; dưới ngưỡng: vẫn còn TRUE
5                       Mô hình tĩnh, bao phủ 0, tái tạo ca nhiễm nền TRUE
6                           Dân số người không đổi trong mọi lần chạy TRUE
7 Sau khi dừng chương trình không còn lượt tiêm; trước đó có mỗi ngày TRUE
8        Cân bằng người cảm nhiễm: đổi S = sinh - tử vong - nhiễm mới TRUE
stopifnot(all(verify_tab$pass))
round(c(critical_coverage = crit_cov_t, endemic = endemic_t, above = above_t, below = below_t), 3)
critical_coverage           endemic             above             below 
            0.846          9857.102             0.000          2966.688 

Kiểm tra thứ tám nối các ngăn đếm với ngăn người: thay đổi số người cảm nhiễm phải bằng số sinh trừ tử vong trừ ca nhiễm mới. Kiểm tra thứ năm đáng chú ý: khi không tiêm, mô hình tĩnh phải tái tạo đúng số ca của lần chạy nền, vì nếu không phiên bản tĩnh không còn là “cùng mô hình”. Kiểm tra thứ tư dùng R0 = 4 để bao phủ tới hạn (85%) nằm dưới 100%; ở R0 = 9.7 của bài, tiêm ở tuổi 9 không thể chặn lây truyền (Bài 3).

Mẹo thực hành
  • Thêm ngăn đếm cộng dồn cho mọi sự kiện có chi phí hoặc hậu quả sức khỏe. Tính dòng bằng diff(); đừng tích phân lại số người trong ngăn.
  • Xây phiên bản tĩnh từ chính mô hình động (đóng băng lực lây nhiễm), để độ lệch đo được chỉ là hiệu quả gián tiếp.
  • Báo cáo theo nhiều khung thời gian và tách rõ thời gian chạy chương trình khỏi thời gian theo dõi.
  • Một kiểm tra tốt đặt giá trị cực đoan vào tham số (hiệu lực 0, giá gấp đôi, trễ một năm) và biết trước đáp án.
Lỗi thường gặp
  • Gắn chi phí điều trị cho số người đang nhiễm \(I(t)\) thay vì cho số ca mới. Kết quả phụ thuộc thời gian lây nhiễm thay vì số ca.
  • Chiết khấu theo năm cho đầu ra theo ngày mà quên đổi đơn vị. Dùng \((1+r)^{-t/365}\) khi \(t\) tính bằng ngày.
  • Cắt khung thời gian đúng lúc chương trình còn chạy. ICER khi đó tính ca bị hoãn là tránh được.
  • Viết “kiểm tra” chỉ để khẳng định điều mong đợi hoặc điều đúng theo cách xây dựng. Kiểm tra như vậy không bao giờ bắt được lỗi.

Bài tập

  1. (Dễ) Tính lại ICER của hai mô hình với tỷ lệ chiết khấu 0%, 3% và 5%. Mô hình nào nhạy hơn? Vì sao?
  2. (Trung bình) Bỏ mệt mỏi kéo dài sau SXHD khỏi DALY (đặt tỷ lệ bằng 0). YLD mỗi ca và ICER thay đổi bao nhiêu?
  3. (Trung bình) Phân tích cơ sở gộp số tử vong và số ca được báo cáo của ba năm 2023–2025. Thay bằng tỷ lệ của từng năm, rồi bằng tỷ lệ toàn cầu trên ca có triệu chứng (Shepard và c.s. 2016) quy về ca đến cơ sở y tế. Tử vong tránh được, DALY tránh được và ICER của mô hình động thay đổi ra sao?
  4. (Khó) Chương trình dừng sau 20 năm. Tính mức chi tối đa cho mỗi trẻ, cho cả hai mô hình, khi theo dõi 20, 30 và 50 năm. Kết luận về giá vắc xin có đổi không?

Bài 1. Chiết khấu làm ICER tăng ở cả hai mô hình, vì chi phí tiêm phát sinh đều đặn còn lợi ích, nhất là YLL, trải dài trong tương lai.

# Exercise 1: discount rate at its low, base and high value (costs and DALYs);
# ICERs in million VND/DALY
icer_at <- function(r) {
  tb <- dengue_totals(dengue_flows(base, p, disc_rate = r), H)
  c(dynamic = dengue_icer(dengue_totals(dengue_flows(dyn, p, disc_rate = r), H), tb, p$wtp)$icer,
    static = dengue_icer(dengue_totals(dengue_flows(sta, p, disc_rate = r), H), tb, p$wtp)$icer)
}
rates <- c(p$disc_rate_low, p$disc_rate, p$disc_rate_high)       # shared bounds (kb/_shared)
names(rates) <- paste0("disc_", fmt_pct(rates, 0))
icer_by_rate <- sapply(rates, icer_at) / 1e6
round(icer_by_rate, 1)
        disc_0% disc_3% disc_5%
dynamic  5121.0  5609.2  5664.2
static   8263.2 10113.9 10969.5

Từ 0% lên 5%, ICER động tăng 11%, ICER tĩnh tăng 33%: mô hình tĩnh nhạy hơn. Lợi ích gián tiếp của mô hình động dồn vào giai đoạn trăng mật, tức những năm đầu, nơi chiết khấu còn nhẹ.

Bài 2. Mệt mỏi kéo dài chiếm 90% YLD mỗi ca theo cách tính GBD 2013, nên bỏ nó làm DALY tránh được giảm và ICER tăng mạnh.

# Exercise 2: drop post-acute fatigue from the DALY (p_fatigue = 0)
p_nf <- p
p_nf$p_fatigue <- 0
tb_nf <- dengue_totals(dengue_flows(base, p_nf), H)
round(c(yld_per_case_with = yld_per_case(p), yld_per_case_without = yld_per_case(p_nf),
        icer_dynamic_million = dengue_icer(dengue_totals(dengue_flows(dyn, p_nf), H), tb_nf,
                                           p$wtp)$icer / 1e6), 4)
   yld_per_case_with yld_per_case_without icer_dynamic_million 
              0.0104               0.0011           16652.0247 

YLD mỗi ca giảm 9.7 lần. Giả định này bất định, nên cần đưa vào phân tích độ nhạy (Hung và c.s. 2018).

Bài 3. Tỷ lệ tử vong cao hơn làm tăng tử vong và YLL tránh được, nên DALY tránh được tăng và ICER giảm; nhưng vì YLD chiếm phần lớn DALY ở đây, ICER chỉ đổi vừa phải.

# Exercise 3: deaths per reported case year by year (2023, 2024, 2025) instead of the pooled
# 2023-2025 rate, and the global rate per symptomatic case (Shepard 2016) converted to a rate
# per attended case. Dynamic model, base horizon.
cfr_year <- c(y2023 = p$rep_deaths_2023 / p$rep_cases_2023, y2024 = p$rep_deaths_2024 / p$rep_cases_2024,
              y2025 = p$rep_deaths_2025 / p$rep_cases_2025)
cfr_all <- c(pooled_2023_2025 = p$cfr_attended, cfr_year, global = p$cfr_sympt / p$p_attended)
ex3 <- NULL
for (nm in names(cfr_all)) {
  p_c <- p
  p_c$cfr_attended <- cfr_all[[nm]]
  tb_c <- dengue_totals(dengue_flows(base, p_c), H)
  r <- dengue_icer(dengue_totals(dengue_flows(dyn, p_c), H), tb_c, p$wtp)
  ex3 <- rbind(ex3, data.frame(deaths_per_100000_attended = 1e5 * cfr_all[[nm]], deaths_averted = r$deaths_averted,
                               dalys_averted = r$dalys_averted, icer_million = r$icer / 1e6,
                               row.names = nm))
}
round(ex3, 2)
                 deaths_per_100000_attended deaths_averted dalys_averted
pooled_2023_2025                      22.07           0.84         67.86
y2023                                 25.00           0.95         70.20
y2024                                 17.61           0.67         64.29
y2025                                 22.63           0.86         68.30
global                                35.30           1.35         78.45
                 icer_million
pooled_2023_2025      5609.23
y2023                 5422.00
y2024                 5921.19
y2025                 5572.82
global                4851.92

ICER dao động từ 4,852 đến 5,921 triệu VNĐ/DALY, so với 5,609 triệu ở phân tích cơ sở. Năm có tỷ lệ tử vong thấp nhất (2024) cho ICER cao nhất trong ba năm. Tỷ lệ toàn cầu cao hơn tỷ lệ báo cáo của Việt Nam, một lời nhắc rằng số tử vong do SXHD có thể bị báo cáo thiếu như số ca.

Bài 4. Khi theo dõi dài hơn, mức chi tối đa của mô hình động giảm vì phần lớn ca tránh được trong trăng mật chỉ bị hoãn. Đọc cả hai hàng: khoảng cách giữa hai mô hình thay đổi theo mốc theo dõi, và giá hiện hành (2,681,200 VNĐ) vẫn cao hơn nhiều mức chi tối đa ở mọi mốc.

# Exercise 4: the maximum affordable spend per vaccinated child when the programme stops after
# H years, for follow-up of H, h_mid and h_high years (both models)
spend_by_followup <- function(f_dyn_x, f_sta_x, h) {
  tb <- dengue_totals(f_base, h)
  c(dynamic = max_spend_per_child(f_dyn_x, dengue_totals(f_dyn_x, h), tb, H, p$wtp),
    static = max_spend_per_child(f_sta_x, dengue_totals(f_sta_x, h), tb, H, p$wtp))
}
spend_tab <- sapply(c(H, h_mid, h_high), function(h) spend_by_followup(f_dyn_stop, f_sta_stop, h))
colnames(spend_tab) <- paste0("followup_", c(H, h_mid, h_high), "y")
round(spend_tab)
        followup_20y followup_30y followup_50y
dynamic        99297        63016        55777
static         55441        47574        37473

Tài liệu tham khảo

# Slide Lời giảng chính Hình/Code
1 Chương trình, không phải đoàn hệ cả tỉnh theo lịch; lứa trẻ mới mỗi năm —
2 Từ trạng thái đến sự kiện chi phí gắn với dòng; ngăn đếm cộng dồn chunk l05-flows
3 Chi phí đơn vị giá BHYT từ dưới lên: 2,227,820 VNĐ mỗi ca nhập viện; đối chiếu nghiên cứu (USD 2016 → VNĐ 2025); giá VNVC 2,681,200 VNĐ mỗi trẻ chunk l05-unit-costs
4 DALY YLD theo GBD 2013; YLL từ bảng sống Việt Nam, chiết khấu còn 25.1 năm công thức YLL
5 Mô hình tĩnh từ mô hình động đóng băng λ; khác biệt duy nhất = hiệu quả gián tiếp foi_fun trong dengue_ode()
6 Kết quả cơ sở ICER động 5,609 vs tĩnh 10,114 triệu VNĐ/DALY; ngưỡng 125.5 triệu chunk l05-cea
7 Độ lệch của mô hình tĩnh số ca tránh được gấp 1.61 lần nếu vắc xin giảm lây chunk l05-bias
8 Chạy bao lâu, theo dõi bao lâu ca bị hoãn; ICER động tăng 79% khi theo dõi đủ lâu chunk l05-followup, figs/l05-horizon.png
9 ICER gia tăng bao phủ cao vs thấp; lấn át mở rộng chunk l05-frontier
10 Xác minh kiểm tra có thể thất bại: hiệu lực 0, giá gấp đôi, trễ một năm, ngưỡng tới hạn chunk l05-verify
11 Đọc kết quả đúng mức giá bán lẻ ≠ giá chương trình; ca được báo cáo ≠ gánh nặng thật; mức chi tối đa 99,297 VNĐ mỗi trẻ chunk l05-bias
12 Tổng kết module tĩnh vs động; R0; NGM; mùa; trăng mật; CEA —