Did the 1 November 2025 Bulk Billing Changes Shift Access?

medicare
bulk-billing
health-policy
australia
An early reproducible look at whether expanded GP bulk billing incentives changed access patterns by age, socioeconomic area, geography and PHN.
Author

Aydin

Published

May 28, 2026

The 1 November 2025 bulk billing changes were designed to make it easier for general practices to bulk bill more patients. The narrowest measurable question is whether the share of GP attendances that were bulk billed moved after the change. The broader access question is harder: a higher bulk billing rate is not the same thing as more appointments, shorter waits or less delayed care.

This post treats access as two linked signals:

  1. the GP bulk billing rate, which is the main outcome;
  2. service-use and payment checks, so we do not confuse a billing-mix change with an access change.

The analysis is deliberately framed as an early signal. The policy began on 1 November 2025, the AIHW dashboard is updated quarterly, and AIHW treats the last 3 months of data as preliminary.

Code
ensure_aihw_tableau(refresh = FALSE)

manifest <- read_csv(file.path(raw_dir, "aihw_tableau_manifest.csv"), show_col_types = FALSE)

national <- read_tableau("national") |>
  standardise_rate_sheet() |>
  mutate(month_index = as.integer(interval(min(service_month), service_month) %/% months(1)))

age_rates <- read_tableau("age") |>
  standardise_rate_sheet("age_group_series_alias", "Age group") |>
  mutate(group = str_replace(group, " - ", "-"))

seifa_rates <- read_tableau("seifa") |>
  standardise_rate_sheet("seifa_series_alias", "Socioeconomic quintile")

benefits_raw <- read_tableau("benefits")
benefits <- benefits_raw |>
  transmute(
    service_month = as.Date(.data[[first_col(benefits_raw, "^month_service_month_value$")]]),
    series = .data[[first_col(benefits_raw, "^chart_4_series_alias$")]],
    value = as.numeric(.data[[first_col(benefits_raw, "^sum_pivot_field_values_value$")]])
  ) |>
  filter(!is.na(service_month), is.finite(value), !is.na(series)) |>
  distinct()

lga_latest <- read_tableau("latest_lga_map") |>
  transmute(
    state = state1_alias,
    lga_code = as.integer(lga_code21_alias),
    lga_name = attr_lga_name_alias,
    latest_bulk_billing_rate = as_number(sum_pivot_field_values_copy_alias),
    remoteness_area = attr_main_ra_alias,
    seifa_quintile = as.integer(attr_main_seifa_alias)
  ) |>
  filter(is.finite(latest_bulk_billing_rate), !is.na(lga_code))

phn_path <- download_if_missing(source_urls$phn_quarterly, file.path(raw_dir, "phn_bulk_billing.xlsx"))
sa3_path <- download_if_missing(source_urls$sa3_quarterly, file.path(raw_dir, "sa3_quarterly.xlsx"))
mmm_path <- download_if_missing(source_urls$mmm_quarterly, file.path(raw_dir, "mmm_quarterly.xlsx"))
pcst_path <- download_if_missing(source_urls$primary_care_service_type, file.path(raw_dir, "primary_care_service_type.xlsx"))
phn_lga_path <- download_if_missing(source_urls$phn_lga, file.path(raw_dir, "phn_lga_concordance.xlsx"))

phn_quarterly <- read_excel(phn_path, sheet = "By BTOS and PHN", skip = 4) |>
  clean_df() |>
  transmute(
    state,
    phn = primary_health_network,
    quarter,
    service_type = broad_type_of_service,
    phn_bulk_billing_rate = as.numeric(mbs_bulk_billing_rate)
  ) |>
  filter(service_type == "Total GP Non-Referred Attendances", !is.na(phn_bulk_billing_rate))

phn_age <- read_excel(phn_path, sheet = "GP NRA by PHN and Age Group", skip = 4) |>
  clean_df() |>
  select(state, phn = primary_health_network, quarter, contains("bulk_billing_rate")) |>
  pivot_longer(contains("bulk_billing_rate"), names_to = "age_group", values_to = "bulk_billing_rate") |>
  mutate(
    age_group = case_when(
      str_detect(age_group, "under_16") ~ "0-15",
      str_detect(age_group, "16_to_64") ~ "16-64",
      str_detect(age_group, "65") ~ "65+",
      str_detect(age_group, "all_ages") ~ "All ages",
      TRUE ~ age_group
    ),
    bulk_billing_rate = as.numeric(bulk_billing_rate)
  ) |>
  filter(!is.na(bulk_billing_rate))

sa3_visits_raw <- read_excel(sa3_path, sheet = "GP NRA by SA3", skip = 4) |>
  clean_df()

sa3_visits <- sa3_visits_raw |>
  transmute(
    state,
    sa3 = sa3,
    quarter,
    quarter_date = quarter_to_date(quarter),
    service_type = broad_type_of_service,
    services = as.numeric(services),
    benefits = as.numeric(benefits),
    bulk_billed_services = as.numeric(bulk_billed_services),
    patient_billed_services = as.numeric(patient_billed_services),
    bulk_billing_rate = as.numeric(mbs_bulk_billing_rate),
    avg_patient_contribution_patient_billed = as.numeric(avg_patient_contribution_per_service_out_of_hospital_patient_billed),
    avg_patient_contribution_total = as.numeric(avg_patient_contribution_per_service_out_of_hospital_total)
  ) |>
  filter(
    service_type == "Total GP Non-Referred Attendances",
    !is.na(quarter_date),
    is.finite(services),
    services > 0,
    !sa3 %in% c("Australia", state),
    !str_detect(state, regex("unknown|other territories", ignore_case = TRUE)),
    !str_detect(sa3, regex("^unknown|unknown state", ignore_case = TRUE))
  ) |>
  mutate(
    sa3_key = paste(state, sa3, sep = " - "),
    quarter_status = case_when(
      quarter_date < transition_quarter_date ~ "Pre-policy",
      quarter_date == transition_quarter_date ~ "Transition quarter",
      quarter_date >= clean_post_quarter_date ~ "Clean post-policy",
      TRUE ~ NA_character_
    )
  )

