This report creates five publication-quality surveillance figures for H5 high pathogenicity avian influenza (HPAI), including H5N1 clade 2.3.4.4b, in Australia. It combines an official state-level data snapshot with deliberately conservative stochastic scenarios.
Important interpretation: the stochastic results are scenario simulations, not forecasts. They show what could occur under explicitly stated assumptions. They should not be interpreted as predictions of the true number of infected birds, because confirmed detections depend strongly on carcass discovery, reporting, sampling, laboratory confirmation, species distribution, and surveillance effort.
Official snapshot embedded below:
Primary official sources:
# -----------------------------------------------------------------------------
# OFFICIAL H5 DATA SNAPSHOT, 6 AUGUST 2026
# The value 123 is deliberately written explicitly in the code, as requested.
# These are confirmed detections in wild birds, not estimated infections.
# -----------------------------------------------------------------------------
national_h5_confirmed_detections <- 123L
national_poultry_detections <- 0L
snapshot_date <- as.Date("2026-08-06")
state_data <- tribble(
~state_code, ~state_name, ~confirmed_detections, ~confirmed_events,
"ACT", "Australian Capital Territory", 0L, 0L,
"NSW", "New South Wales", 2L, 2L,
"NT", "Northern Territory", 0L, 0L,
"QLD", "Queensland", 1L, 1L,
"SA", "South Australia", 87L, 22L,
"TAS", "Tasmania", 0L, 0L,
"VIC", "Victoria", 23L, 7L,
"WA", "Western Australia", 10L, 10L
) %>%
mutate(
share_percent = 100 * confirmed_detections / national_h5_confirmed_detections,
detections_per_event = if_else(confirmed_events > 0,
confirmed_detections / confirmed_events,
NA_real_)
)
stopifnot(sum(state_data$confirmed_detections) == national_h5_confirmed_detections)
stopifnot(sum(state_data$confirmed_events) == 42L)
kable(
state_data,
digits = 2,
caption = "Observed Australian H5 wild-bird detections and confirmed events, 6 August 2026"
)
| state_code | state_name | confirmed_detections | confirmed_events | share_percent | detections_per_event |
|---|---|---|---|---|---|
| ACT | Australian Capital Territory | 0 | 0 | 0.00 | NA |
| NSW | New South Wales | 2 | 2 | 1.63 | 1.00 |
| NT | Northern Territory | 0 | 0 | 0.00 | NA |
| QLD | Queensland | 1 | 1 | 0.81 | 1.00 |
| SA | South Australia | 87 | 22 | 70.73 | 3.95 |
| TAS | Tasmania | 0 | 0 | 0.00 | NA |
| VIC | Victoria | 23 | 7 | 18.70 | 3.29 |
| WA | Western Australia | 10 | 10 | 8.13 | 1.00 |
The difference between detections and events is scientifically important. Multiple positive birds may belong to one epidemiological event. Therefore, neither measure should automatically be treated as incidence in the underlying wild-bird population.
Let \(Y_{s,t}\) be the number of newly confirmed detections in scenario \(s\) and week \(t\). The simulation uses a negative-binomial observation process with a time-varying effective reproduction multiplier and density-dependent attenuation:
\[ Y_{s,t+1} \sim \operatorname{NegBin}\left(\mu_{s,t+1}, k_s\right), \]
\[ \mu_{s,t+1} = \left[Y_{s,t}R_{s,t}\left(1-\frac{C_{s,t}}{K_s}\right)_+ + \lambda_s\right]q_{s,t}, \]
\[ R_{s,t}=R_{0,s}\exp(-\delta_s t), \]
\[ C_{s,t+1}=C_{s,t}+Y_{s,t+1}. \]
Here:
This structure avoids mechanically generating unrealistically explosive counts. It is still a simplified surveillance model rather than a mechanistic wild-bird transmission model.
scenario_parameters <- tribble(
~scenario, ~description, ~R0, ~decline_delta, ~K_additional, ~importation_lambda, ~ascertainment_q, ~dispersion_k,
"A: Rapid containment",
"Strong surveillance and effective local interruption",
0.72, 0.055, 140, 0.15, 0.90, 0.45,
"B: Sustained low-level detection",
"Persistent coastal detections with gradual control",
0.92, 0.035, 200, 0.30, 1.00, 0.40,
"C: Moderate wildlife expansion",
"Limited local amplification across affected coastal systems",
1.08, 0.045, 280, 0.45, 1.05, 0.35,
"D: Adverse but bounded",
"Higher short-term amplification followed by intensified control",
1.22, 0.070, 380, 0.65, 1.10, 0.30
) %>%
mutate(
initial_weekly_detections = 18L,
initial_cumulative = national_h5_confirmed_detections,
horizon_weeks = params$horizon_weeks,
simulations = params$n_sim
)
kable(
scenario_parameters,
digits = 3,
caption = "Parameters for conservative stochastic H5 detection scenarios"
)
| scenario | description | R0 | decline_delta | K_additional | importation_lambda | ascertainment_q | dispersion_k | initial_weekly_detections | initial_cumulative | horizon_weeks | simulations |
|---|---|---|---|---|---|---|---|---|---|---|---|
| A: Rapid containment | Strong surveillance and effective local interruption | 0.72 | 0.055 | 140 | 0.15 | 0.90 | 0.45 | 18 | 123 | 12 | 3000 |
| B: Sustained low-level detection | Persistent coastal detections with gradual control | 0.92 | 0.035 | 200 | 0.30 | 1.00 | 0.40 | 18 | 123 | 12 | 3000 |
| C: Moderate wildlife expansion | Limited local amplification across affected coastal systems | 1.08 | 0.045 | 280 | 0.45 | 1.05 | 0.35 | 18 | 123 | 12 | 3000 |
| D: Adverse but bounded | Higher short-term amplification followed by intensified control | 1.22 | 0.070 | 380 | 0.65 | 1.10 | 0.30 | 18 | 123 | 12 | 3000 |
Parameter values are transparent scenario assumptions, not fitted estimates. They are chosen to create plausible bounded stress tests rather than extreme projections.
# ozmaps supplies Australian state polygons as an sf object.
aus_states <- ozmaps::ozmap_states
# Harmonise state labels robustly across ozmaps versions.
name_field <- intersect(c("NAME", "name", "STATE_NAME", "state"), names(aus_states))[1]
if (is.na(name_field)) stop("Could not identify the state-name field in ozmap_states.")
aus_map <- aus_states %>%
mutate(state_name_map = .data[[name_field]]) %>%
left_join(state_data, by = c("state_name_map" = "state_name")) %>%
mutate(
confirmed_detections = replace_na(confirmed_detections, 0L),
burden_class = cut(
confirmed_detections,
breaks = c(-Inf, 0, 2, 10, 25, Inf),
labels = c("0", "1–2", "3–10", "11–25", ">25"),
right = TRUE
)
)
centroids <- suppressWarnings(st_point_on_surface(aus_map))
centroid_coords <- st_coordinates(centroids)
centroids <- centroids %>%
mutate(x = centroid_coords[, 1], y = centroid_coords[, 2])
p1 <- ggplot(aus_map) +
geom_sf(aes(fill = confirmed_detections), colour = "white", linewidth = 0.55) +
geom_text(
data = st_drop_geometry(centroids),
aes(x = x, y = y, label = paste0(state_code, "\n", confirmed_detections)),
fontface = "bold", size = 3.5
) +
scale_fill_viridis_c(
option = "C", trans = "sqrt", begin = 0.08, end = 0.95,
breaks = c(0, 1, 2, 10, 25, 50, 87),
name = "Confirmed\ndetections"
) +
coord_sf(crs = 3577, expand = FALSE) +
labs(
title = "State burden of confirmed H5 detections in Australian wild birds",
subtitle = "Laboratory-confirmed detections; square-root colour scale preserves low-count visibility",
caption = paste0(
"Data: Australian Government DAFF, 11:00 AEST, 6 August 2026. ",
"National total = ", national_h5_confirmed_detections,
". Counts represent detected positive birds, not estimated infections."
)
) +
theme_surveillance() +
theme(axis.text = element_blank(), axis.title = element_blank(), panel.grid = element_blank())
p1
ggsave("figures/Figure_1_state_H5_burden_map.png", p1, width = 10, height = 7, dpi = 400)
plot_state <- state_data %>%
filter(confirmed_detections > 0) %>%
arrange(confirmed_detections) %>%
mutate(state_code = factor(state_code, levels = state_code))
p2a <- ggplot(plot_state, aes(x = confirmed_detections, y = state_code)) +
geom_segment(aes(x = 0, xend = confirmed_detections, yend = state_code),
linewidth = 1.2, colour = "grey75") +
geom_point(aes(size = confirmed_events), shape = 21, stroke = 0.8) +
geom_text(aes(label = confirmed_detections), hjust = -0.65, size = 3.8, fontface = "bold") +
scale_size_continuous(range = c(4, 12), name = "Confirmed events") +
scale_x_continuous(expand = expansion(mult = c(0, 0.15))) +
labs(
title = "Confirmed detections and event structure",
subtitle = "Point size represents independent confirmed events",
x = "Confirmed positive wild birds", y = NULL
) +
theme_surveillance()
p2b <- plot_state %>%
filter(confirmed_events > 0) %>%
ggplot(aes(x = reorder(state_code, detections_per_event), y = detections_per_event)) +
geom_col(width = 0.68) +
geom_hline(yintercept = 1, linetype = "dashed", linewidth = 0.6) +
geom_text(aes(label = sprintf("%.1f", detections_per_event)), vjust = -0.45, fontface = "bold") +
scale_y_continuous(expand = expansion(mult = c(0, 0.14))) +
labs(
title = "Within-event detection intensity",
subtitle = "Higher values may indicate clustered mortality or intensified sampling",
x = "State", y = "Detections per confirmed event"
) +
theme_surveillance()
p2 <- p2a + p2b + plot_annotation(
title = "H5 surveillance burden: positive birds versus confirmed events",
caption = "An event may contain multiple positive birds; differences can reflect both epidemiology and surveillance practice."
)
p2
ggsave("figures/Figure_2_detection_event_structure.png", p2, width = 13, height = 6.5, dpi = 400)
simulate_one_path <- function(par, horizon = params$horizon_weeks) {
y <- integer(horizon + 1)
cumulative <- numeric(horizon + 1)
Rt <- numeric(horizon + 1)
y[1] <- par$initial_weekly_detections
cumulative[1] <- par$initial_cumulative
Rt[1] <- par$R0
for (tt in seq_len(horizon)) {
Rt[tt + 1] <- par$R0 * exp(-par$decline_delta * tt)
additional_so_far <- cumulative[tt] - par$initial_cumulative
saturation <- max(0, 1 - additional_so_far / par$K_additional)
mu <- (y[tt] * Rt[tt + 1] * saturation + par$importation_lambda) * par$ascertainment_q
mu <- max(mu, 0.02)
# R's rnbinom uses Var(Y)=mu + mu^2/size.
y[tt + 1] <- rnbinom(1, size = par$dispersion_k, mu = mu)
cumulative[tt + 1] <- cumulative[tt] + y[tt + 1]
}
tibble(
week = 0:horizon,
weekly_detections = y,
cumulative_detections = cumulative,
Rt = Rt
)
}
run_scenario <- function(par_row, n_sim = params$n_sim) {
par <- as.list(par_row)
map_dfr(seq_len(n_sim), function(i) {
simulate_one_path(par) %>%
mutate(simulation = i, scenario = par$scenario)
})
}
simulation_raw <- scenario_parameters %>%
split(.$scenario) %>%
map_dfr(run_scenario)
simulation_summary <- simulation_raw %>%
group_by(scenario, week) %>%
summarise(
weekly_median = median(weekly_detections),
weekly_q10 = quantile(weekly_detections, 0.10),
weekly_q90 = quantile(weekly_detections, 0.90),
cumulative_median = median(cumulative_detections),
cumulative_q025 = quantile(cumulative_detections, 0.025),
cumulative_q10 = quantile(cumulative_detections, 0.10),
cumulative_q90 = quantile(cumulative_detections, 0.90),
cumulative_q975 = quantile(cumulative_detections, 0.975),
Rt_median = median(Rt),
.groups = "drop"
)
write.csv(simulation_summary, "outputs/stochastic_scenario_summary.csv", row.names = FALSE)
p3 <- ggplot(simulation_summary, aes(x = week, y = weekly_median)) +
geom_ribbon(aes(ymin = weekly_q10, ymax = weekly_q90, fill = scenario),
alpha = 0.22, colour = NA) +
geom_line(aes(colour = scenario), linewidth = 1.0) +
geom_hline(yintercept = 0, linewidth = 0.4) +
facet_wrap(~scenario, ncol = 2, scales = "free_y") +
scale_x_continuous(breaks = 0:params$horizon_weeks) +
scale_y_continuous(expand = expansion(mult = c(0, 0.08))) +
guides(fill = "none", colour = "none") +
labs(
title = "Stochastic weekly H5 detection scenarios",
subtitle = paste0(
"Median and 10th–90th percentile envelopes from ",
format(params$n_sim, big.mark = ","), " simulations per scenario"
),
x = "Weeks after 6 August 2026",
y = "New confirmed detections per week",
caption = "Scenario simulations—not forecasts. Negative-binomial variation represents clustered detection and surveillance uncertainty."
) +
theme_surveillance()
p3
ggsave("figures/Figure_3_weekly_stochastic_scenarios.png", p3, width = 12, height = 8, dpi = 400)
p4 <- ggplot(simulation_summary, aes(x = week, y = cumulative_median, colour = scenario)) +
geom_ribbon(
aes(ymin = cumulative_q025, ymax = cumulative_q975, fill = scenario),
alpha = 0.12, colour = NA
) +
geom_ribbon(
aes(ymin = cumulative_q10, ymax = cumulative_q90, fill = scenario),
alpha = 0.20, colour = NA
) +
geom_line(linewidth = 1.15) +
geom_point(data = filter(simulation_summary, week == 0), size = 2.7) +
geom_hline(yintercept = national_h5_confirmed_detections,
linetype = "dashed", linewidth = 0.65, colour = "grey30") +
annotate("text", x = 0.3, y = national_h5_confirmed_detections + 8,
label = paste0("Observed baseline = ", national_h5_confirmed_detections),
hjust = 0, size = 3.6) +
scale_x_continuous(breaks = 0:params$horizon_weeks) +
scale_y_continuous(expand = expansion(mult = c(0.02, 0.08))) +
labs(
title = "Bounded cumulative H5 detection scenarios",
subtitle = "Dark ribbons: 80% interval; light ribbons: 95% interval",
x = "Weeks after 6 August 2026",
y = "Cumulative confirmed detections",
colour = "Scenario", fill = "Scenario",
caption = "The finite-capacity term limits short-horizon growth; it is a modelling safeguard, not a biological population ceiling."
) +
theme_surveillance() +
theme(legend.position = "bottom")
p4
ggsave("figures/Figure_4_cumulative_scenario_trajectories.png", p4, width = 11.5, height = 7, dpi = 400)
The following plot transforms the simulations into operational quantities. For each scenario and week, it estimates the probability that cumulative detections exceed selected surveillance thresholds. These thresholds are illustrative and should be replaced by thresholds agreed with wildlife-health and biosecurity authorities.
thresholds <- c(150, 175, 200, 250, 300, 400)
threshold_probability <- simulation_raw %>%
crossing(threshold = thresholds) %>%
group_by(scenario, week, threshold) %>%
summarise(
exceedance_probability = mean(cumulative_detections >= threshold),
.groups = "drop"
) %>%
mutate(
scenario_short = str_remove(scenario, ":.*$"),
threshold_label = paste0("≥", threshold)
)
p5 <- ggplot(
threshold_probability,
aes(x = week, y = threshold_label, fill = exceedance_probability)
) +
geom_tile(colour = "white", linewidth = 0.35) +
geom_text(
aes(label = ifelse(exceedance_probability >= 0.05,
sprintf("%.0f%%", 100 * exceedance_probability), "")),
size = 2.7
) +
facet_wrap(~scenario, ncol = 2) +
scale_fill_viridis_c(
option = "B", limits = c(0, 1),
breaks = c(0, 0.25, 0.5, 0.75, 1),
labels = scales::percent_format(accuracy = 1),
name = "Probability"
) +
scale_x_continuous(breaks = 0:params$horizon_weeks) +
labs(
title = "Probability of crossing cumulative surveillance thresholds",
subtitle = "Decision-oriented summary of stochastic uncertainty",
x = "Weeks after 6 August 2026",
y = "Cumulative-detection threshold",
caption = "Illustrative thresholds only. Operational triggers should incorporate species, location, mortality clustering and poultry proximity."
) +
theme_surveillance() +
theme(panel.grid = element_blank())
p5
ggsave("figures/Figure_5_threshold_exceedance_heatmap.png", p5, width = 12, height = 8, dpi = 400)
final_week_summary <- simulation_summary %>%
filter(week == params$horizon_weeks) %>%
transmute(
Scenario = scenario,
`Median cumulative detections` = round(cumulative_median),
`80% interval` = paste0(round(cumulative_q10), "–", round(cumulative_q90)),
`95% interval` = paste0(round(cumulative_q025), "–", round(cumulative_q975)),
`Median final-week detections` = round(weekly_median, 1),
`Median final-week R multiplier` = round(Rt_median, 3)
)
kable(
final_week_summary,
caption = paste0("Scenario outcomes at week ", params$horizon_weeks,
"; simulations are not forecasts")
)
| Scenario | Median cumulative detections | 80% interval | 95% interval | Median final-week detections | Median final-week R multiplier |
|---|---|---|---|---|---|
| A: Rapid containment | 132 | 124–179 | 123–226 | 0 | 0.372 |
| B: Sustained low-level detection | 143 | 126–230 | 124–318 | 0 | 0.604 |
| C: Moderate wildlife expansion | 149 | 127–272 | 124–401 | 0 | 0.629 |
| D: Adverse but bounded | 154 | 129–306 | 125–515 | 0 | 0.527 |
The figures are designed for prevention and monitoring:
A responsible operational trigger should not use counts alone. It should combine:
\[ \text{Priority}_{i,t}=w_1Z(\text{mortality cluster})+w_2Z(\text{new species})+ w_3Z(\text{poultry proximity})+w_4Z(\text{geographic spread})+w_5Z(\text{mammal involvement}). \]
Weights \(w_j\) should be set by epidemiologists, wildlife ecologists, veterinary authorities and public-health agencies.
state_data, national_h5_confirmed_detections,
and snapshot_date before publication.cat("Report generated with seed:", params$seed, "\n")
## Report generated with seed: 20260806
cat("Simulations per scenario:", params$n_sim, "\n")
## Simulations per scenario: 3000
cat("Simulation horizon:", params$horizon_weeks, "weeks\n\n")
## Simulation horizon: 12 weeks
sessionInfo()
## R version 4.4.3 (2025-02-28)
## Platform: x86_64-apple-darwin20
## Running under: macOS Sequoia 15.7.7
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Australia/Sydney
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] patchwork_1.3.2 ozmaps_0.4.5 sf_1.0-21 knitr_1.50
## [5] stringr_1.5.1 tibble_3.2.1 purrr_1.0.4 tidyr_1.3.1
## [9] dplyr_1.1.4 ggplot2_4.0.3
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.9 generics_0.1.3 class_7.3-23 KernSmooth_2.23-26
## [5] stringi_1.8.7 digest_0.6.37 magrittr_2.0.3 evaluate_1.0.3
## [9] grid_4.4.3 RColorBrewer_1.1-3 oz_1.0-22 fastmap_1.2.0
## [13] jsonlite_1.9.1 e1071_1.7-16 DBI_1.2.3 viridisLite_0.4.2
## [17] scales_1.4.0 textshaping_1.0.0 jquerylib_0.1.4 cli_3.6.4
## [21] rlang_1.3.0 units_0.8-7 withr_3.0.2 cachem_1.1.0
## [25] yaml_2.3.10 tools_4.4.3 vctrs_0.6.5 R6_2.6.1
## [29] proxy_0.4-27 lifecycle_1.0.4 classInt_0.4-11 ragg_1.4.0
## [33] pkgconfig_2.0.3 pillar_1.10.1 bslib_0.9.0 gtable_0.3.6
## [37] glue_1.8.0 Rcpp_1.0.14 systemfonts_1.3.2 xfun_0.52
## [41] tidyselect_1.2.1 rstudioapi_0.17.1 farver_2.1.2 htmltools_0.5.8.1
## [45] labeling_0.4.3 rmarkdown_2.29 compiler_4.4.3 S7_0.2.2