sa3_exposure <- sa3_visits |>
  filter(
    quarter_date >= max(quarter_date[quarter_date < transition_quarter_date], na.rm = TRUE) %m-% months(9),
    quarter_date < transition_quarter_date
  ) |>
  group_by(sa3_key) |>
  summarise(
    baseline_quarters = n_distinct(quarter_date),
    baseline_services = mean(services, na.rm = TRUE),
    pre_policy_bulk_billing_rate = weighted_rate(bulk_billed_services, services),
    pre_policy_patient_contribution = weighted.mean(
      avg_patient_contribution_patient_billed,
      w = patient_billed_services,
      na.rm = TRUE
    ),
    .groups = "drop"
  ) |>
  mutate(
    pre_policy_non_bulk_billed_share = 1 - pre_policy_bulk_billing_rate,
    pre_policy_gap_10pp = pre_policy_non_bulk_billed_share / 0.1
  )

sa3_panel <- sa3_visits |>
  left_join(sa3_exposure, by = "sa3_key") |>
  mutate(
    log_services = log1p(services),
    clean_post_policy = quarter_date >= clean_post_quarter_date,
    exposure_band = cut(
      pre_policy_non_bulk_billed_share,
      breaks = quantile(pre_policy_non_bulk_billed_share, probs = c(0, 1 / 3, 2 / 3, 1), na.rm = TRUE),
      include.lowest = TRUE,
      labels = c("Lower room to move", "Middle", "Higher room to move")
    ),
    rate_check = bulk_billed_services / services,
    rate_difference = bulk_billing_rate - rate_check
  ) |>
  filter(
    is.finite(pre_policy_gap_10pp),
    is.finite(baseline_services),
    baseline_quarters == 4
  )

mmm_visits <- read_excel(mmm_path, sheet = "All Services", skip = 4) |>
  clean_df() |>
  transmute(
    modified_monash = modified_monash_group,
    quarter,
    quarter_date = quarter_to_date(quarter),
    service_type = broad_type_of_service,
    services = as.numeric(services),
    bulk_billed_services = as.numeric(bulk_billed_services),
    patient_billed_services = as.numeric(patient_billed_services),
    bulk_billing_rate = as.numeric(mbs_bulk_billing_rate)
  ) |>
  filter(
    service_type == "Total GP Non-Referred Attendances",
    !is.na(quarter_date),
    is.finite(services),
    services > 0,
    !modified_monash %in% c("Australia")
  ) |>
  mutate(
    quarter_status = case_when(
      quarter_date < transition_quarter_date ~ "Pre-policy",
      quarter_date == transition_quarter_date ~ "Transition quarter",
      quarter_date >= clean_post_quarter_date ~ "Clean post-policy",
      TRUE ~ NA_character_
    )
  )

pcst_quarters <- read_excel(pcst_path, sheet = "State", skip = 4) |>
  clean_df() |>
  transmute(
    quarter,
    quarter_date = quarter_to_date(quarter),
    service_type = broad_type_of_service,
    primary_care_service_type,
    services = as.numeric(services)
  ) |>
  filter(!is.na(quarter_date), service_type == "Total GP Non-Referred Attendances")

phn_lga <- read_excel(phn_lga_path) |>
  clean_df() |>
  transmute(
    lga_code = as.integer(lga_code_2021),
    lga_name = lga_name_2021,
    phn_code = phn_code_2023,
    phn = phn_name_2023,
    ratio = as.numeric(ratio_from_to)
  )

lga_phn_latest <- lga_latest |>
  left_join(phn_lga, by = "lga_code", suffix = c("", "_phn")) |>
  group_by(phn) |>
  summarise(
    lgas = n_distinct(lga_code),
    latest_lga_mean_rate = weighted.mean(latest_bulk_billing_rate, w = coalesce(ratio, 1), na.rm = TRUE),
    .groups = "drop"
  ) |>
  filter(!is.na(phn))

Source Audit

The AIHW monthly dashboard is the main source because it is dated by month of service and has the policy date built into the time series. The Department’s PHN workbook is added as a quarterly cross-check because it has current PHN and age information, but it is not identical to the AIHW dashboard.

Code
audit <- tibble(
  source = c(
    "AIHW Tableau monthly dashboard",
    "Department quarterly PHN workbook",
    "Department quarterly SA3 workbook",
    "Department quarterly MMM workbook",
    "Department primary care service type workbook",
    "PHN 2023 to LGA 2021 concordance",
    "Modified Monash Model 2019 feature service"
  ),
  used_for = c(
    "Monthly national, age, SEIFA, benefits and latest LGA map",
    "Quarterly PHN and PHN by age cross-check",
    "Quarterly SA3 GP visit-volume panel",
    "Quarterly Modified Monash visit-volume cross-check",
    "Primary care service type source audit",
    "Approximate latest LGA-to-PHN grouping",
    "Documented as the correct MMM source; not spatially joined in this render"
  ),
  latest_period = c(
    format(max(national$service_month), "%B %Y"),
    phn_quarterly$quarter[[1]],
    sa3_panel |>
      filter(quarter_date == max(quarter_date, na.rm = TRUE)) |>
      pull(quarter) |>
      first(),
    mmm_visits |>
      filter(quarter_date == max(quarter_date, na.rm = TRUE)) |>
      pull(quarter) |>
      first(),
    pcst_quarters |>
      filter(quarter_date == max(quarter_date, na.rm = TRUE)) |>
      pull(quarter) |>
      first(),
    "2021 LGA to 2023 PHN",
    "2019 MMM polygons"
  ),
  caveat = c(
    "Last 3 months are preliminary and may revise",
    "Quarterly and processed-date style statistics may differ from AIHW",
    "December quarter 2025 is a transition quarter, not a clean post-policy period",
    "Coarser than SA3 but directly reports Modified Monash categories",
    "Used for source triangulation, not the main model",
    "Latest LGA means are not attendance-weighted",
    "A faithful MMM join needs spatial or mesh-block concordance work"
  )
)

audit |> kable()
source used_for latest_period caveat
AIHW Tableau monthly dashboard Monthly national, age, SEIFA, benefits and latest LGA map March 2026 Last 3 months are preliminary and may revise
Department quarterly PHN workbook Quarterly PHN and PHN by age cross-check 2025-26 Q3 (Mar Qtr) Quarterly and processed-date style statistics may differ from AIHW
Department quarterly SA3 workbook Quarterly SA3 GP visit-volume panel 2025-26 Q3 (Mar Qtr) December quarter 2025 is a transition quarter, not a clean post-policy period
Department quarterly MMM workbook Quarterly Modified Monash visit-volume cross-check 2025-26 Q3 (Mar Qtr) Coarser than SA3 but directly reports Modified Monash categories
Department primary care service type workbook Primary care service type source audit 2025-26 Q3 (Mar Qtr) Used for source triangulation, not the main model
PHN 2023 to LGA 2021 concordance Approximate latest LGA-to-PHN grouping 2021 LGA to 2023 PHN Latest LGA means are not attendance-weighted
Modified Monash Model 2019 feature service Documented as the correct MMM source; not spatially joined in this render 2019 MMM polygons A faithful MMM join needs spatial or mesh-block concordance work
Code
manifest |> kable()
label sheet rows columns path source_workbook
national GP attendances 1 506 22 aihw_tableau_national.csv HWE-97-MBS-GP-bulk-billing-data_20260526
age GP attendances 2 1518 21 aihw_tableau_age.csv HWE-97-MBS-GP-bulk-billing-data_20260526
seifa GP attendances 3 2530 22 aihw_tableau_seifa.csv HWE-97-MBS-GP-bulk-billing-data_20260526
benefits GP attendances 4 1518 24 aihw_tableau_benefits.csv HWE-97-MBS-GP-bulk-billing-data_20260526
latest_lga_map Map 515 8 aihw_tableau_latest_lga_map.csv HWE-97-MBS-GP-bulk-billing-data_20260526

The extract found 506 monthly observations from February 1984 to March 2026. There are 6 months in the immediate pre-window and 5 months after the policy start.

National Signal

The national monthly rate is the cleanest first test. A one-off step after November is suggestive, but the stronger signal is a step that is larger than the pre-existing trend and is visible in the 3-month average.

Code
national_summary <- window_compare(national) |>
  mutate(
    across(starts_with("rate"), \(x) percent(x, accuracy = 0.1)),
    change = percent(change, accuracy = 0.1)
  )

national_summary |> kable()
dimension group rate_May-Oct 2025 rate_Nov 2025 onward months_May-Oct 2025 months_Nov 2025 onward change
All All patients 78.1% 81.7% 6 5 3.6%
Code
national_plot <- national |>
  arrange(service_month) |>
  mutate(
    rolling_3_month = (bulk_billing_rate + lag(bulk_billing_rate, 1) + lag(bulk_billing_rate, 2)) / 3
  ) |>
  filter(service_month >= as.Date("2023-01-01")) |>
  pivot_longer(c(bulk_billing_rate, rolling_3_month), names_to = "measure", values_to = "rate")

ggplot(national_plot, aes(service_month, rate, colour = measure)) +
  geom_vline(xintercept = policy_date, linetype = "dashed", colour = "grey40") +
  geom_line(linewidth = 0.8, na.rm = TRUE) +
  scale_y_continuous(labels = percent) +
  scale_colour_manual(
    values = c(bulk_billing_rate = "#1b6ca8", rolling_3_month = "#c44e52"),
    labels = c(bulk_billing_rate = "Monthly", rolling_3_month = "Rolling 3 months")
  ) +
  labs(x = NULL, y = "Bulk billing rate", colour = NULL)

National GP bulk billing rate, with a 3-month rolling average.
Code
model_data <- national |>
  filter(service_month >= as.Date("2023-01-01")) |>
  mutate(
    post = service_month >= policy_date,
    months_since_policy = pmax(0, as.integer(interval(policy_date, service_month) %/% months(1))),
    month_index = as.integer(interval(min(service_month), service_month) %/% months(1))
  )

its_model <- lm(bulk_billing_rate ~ month_index + post + months_since_policy, data = model_data)

tidy(its_model, conf.int = TRUE) |>
  mutate(
    estimate = percent(estimate, accuracy = 0.01),
    conf.low = percent(conf.low, accuracy = 0.01),
    conf.high = percent(conf.high, accuracy = 0.01)
  ) |>
  kable()
term estimate std.error statistic p.value conf.low conf.high
(Intercept) 77.56% 0.0031667 244.9187761 0.0000000 76.92% 78.20%
month_index 0.01% 0.0001650 0.5360189 0.5953359 -0.02% 0.04%
postTRUE 3.24% 0.0080242 4.0401600 0.0002779 1.61% 4.87%
months_since_policy 0.30% 0.0029888 1.0059201 0.3213582 -0.31% 0.91%

The postTRUE coefficient is the immediate step after November 2025, after allowing for a linear pre/post time trend. This is useful as a screening estimate, not as a final causal estimate.

Who Moved?

The policy should matter most for patients who were not already covered by the old child/concession-heavy incentive structure. In the public AIHW age bands, that makes 16-64 the group to watch.

Code
age_compare <- window_compare(age_rates) |>
  mutate(
    across(starts_with("rate"), \(x) percent(x, accuracy = 0.1)),
    change = percent(change, accuracy = 0.1)
  )

age_compare |> kable()
dimension group rate_May-Oct 2025 rate_Nov 2025 onward months_May-Oct 2025 months_Nov 2025 onward change
Age group 16-64 69.6% 75.9% 6 5 6.2%
Age group 65+ 87.4% 88.4% 6 5 1.0%
Age group 0-15 90.6% 90.8% 6 5 0.3%
Code
age_rates |>
  filter(service_month >= as.Date("2023-01-01")) |>
  ggplot(aes(service_month, bulk_billing_rate, colour = group)) +
  geom_vline(xintercept = policy_date, linetype = "dashed", colour = "grey40") +
  geom_line(linewidth = 0.8) +
  scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Bulk billing rate", colour = "Age group")

Bulk billing rates by age group.
Code
seifa_compare <- window_compare(seifa_rates) |>
  mutate(
    across(starts_with("rate"), \(x) percent(x, accuracy = 0.1)),
    change = percent(change, accuracy = 0.1)
  )

seifa_compare |> kable()
dimension group rate_May-Oct 2025 rate_Nov 2025 onward months_May-Oct 2025 months_Nov 2025 onward change
Socioeconomic quintile 2 81.3% 85.6% 6 5 4.3%
Socioeconomic quintile 3 80.6% 84.6% 6 5 4.0%
Socioeconomic quintile 4 74.4% 78.1% 6 5 3.7%
Socioeconomic quintile 1 (lowest socioeconomic area) 88.4% 92.1% 6 5 3.7%
Socioeconomic quintile 5 (highest socioeconomic area) 67.2% 69.7% 6 5 2.5%
Code
seifa_rates |>
  filter(service_month >= as.Date("2023-01-01")) |>
  mutate(group = fct_relevel(group, "1 (lowest socioeconomic area)", "2", "3", "4", "5 (highest socioeconomic area)")) |>
  ggplot(aes(service_month, bulk_billing_rate, colour = group)) +
  geom_vline(xintercept = policy_date, linetype = "dashed", colour = "grey40") +
  geom_line(linewidth = 0.8) +
  scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Bulk billing rate", colour = "SEIFA quintile")

Bulk billing rates by socioeconomic quintile.

Payment Check

AIHW’s benefits measure includes Medicare benefits for bulk billed GP attendances and the matched bulk billing incentive items. It does not include the separate Bulk Billing Practice Incentive Program payments.

Code
benefits |>
  filter(service_month >= as.Date("2023-01-01")) |>
  ggplot(aes(service_month, value, colour = series)) +
  geom_vline(xintercept = policy_date, linetype = "dashed", colour = "grey40") +
  geom_line(linewidth = 0.8) +
  scale_y_continuous(labels = dollar) +
  labs(x = NULL, y = "Monthly benefit amount", colour = NULL)

Bulk billed GP attendance benefits and related bulk billing incentives.
Code
benefit_wide <- benefits |>
  filter(service_month >= as.Date("2025-05-01")) |>
  pivot_wider(names_from = series, values_from = value)

if (all(c("Bulk billing incentives", "Total benefit") %in% names(benefit_wide))) {
  benefit_wide |>
    transmute(
      service_month,
      incentive_share = `Bulk billing incentives` / `Total benefit`
    ) |>
    arrange(service_month) |>
    mutate(incentive_share = percent(incentive_share, accuracy = 0.1)) |>
    kable()
} else {
  tibble(note = "The expected incentive and total benefit series were not both present in the Tableau export.") |>
    kable()
}
service_month incentive_share
2025-05-01 18.7%
2025-06-01 18.5%
2025-07-01 19.2%
2025-08-01 19.1%
2025-09-01 19.2%
2025-10-01 18.9%
2025-11-01 27.1%
2025-12-01 27.7%
2026-01-01 26.9%
2026-02-01 26.6%
2026-03-01 27.0%

Geography

The AIHW dashboard gives a latest-month LGA map. That is not enough for an LGA-level before/after model, but it is enough to see where the current access surface is high or low. The PHN grouping below is an approximate LGA-to-PHN summary of latest LGA rates, not an attendance-weighted PHN statistic.

Code
bind_rows(
  lga_latest |>
    arrange(latest_bulk_billing_rate) |>
    slice_head(n = 10) |>
    mutate(rank_type = "Lowest latest LGA rates"),
  lga_latest |>
    arrange(desc(latest_bulk_billing_rate)) |>
    slice_head(n = 10) |>
    mutate(rank_type = "Highest latest LGA rates")
) |>
  transmute(
    rank_type,
    state,
    lga_name,
    remoteness_area,
    seifa_quintile,
    latest_bulk_billing_rate = percent(latest_bulk_billing_rate, accuracy = 0.1)
  ) |>
  kable()
rank_type state lga_name remoteness_area seifa_quintile latest_bulk_billing_rate
Lowest latest LGA rates WA Peppermint Grove 0 (Major cities) NA 47.7%
Lowest latest LGA rates WA Cottesloe 0 (Major cities) NA 47.9%
Lowest latest LGA rates WA Mosman Park 0 (Major cities) NA 51.6%
Lowest latest LGA rates WA Subiaco 0 (Major cities) NA 51.9%
Lowest latest LGA rates NSW Mosman 0 (Major cities) NA 53.4%
Lowest latest LGA rates WA Claremont 0 (Major cities) NA 54.3%
Lowest latest LGA rates WA Nedlands 0 (Major cities) NA 54.6%
Lowest latest LGA rates ACT Unincorporated ACT 0 (Major cities) NA 55.0%
Lowest latest LGA rates WA Cambridge 0 (Major cities) NA 55.4%
Lowest latest LGA rates WA East Fremantle 0 (Major cities) NA 55.4%
Highest latest LGA rates Queensland Torres Strait Island 4 (Very remote) NA 98.9%
Highest latest LGA rates Queensland Torres 4 (Very remote) NA 98.9%
Highest latest LGA rates NSW Fairfield 0 (Major cities) NA 98.9%
Highest latest LGA rates SA Whyalla 2 (Outer regional) NA 98.8%
Highest latest LGA rates NT West Daly 3 (Remote) NA 98.7%
Highest latest LGA rates Tasmania Flinders (Tas.) 4 (Very remote) 2 98.4%
Highest latest LGA rates NT MacDonnell 4 (Very remote) NA 98.3%
Highest latest LGA rates NT Central Desert 4 (Very remote) NA 98.3%
Highest latest LGA rates NSW Cumberland 0 (Major cities) NA 98.3%
Highest latest LGA rates NSW Campbelltown (NSW) 0 (Major cities) 2 98.2%
Code
lga_latest |>
  filter(!is.na(remoteness_area)) |>
  ggplot(aes(fct_reorder(remoteness_area, latest_bulk_billing_rate, .fun = median, na.rm = TRUE), latest_bulk_billing_rate)) +
  geom_boxplot(fill = "#d9e8f5", colour = "grey35", outlier.alpha = 0.35) +
  coord_flip() +
  scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Latest LGA bulk billing rate")

Latest LGA bulk billing rates by AIHW remoteness field.
Code
lga_phn_latest |>
  arrange(latest_lga_mean_rate) |>
  slice_head(n = 15) |>
  mutate(latest_lga_mean_rate = percent(latest_lga_mean_rate, accuracy = 0.1)) |>
  kable()
phn lgas latest_lga_mean_rate
Australian Capital Territory 1 55.0%
Perth North 18 61.9%
Northern Sydney 10 71.6%
Perth South 15 75.5%
Adelaide 18 75.8%
South Eastern Melbourne 14 77.9%
Central and Eastern Sydney 13 79.4%
Brisbane South 4 79.9%
Eastern Melbourne 13 80.2%
Central Queensland, Wide Bay, Sunshine Coast 11 82.4%
North Western Melbourne 13 82.8%
South Eastern NSW 12 83.8%
Country WA 98 83.9%
Hunter New England and Central Coast 23 83.9%
Brisbane North 5 84.1%

The Modified Monash Model source is available as a polygon feature service, but this render does not force a low-quality LGA-only MMM approximation. A proper MMM join should use a spatial intersection or a finer ABS geography concordance, then aggregate to the patient geography used by the Medicare data.

PHN Cross-Check

The Department’s PHN workbook is quarterly. It is useful as a second view of whether the post-policy shift is visible across PHNs, and whether the working-age pattern appears there too.

Code
phn_quarterly |>
  filter(phn %in% c("Australia", "NSW", "VIC", "QLD", "SA", "WA", "TAS", "ACT", "NT")) |>
  arrange(quarter, state, phn) |>
  tail(20) |>
  mutate(phn_bulk_billing_rate = percent(phn_bulk_billing_rate, accuracy = 0.1)) |>
  kable()
state phn quarter service_type phn_bulk_billing_rate
SA SA 2024-25 Q4 (Jun Qtr) Total GP Non-Referred Attendances 76.5%
WA WA 2024-25 Q4 (Jun Qtr) Total GP Non-Referred Attendances 71.9%
ACT ACT 2025-26 Q1 (Sep Qtr) Total GP Non-Referred Attendances 51.4%
Australia Australia 2025-26 Q1 (Sep Qtr) Total GP Non-Referred Attendances 77.6%
NSW NSW 2025-26 Q1 (Sep Qtr) Total GP Non-Referred Attendances 82.2%
NT NT 2025-26 Q1 (Sep Qtr) Total GP Non-Referred Attendances 76.6%
SA SA 2025-26 Q1 (Sep Qtr) Total GP Non-Referred Attendances 74.5%
WA WA 2025-26 Q1 (Sep Qtr) Total GP Non-Referred Attendances 69.9%
ACT ACT 2025-26 Q2 (Dec Qtr) Total GP Non-Referred Attendances 53.0%
Australia Australia 2025-26 Q2 (Dec Qtr) Total GP Non-Referred Attendances 80.1%
NSW NSW 2025-26 Q2 (Dec Qtr) Total GP Non-Referred Attendances 84.2%
NT NT 2025-26 Q2 (Dec Qtr) Total GP Non-Referred Attendances 85.6%
SA SA 2025-26 Q2 (Dec Qtr) Total GP Non-Referred Attendances 78.2%
WA WA 2025-26 Q2 (Dec Qtr) Total GP Non-Referred Attendances 72.2%
ACT ACT 2025-26 Q3 (Mar Qtr) Total GP Non-Referred Attendances 54.1%
Australia Australia 2025-26 Q3 (Mar Qtr) Total GP Non-Referred Attendances 81.9%
NSW NSW 2025-26 Q3 (Mar Qtr) Total GP Non-Referred Attendances 85.6%
NT NT 2025-26 Q3 (Mar Qtr) Total GP Non-Referred Attendances 89.8%
SA SA 2025-26 Q3 (Mar Qtr) Total GP Non-Referred Attendances 80.4%
WA WA 2025-26 Q3 (Mar Qtr) Total GP Non-Referred Attendances 74.0%
Code
latest_phn_quarter <- phn_quarterly$quarter[[1]]

phn_quarterly |>
  filter(quarter == latest_phn_quarter, !phn %in% c("Australia", state)) |>
  arrange(phn_bulk_billing_rate) |>
  slice_head(n = 15) |>
  transmute(
    quarter,
    state,
    phn,
    phn_bulk_billing_rate = percent(phn_bulk_billing_rate, accuracy = 0.1)
  ) |>
  kable()
quarter state phn phn_bulk_billing_rate
2025-26 Q3 (Mar Qtr) ACT Australian Capital Territory 54.1%
2025-26 Q3 (Mar Qtr) WA Perth North 68.0%
2025-26 Q3 (Mar Qtr) Qld Brisbane North 70.9%
2025-26 Q3 (Mar Qtr) NSW Northern Sydney 73.5%
2025-26 Q3 (Mar Qtr) WA Perth South 76.4%
2025-26 Q3 (Mar Qtr) NSW Hunter New England and Central Coast 77.8%
2025-26 Q3 (Mar Qtr) Tas Tasmania 78.0%
2025-26 Q3 (Mar Qtr) SA Adelaide 79.1%
2025-26 Q3 (Mar Qtr) NSW Central and Eastern Sydney 79.4%
2025-26 Q3 (Mar Qtr) Qld Brisbane South 79.6%
2025-26 Q3 (Mar Qtr) Qld Central Queensland, Wide Bay, Sunshine Coast 79.6%
2025-26 Q3 (Mar Qtr) Vic Eastern Melbourne 80.7%
2025-26 Q3 (Mar Qtr) WA Country WA 81.6%
2025-26 Q3 (Mar Qtr) Vic South Eastern Melbourne 81.7%
2025-26 Q3 (Mar Qtr) Qld Northern Queensland 82.0%
Code
phn_age |>
  filter(quarter == latest_phn_quarter, phn == "Australia", age_group != "All ages") |>
  transmute(
    quarter,
    age_group,
    bulk_billing_rate = percent(bulk_billing_rate, accuracy = 0.1)
  ) |>
  kable()
quarter age_group bulk_billing_rate
2025-26 Q3 (Mar Qtr) 0-15 90.8%
2025-26 Q3 (Mar Qtr) 16-64 76.2%
2025-26 Q3 (Mar Qtr) 65+ 88.5%

Visit Access

The next test is stricter: did areas with more room for the incentive to shift billing also see more GP attendances? For this, the lowest-level public panel in the current official releases is SA3 by quarter. The December quarter 2025 is mixed, because October was pre-policy and November-December were post-policy, so it is labelled as a transition quarter and excluded from the fixed-effects model.

Code
exposure_baseline_quarters <- sa3_panel |>
  filter(quarter_date < transition_quarter_date) |>
  distinct(quarter, quarter_date) |>
  arrange(desc(quarter_date)) |>
  slice_head(n = 4) |>
  arrange(quarter_date)

visit_access_audit <- tibble(
  metric = c(
    "Latest SA3 quarter",
    "Usable pre-policy quarters",
    "Exposure baseline window",
    "Transition quarter",
    "Visit model window starts",
    "Clean post-policy quarters",
    "SA3s in model panel",
    "Rows with missing model fields",
    "Largest rate validation difference"
  ),
  value = c(
    sa3_panel |>
      filter(quarter_date == max(quarter_date, na.rm = TRUE)) |>
      pull(quarter) |>
      first(),
    n_distinct(sa3_panel$quarter_date[sa3_panel$quarter_date < transition_quarter_date]),
    paste(exposure_baseline_quarters$quarter, collapse = " to "),
    "2025-26 Q2 (Dec Qtr), excluded from the model",
    sa3_panel |>
      filter(quarter_date == visit_model_start_quarter_date) |>
      pull(quarter) |>
      first(),
    n_distinct(sa3_panel$quarter_date[sa3_panel$quarter_date >= clean_post_quarter_date]),
    n_distinct(sa3_panel$sa3_key),
    sum(!complete.cases(sa3_panel[, c("services", "bulk_billed_services", "bulk_billing_rate", "pre_policy_gap_10pp")])),
    percent(max(abs(sa3_panel$rate_difference), na.rm = TRUE), accuracy = 0.001)
  )
)

visit_access_audit |> kable()
metric value
Latest SA3 quarter 2025-26 Q3 (Mar Qtr)
Usable pre-policy quarters 21
Exposure baseline window 2024-25 Q2 (Dec Qtr) to 2024-25 Q3 (Mar Qtr) to 2024-25 Q4 (Jun Qtr) to 2025-26 Q1 (Sep Qtr)
Transition quarter 2025-26 Q2 (Dec Qtr), excluded from the model
Visit model window starts 2023-24 Q1 (Sep Qtr)
Clean post-policy quarters 1
SA3s in model panel 332
Rows with missing model fields 0
Largest rate validation difference 0.000%
Code
sa3_national_visits <- sa3_panel |>
  group_by(quarter_date, quarter_status) |>
  summarise(services = sum(services, na.rm = TRUE), .groups = "drop")

ggplot(sa3_national_visits, aes(quarter_date, services)) +
  geom_vline(xintercept = transition_quarter_date, linetype = "dotted", colour = "grey45") +
  geom_vline(xintercept = clean_post_quarter_date, linetype = "dashed", colour = "grey25") +
  geom_line(colour = "#1b6ca8", linewidth = 0.8) +
  geom_point(aes(shape = quarter_status), colour = "#1b6ca8", size = 2) +
  scale_y_continuous(labels = comma) +
  labs(x = NULL, y = "GP NRA services", shape = NULL)

Quarterly GP non-referred attendance services summed across SA3s.
Code
sa3_growth <- sa3_panel |>
  mutate(window = case_when(
    quarter_date %in% exposure_baseline_quarters$quarter_date ~ "baseline",
    quarter_date >= clean_post_quarter_date ~ "post",
    TRUE ~ NA_character_
  )) |>
  filter(!is.na(window)) |>
  group_by(sa3_key, state, sa3, exposure_band, pre_policy_non_bulk_billed_share, baseline_services, window) |>
  summarise(
    services = mean(services, na.rm = TRUE),
    bb_rate = weighted_rate(bulk_billed_services, services),
    .groups = "drop"
  ) |>
  pivot_wider(names_from = window, values_from = c(services, bb_rate)) |>
  mutate(
    visit_growth = services_post / services_baseline - 1,
    bulk_billing_rate_change = bb_rate_post - bb_rate_baseline,
    exposure_label = as.character(exposure_band)
  ) |>
  filter(is.finite(visit_growth), is.finite(pre_policy_non_bulk_billed_share))

ggplot(sa3_growth, aes(visit_growth)) +
  geom_histogram(bins = 30, fill = "#d9e8f5", colour = "white") +
  geom_vline(xintercept = 0, linetype = "dashed", colour = "grey35") +
  scale_x_continuous(labels = percent) +
  labs(x = "Visit growth", y = "SA3s")

SA3 visit growth from the pre-policy exposure window to clean post-policy quarters.
Code
band_trends <- sa3_panel |>
  group_by(exposure_band, quarter_date, quarter_status) |>
  summarise(services = sum(services, na.rm = TRUE), .groups = "drop") |>
  group_by(exposure_band) |>
  mutate(
    baseline_services = mean(services[quarter_date %in% exposure_baseline_quarters$quarter_date], na.rm = TRUE),
    visit_index = services / baseline_services
  ) |>
  ungroup()

ggplot(band_trends, aes(quarter_date, visit_index, colour = exposure_band)) +
  geom_vline(xintercept = transition_quarter_date, linetype = "dotted", colour = "grey45") +
  geom_vline(xintercept = clean_post_quarter_date, linetype = "dashed", colour = "grey25") +
  geom_line(linewidth = 0.8, na.rm = TRUE) +
  scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Visit volume index", colour = "Pre-policy non-bulk-billed share")

Longer-run quarterly GP visit volume indexed to the four-quarter pre-policy exposure window.

The 2021-22 peak is real in the official service counts, not an indexing artefact. It lines up with the pandemic-era disruption and rebound in GP claiming, so it is a poor baseline for the 2025 policy question. The chart below keeps the same index but starts after that shock; the visit-volume models also use this flatter window.

Code
band_trends |>
  filter(quarter_date >= visit_model_start_quarter_date) |>
  ggplot(aes(quarter_date, visit_index, colour = exposure_band)) +
  geom_vline(xintercept = transition_quarter_date, linetype = "dotted", colour = "grey45") +
  geom_vline(xintercept = clean_post_quarter_date, linetype = "dashed", colour = "grey25") +
  geom_line(linewidth = 0.8, na.rm = TRUE) +
  scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Visit volume index", colour = "Pre-policy non-bulk-billed share")

Post-spike quarterly GP visit volume indexed to the four-quarter pre-policy exposure window.
Code
ggplot(sa3_growth, aes(pre_policy_non_bulk_billed_share, visit_growth)) +
  geom_hline(yintercept = 0, linetype = "dashed", colour = "grey50") +
  geom_point(aes(size = baseline_services), alpha = 0.35, colour = "#1b6ca8") +
  geom_smooth(aes(weight = baseline_services), method = "lm", se = TRUE, colour = "#c44e52") +
  scale_x_continuous(labels = percent) +
  scale_y_continuous(labels = percent) +
  scale_size_continuous(labels = comma, range = c(1, 7)) +
  labs(
    x = "Pre-policy non-bulk-billed share",
    y = "Visit growth",
    size = "Baseline services"
  )

Areas with lower pre-policy bulk billing had more room for the incentives to shift billing behaviour.
Code
ggplot(sa3_growth, aes(bulk_billing_rate_change, visit_growth)) +
  geom_hline(yintercept = 0, linetype = "dashed", colour = "grey50") +
  geom_vline(xintercept = 0, linetype = "dashed", colour = "grey50") +
  geom_point(aes(size = baseline_services), alpha = 0.35, colour = "#1b6ca8") +
  geom_smooth(aes(weight = baseline_services), method = "lm", se = TRUE, colour = "#c44e52") +
  scale_x_continuous(labels = percent) +
  scale_y_continuous(labels = percent) +
  scale_size_continuous(labels = comma, range = c(1, 7)) +
  labs(
    x = "Observed bulk billing rate change",
    y = "Visit growth",
    size = "Baseline services"
  )

Observed SA3 bulk billing rate change versus visit growth.
Code
access_model_panel <- sa3_panel |>
  filter(
    quarter_date >= visit_model_start_quarter_date,
    quarter_date != transition_quarter_date
  ) |>
  mutate(post_x_gap_10pp = as.integer(clean_post_policy) * pre_policy_gap_10pp)

fit_access_model <- function(outcome, label, outcome_type) {
  model_data <- access_model_panel |>
    filter(
      is.finite(.data[[outcome]]),
      is.finite(post_x_gap_10pp),
      is.finite(baseline_services),
      baseline_services > 0
    )

  model <- lm(
    reformulate(
      c("factor(sa3_key)", "factor(quarter_date)", "post_x_gap_10pp"),
      response = outcome
    ),
    data = model_data,
    weights = baseline_services
  )

  tidy(model, conf.int = TRUE) |>
    filter(term == "post_x_gap_10pp") |>
    transmute(
      outcome = label,
      interpretation = "Clean post-policy change per 10 percentage point higher pre-policy non-bulk-billed share",
      estimate = format_model_estimate(estimate, outcome_type),
      conf_low = format_model_estimate(conf.low, outcome_type),
      conf_high = format_model_estimate(conf.high, outcome_type),
      rows = nrow(model_data),
      sa3s = n_distinct(model_data$sa3_key)
    )
}

access_model_summary <- bind_rows(
  fit_access_model("log_services", "GP visit volume", "log"),
  fit_access_model("bulk_billing_rate", "Bulk billing rate", "rate"),
  fit_access_model("avg_patient_contribution_patient_billed", "Patient contribution among patient-billed services", "dollar")
)

access_model_summary |> kable()
outcome interpretation estimate conf_low conf_high rows sa3s
GP visit volume Clean post-policy change per 10 percentage point higher pre-policy non-bulk-billed share -0.4% -0.6% -0.1% 3320 332
Bulk billing rate Clean post-policy change per 10 percentage point higher pre-policy non-bulk-billed share -0.43% -0.59% -0.27% 3320 332
Patient contribution among patient-billed services Clean post-policy change per 10 percentage point higher pre-policy non-bulk-billed share -$1.11 -$1.30 -$0.93 3320 332

The first row is the key access test. A positive estimate would mean higher-exposure areas had faster visit-volume growth after the clean post-policy quarter. The second and third rows are mechanism checks: the policy story is stronger if the same high-exposure areas also show larger bulk billing rate gains or lower patient contributions.

Code
reference_quarter <- max(access_model_panel$quarter_date[access_model_panel$quarter_date < transition_quarter_date], na.rm = TRUE)

event_study_data <- access_model_panel |>
  select(sa3_key, quarter_date, log_services, pre_policy_gap_10pp, baseline_services) |>
  left_join(
    access_model_panel |>
      filter(quarter_date == reference_quarter) |>
      select(sa3_key, reference_log_services = log_services),
    by = "sa3_key"
  ) |>
  mutate(delta_log_services = log_services - reference_log_services) |>
  filter(is.finite(delta_log_services), is.finite(pre_policy_gap_10pp), is.finite(baseline_services))

event_study <- event_study_data |>
  group_by(quarter_date) |>
  group_modify(\(.x, .y) {
    tidy(
      lm(delta_log_services ~ pre_policy_gap_10pp, data = .x, weights = baseline_services),
      conf.int = TRUE
    ) |>
      filter(term == "pre_policy_gap_10pp")
  }) |>
  ungroup() |>
  mutate(
    estimate_pct = exp(estimate) - 1,
    conf_low_pct = exp(conf.low) - 1,
    conf_high_pct = exp(conf.high) - 1,
    quarter_status = case_when(
      quarter_date < transition_quarter_date ~ "Pre-policy",
      quarter_date >= clean_post_quarter_date ~ "Clean post-policy",
      TRUE ~ "Transition quarter"
    )
  )

ggplot(event_study, aes(quarter_date, estimate_pct)) +
  geom_hline(yintercept = 0, colour = "grey55") +
  geom_vline(xintercept = transition_quarter_date, linetype = "dotted", colour = "grey45") +
  geom_vline(xintercept = clean_post_quarter_date, linetype = "dashed", colour = "grey25") +
  geom_ribbon(aes(ymin = conf_low_pct, ymax = conf_high_pct), fill = "#d9e8f5", alpha = 0.7) +
  geom_line(colour = "#1b6ca8", linewidth = 0.8) +
  geom_point(aes(shape = quarter_status), colour = "#1b6ca8", size = 2) +
  scale_y_continuous(labels = percent) +
  labs(
    x = NULL,
    y = "Differential visit change per 10pp exposure",
    shape = NULL
  )

Differential SA3 visit-volume change by pre-policy non-bulk-billed share, relative to September quarter 2025.
Code
mmm_trends <- mmm_visits |>
  filter(modified_monash != "Unknown") |>
  group_by(modified_monash, quarter_date, quarter_status) |>
  summarise(services = sum(services, na.rm = TRUE), .groups = "drop") |>
  group_by(modified_monash) |>
  mutate(
    baseline_services = mean(services[quarter_date %in% exposure_baseline_quarters$quarter_date], na.rm = TRUE),
    visit_index = services / baseline_services
  ) |>
  ungroup()

ggplot(mmm_trends, aes(quarter_date, visit_index, colour = modified_monash)) +
  geom_vline(xintercept = transition_quarter_date, linetype = "dotted", colour = "grey45") +
  geom_vline(xintercept = clean_post_quarter_date, linetype = "dashed", colour = "grey25") +
  geom_line(linewidth = 0.8, na.rm = TRUE) +
  scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Visit volume index", colour = "Modified Monash")

Modified Monash GP visit volume indexed to the four-quarter pre-policy exposure window.
Code
unknown_mmm <- mmm_visits |>
  group_by(quarter, quarter_date) |>
  summarise(
    total_services = sum(services, na.rm = TRUE),
    unknown_services = sum(services[modified_monash == "Unknown"], na.rm = TRUE),
    unknown_share = unknown_services / total_services,
    .groups = "drop"
  ) |>
  filter(quarter_date >= as.Date("2023-09-01")) |>
  arrange(quarter_date)

unknown_mmm |>
  mutate(
    total_services = comma(total_services),
    unknown_services = comma(unknown_services),
    unknown_share = percent(unknown_share, accuracy = 0.001)
  ) |>
  kable()
quarter quarter_date total_services unknown_services unknown_share
2023-24 Q1 (Sep Qtr) 2023-09-01 41,611,756 419 0.001%
2023-24 Q2 (Dec Qtr) 2023-12-01 39,169,904 402 0.001%
2023-24 Q3 (Mar Qtr) 2024-03-01 39,909,885 861 0.002%
2023-24 Q4 (Jun Qtr) 2024-06-01 43,838,981 9,111 0.021%
2024-25 Q1 (Sep Qtr) 2024-09-01 43,278,054 8,829 0.020%
2024-25 Q2 (Dec Qtr) 2024-12-01 40,206,213 8,403 0.021%
2024-25 Q3 (Mar Qtr) 2025-03-01 40,481,593 8,076 0.020%
2024-25 Q4 (Jun Qtr) 2025-06-01 43,733,040 7,893 0.018%
2025-26 Q1 (Sep Qtr) 2025-09-01 42,347,110 7,466 0.018%
2025-26 Q2 (Dec Qtr) 2025-12-01 39,768,686 7,030 0.018%
2025-26 Q3 (Mar Qtr) 2026-03-01 39,968,536 6,986 0.017%

The Unknown Modified Monash category is excluded from the indexed MMM chart because it is tiny in absolute terms and appears to have a classification break around the June quarter 2024. It rises from hundreds of services per quarter to roughly 7,000-9,000, but still remains around 0.021% of GP non-referred attendances. That makes it visually noisy as an index and not a meaningful access geography.

Code
sa3_growth |>
  group_by(exposure_band) |>
  summarise(
    sa3s = n(),
    median_pre_policy_non_bulk_billed_share = median(pre_policy_non_bulk_billed_share, na.rm = TRUE),
    median_visit_growth = median(visit_growth, na.rm = TRUE),
    weighted_visit_growth = weighted.mean(visit_growth, w = baseline_services, na.rm = TRUE),
    .groups = "drop"
  ) |>
  mutate(
    median_pre_policy_non_bulk_billed_share = percent(median_pre_policy_non_bulk_billed_share, accuracy = 0.1),
    median_visit_growth = percent(median_visit_growth, accuracy = 0.1),
    weighted_visit_growth = percent(weighted_visit_growth, accuracy = 0.1)
  ) |>
  kable()
exposure_band sa3s median_pre_policy_non_bulk_billed_share median_visit_growth weighted_visit_growth
Lower room to move 111 14.7% -3.8% -3.8%
Middle 111 22.9% -4.5% -3.8%
Higher room to move 110 34.3% -5.3% -5.0%

Early Answer

Code
national_delta <- window_compare(national)$change[[1]]
age_delta <- window_compare(age_rates) |>
  select(group, change) |>
  arrange(desc(change))
seifa_delta <- window_compare(seifa_rates) |>
  select(group, change) |>
  arrange(desc(change))
visit_access_estimate <- access_model_summary |>
  filter(outcome == "GP visit volume") |>
  pull(estimate) |>
  first()

tibble(
  question = c(
    "Did the national rate move after 1 November 2025?",
    "Was the largest age signal in working-age adults?",
    "Did lower and higher socioeconomic areas both move?",
    "Did higher-exposure SA3s also show more GP visits?",
    "Can we call this access rather than billing?"
  ),
  early_read = c(
    paste0("National before/after change: ", percent(national_delta, accuracy = 0.1), "."),
    paste0("Largest age-band change: ", age_delta$group[[1]], " at ", percent(age_delta$change[[1]], accuracy = 0.1), "."),
    paste0("Largest SEIFA change: ", seifa_delta$group[[1]], " at ", percent(seifa_delta$change[[1]], accuracy = 0.1), "."),
    paste0("SA3 fixed-effects visit estimate: ", visit_access_estimate, " per 10 percentage point higher pre-policy non-bulk-billed share."),
    "Not fully. The data show billing and service-use/payment signals, not unmet need or appointment availability."
  )
) |>
  kable()
question early_read
Did the national rate move after 1 November 2025? National before/after change: 3.6%.
Was the largest age signal in working-age adults? Largest age-band change: 16-64 at 6.2%.
Did lower and higher socioeconomic areas both move? Largest SEIFA change: 2 at 4.3%.
Did higher-exposure SA3s also show more GP visits? SA3 fixed-effects visit estimate: -0.4% per 10 percentage point higher pre-policy non-bulk-billed share.
Can we call this access rather than billing? Not fully. The data show billing and service-use/payment signals, not unmet need or appointment availability.

The early read is that the 1 November 2025 changes should be treated as having shifted measured bulk billing access if the post-November lift is visible nationally, strongest for 16-64, and not limited to places that already had high bulk billing. The broader access case is stronger if the SA3 visit-volume model shows higher-exposure areas also gaining visits after the clean post-policy quarter. The evidence is weaker if the rise is small relative to normal monthly volatility, if it does not persist in the rolling average, or if service-use checks deteriorate.

Caveats

  • AIHW’s monthly dashboard is based on date of service; Department Medicare statistics can differ because official Medicare statistics are generally processed-date based.
  • AIHW notes that the last 3 months of monthly dashboard data are preliminary.
  • Bulk billing rate is a claim-level access proxy, not a direct measure of unmet need, waiting time or clinic availability.
  • The AIHW benefits series excludes Bulk Billing Practice Incentive Program payments.
  • LGA statistics are based on patient location. People can travel outside their LGA for care.
  • The SA3 visit model uses service counts rather than per-capita rates. SA3 fixed effects absorb stable differences between areas, but population growth can still affect visit volumes.
  • The December quarter 2025 is excluded from the visit model because it mixes one pre-policy month with two post-policy months.
  • PHN and LGA summaries in this post mix monthly AIHW dashboard data and quarterly Department statistics; they are used as triangulation, not as one single modelled dataset.
  • Modified Monash should be added with a proper spatial or fine-geography concordance before making MMM-specific claims.