# =============================================================================
# 03_render_public_reports.R
# Renders static HTML public-data reports for Dorset HealthCare (RDY) demo.
# Uses public aggregate data from site/public-data/processed/ only.
# Not an official Dorset HealthCare report — human review required.
# =============================================================================

args <- commandArgs(trailingOnly = FALSE)
script_path <- sub("--file=", "", args[grep("--file=", args)])
if (length(script_path) > 0) {
  script_dir <- dirname(normalizePath(script_path))
} else {
  script_dir <- getwd()
}

site_dir <- normalizePath(file.path(script_dir, ".."))
reports_dir <- file.path(site_dir, "reports")
public_dir <- file.path(site_dir, "public-data")
processed_dir <- file.path(public_dir, "processed")
metadata_dir <- file.path(public_dir, "metadata")
dir.create(reports_dir, recursive = TRUE, showWarnings = FALSE)

# --- Helpers -----------------------------------------------------------------

esc <- function(x) {
  x <- as.character(x)
  x <- gsub("&", "&amp;", x, fixed = TRUE)
  x <- gsub("<", "&lt;", x, fixed = TRUE)
  x <- gsub(">", "&gt;", x, fixed = TRUE)
  x <- gsub("\"", "&quot;", x, fixed = TRUE)
  x
}

load_demo <- function(filename, required = FALSE) {
  path <- file.path(processed_dir, filename)
  if (!file.exists(path)) {
    msg <- paste("Missing demo CSV:", filename)
    if (required) warning(msg)
    return(NULL)
  }
  tryCatch(
    read.csv(path, stringsAsFactors = FALSE, check.names = FALSE),
    error = function(e) {
      warning("Could not read ", filename, ": ", conditionMessage(e))
      NULL
    }
  )
}

load_rdy_glob <- function(pattern) {
  files <- list.files(processed_dir, pattern = pattern, full.names = TRUE)
  if (length(files) == 0) return(NULL)
  tryCatch(read.csv(files[1], stringsAsFactors = FALSE, check.names = FALSE),
           error = function(e) NULL)
}

load_trend_file <- function(filename) {
  path <- file.path(processed_dir, filename)
  if (!file.exists(path)) return(NULL)
  tryCatch(read.csv(path, stringsAsFactors = FALSE, check.names = FALSE),
           error = function(e) NULL)
}

parse_stacked_period <- function(x) {
  as.Date(vapply(x, function(xi) {
    xi <- trimws(as.character(xi))
    d <- parse_period_start(xi)
    if (length(d) > 0 && !is.na(d)) return(unclass(d))
    m <- regmatches(xi, regexec("MSitAE-([A-Za-z]+)-([0-9]{4})", xi, ignore.case = TRUE))[[1]]
    if (length(m) == 3) {
      month_num <- match(tolower(m[2]), tolower(month.name))
      if (!is.na(month_num)) {
        return(unclass(as.Date(sprintf("%04d-%02d-01", as.integer(m[3]), month_num))))
      }
    }
    m2 <- regmatches(xi, regexec("DM01-([A-Za-z]+)-([0-9]{4})", xi, ignore.case = TRUE))[[1]]
    if (length(m2) == 3) {
      month_num <- match(tolower(m2[2]), tolower(month.name))
      if (!is.na(month_num)) {
        return(unclass(as.Date(sprintf("%04d-%02d-01", as.integer(m2[3]), month_num))))
      }
    }
    m3 <- regmatches(xi, regexec("([a-z]+)-([0-9]{4})$", xi, ignore.case = TRUE))[[1]]
    if (length(m3) == 3) {
      month_num <- match(tolower(m3[2]), tolower(month.name))
      if (!is.na(month_num)) {
        return(unclass(as.Date(sprintf("%04d-%02d-01", as.integer(m3[3]), month_num))))
      }
    }
    m_nof <- regmatches(xi, regexec("Q([1-4])\\s+([0-9]{4})/([0-9]{2})", xi, ignore.case = TRUE))[[1]]
    if (length(m_nof) == 4) {
      qn <- as.integer(m_nof[2])
      fy <- as.integer(m_nof[3])
      if (qn == 4L) {
        return(unclass(as.Date(sprintf("%04d-%02d-01", fy + 1L, 1L))))
      }
      return(unclass(as.Date(sprintf("%04d-%02d-01", fy, (qn - 1L) * 3L + 4L))))
    }
    m4 <- regmatches(xi, regexec("Q([1-4])\\s+([0-9]{4})", xi, ignore.case = TRUE))[[1]]
    if (length(m4) == 3) {
      qn <- as.integer(m4[2])
      yr <- as.integer(m4[3])
      return(unclass(as.Date(sprintf("%04d-%02d-01", yr, (qn - 1L) * 3L + 1L))))
    }
    NA_real_
  }, numeric(1)), origin = "1970-01-01")
}

extract_stacked_trend <- function(df, measure_id = NULL, measure_name = NULL, label = NULL) {
  if (is.null(df) || nrow(df) == 0) return(list(available = FALSE, n_periods = 0L))
  sub <- df
  if (!is.null(measure_id) && "measure_id" %in% names(sub)) {
    sub <- sub[trimws(sub$measure_id) == measure_id, , drop = FALSE]
  }
  if (!is.null(measure_name) && "measure_name" %in% names(sub)) {
    sub <- sub[trimws(sub$measure_name) == measure_name, , drop = FALSE]
  }
  if (nrow(sub) == 0) {
    return(list(available = FALSE, n_periods = 0L, measure_label = label %||% ""))
  }
  sub$period <- parse_stacked_period(sub$reporting_period_start)
  if (all(is.na(sub$period)) && "publication_period" %in% names(sub)) {
    sub$period <- parse_stacked_period(sub$publication_period)
  }
  sub$value <- to_num(sub$metric_value)
  sub <- sub[!is.na(sub$value), , drop = FALSE]
  if (nrow(sub) == 0) return(list(available = FALSE, n_periods = 0L))
  if (!all(is.na(sub$period))) {
    sub <- sub[!is.na(sub$period), , drop = FALSE]
    agg <- stats::aggregate(value ~ period, data = sub, FUN = function(v) v[1])
    ts_sub <- agg[order(agg$period), , drop = FALSE]
  } else {
    sub <- sub[order(sub$publication_period), , drop = FALSE]
    ts_sub <- data.frame(
      period = seq_len(nrow(sub)),
      value = sub$value,
      stringsAsFactors = FALSE
    )
  }
  measure_label <- if (!is.null(label) && nzchar(label)) {
    label
  } else if ("measure_name" %in% names(sub) && nzchar(sub$measure_name[1])) {
    sub$measure_name[1]
  } else {
    as.character(measure_id)
  }
  compute_period_trend(ts_sub, measure_label)
}

`%||%` <- function(a, b) if (!is.null(a) && length(a) > 0 && !all(is.na(a)) && nzchar(as.character(a)[1])) a else b

trends_from_stacked <- function(df, measures) {
  if (is.null(df) || nrow(df) == 0) return(list())
  lapply(measures, function(m) {
    extract_stacked_trend(
      df,
      measure_id = m$id,
      measure_name = m$name,
      label = m$label
    )
  })
}

read_note <- function(source_id) {
  path <- file.path(metadata_dir, paste0("filter_note_", source_id, ".txt"))
  if (!file.exists(path)) return("")
  paste(readLines(path, warn = FALSE), collapse = "\n")
}

NOF_VALUE_SOURCE <- "NHS England published field in NOF data CSV — not recalculated by this demo"
NOF_COMPARATOR_GROUP <- "Mental health and community trusts in NHS Oversight Framework data file for this Quarter + Metric_ID + Reporting_date"
NOF_RANK_DIRECTION <- paste(
  "NHS England published rank (1 = best in peer group per NOF methodology).",
  "Polarity not independently verified in this demo — confirm against NHS England metric definitions."
)
RDY_FILTER_TEXT <- "Trust_code=RDY OR trust name matches Dorset HealthCare / Dorset Healthcare University NHS Foundation Trust"

path_for_display <- function(p) {
  if (is.null(p) || length(p) == 0 || is.na(p) || trimws(p) == "") return("")
  p <- normalizePath(p, winslash = "/", mustWork = FALSE)
  site_norm <- normalizePath(site_dir, winslash = "/")
  sub(paste0("^", gsub("([.|()\\^{}+$*?]|\\[|\\])", "\\\\\\1", site_norm), "/?"), "site/", p)
}

split_register_paths <- function(s) {
  if (is.null(s) || length(s) == 0 || is.na(s) || trimws(s) == "") return(character())
  trimws(strsplit(as.character(s), ";")[[1]])
}

read_register_row <- function(source_id) {
  reg_path <- file.path(public_dir, "DATA_SOURCE_REGISTER.csv")
  if (!file.exists(reg_path)) return(NULL)
  reg <- tryCatch(
    read.csv(reg_path, stringsAsFactors = FALSE, check.names = FALSE),
    error = function(e) NULL
  )
  if (is.null(reg)) return(NULL)
  row <- reg[reg$source_id == source_id, , drop = FALSE]
  if (nrow(row) == 0) return(NULL)
  row[1, , drop = FALSE]
}

load_nof_full_rdy <- function() {
  files <- list.files(processed_dir, pattern = "^rdy_nof_mh_community.*-data\\.csv$", full.names = TRUE)
  if (length(files) == 0) return(NULL)
  pick <- files[which.max(vapply(files, function(f) {
    bn <- tolower(basename(f))
    m <- regmatches(bn, regexec("q([1-4])[_-]([0-9]{4})", bn))[[1]]
    if (length(m) != 3) return(0)
    as.numeric(m[3]) * 10 + as.numeric(m[2])
  }, numeric(1)))]
  tryCatch(read.csv(pick, stringsAsFactors = FALSE, check.names = FALSE), error = function(e) NULL)
}

load_nof_raw <- function() {
  files <- list.files(file.path(public_dir, "raw"), pattern = "nof_mh_community.*-data\\.csv$", full.names = TRUE)
  if (length(files) == 0) return(NULL)
  tryCatch(read.csv(files[1], stringsAsFactors = FALSE, check.names = FALSE), error = function(e) NULL)
}

is_nof_raw_metric <- function(id) {
  grepl("^OF0[0-9]{3}$", trimws(as.character(id)))
}

nof_quarter_sort_key <- function(q) {
  q <- trimws(as.character(q))
  m <- regmatches(q, regexpr("Q[1-4]", q))
  if (length(m) == 0) return(0)
  qn <- as.integer(sub("Q", "", m))
  y <- suppressWarnings(as.integer(sub(".*([0-9]{4}).*", "\\1", q)))
  if (is.na(y)) y <- 0
  y * 10 + qn
}

nof_latest_quarter <- function(df) {
  qs <- unique(trimws(df$Quarter))
  qs[which.max(vapply(qs, nof_quarter_sort_key, numeric(1)))]
}

count_comparator_rows <- function(raw_df, quarter, metric_id, reporting_date) {
  if (is.null(raw_df) || nrow(raw_df) == 0) return(NA_integer_)
  sub <- raw_df[
    trimws(raw_df$Quarter) == trimws(quarter) &
      trimws(raw_df$Metric_ID) == trimws(metric_id) &
      trimws(raw_df$Reporting_date) == trimws(reporting_date),
    ,
    drop = FALSE
  ]
  as.integer(sum(!is.na(to_num(sub$Value))))
}

build_nof_audit_df <- function(display_rows, raw_df, reg_row) {
  raw_paths <- split_register_paths(if (!is.null(reg_row)) reg_row$downloaded_file_path else "")
  raw_data_path <- raw_paths[grepl("-data\\.csv", raw_paths, ignore.case = TRUE)][1]
  if (length(raw_data_path) == 0 || is.na(raw_data_path)) raw_data_path <- raw_paths[1]

  proc_paths <- split_register_paths(if (!is.null(reg_row)) reg_row$processed_file_path else "")
  proc_data_path <- proc_paths[grepl("-data\\.csv", proc_paths, ignore.case = TRUE)][1]
  if (length(proc_data_path) == 0 || is.na(proc_data_path)) proc_data_path <- proc_paths[1]

  source_url <- if (!is.null(reg_row)) reg_row$source_url else ""

  rows <- lapply(seq_len(nrow(display_rows)), function(i) {
    r <- display_rows[i, , drop = FALSE]
    comp_n <- count_comparator_rows(raw_df, r$Quarter, r$Metric_ID, r$Reporting_date)
    data.frame(
      Metric_ID = r$Metric_ID,
      Metric_description = r$Metric_description,
      Quarter = r$Quarter,
      Reporting_date = r$Reporting_date,
      Source_dataset = "NHS Oversight Framework MH/community trust data",
      Raw_source_file = path_for_display(raw_data_path),
      Raw_source_url = source_url,
      Processed_extract_path = path_for_display(proc_data_path),
      Demo_csv_path = "site/public-data/processed/demo_nof_overview.csv",
      RDY_filter = RDY_FILTER_TEXT,
      RDY_row_identifier = paste(r$Trust_code, r$Quarter, r$Metric_ID, r$Reporting_date, sep = " | "),
      Comparator_group = NOF_COMPARATOR_GROUP,
      Comparator_rows_used = comp_n,
      Calculation_for_median = NOF_VALUE_SOURCE,
      Calculation_for_rank = NOF_VALUE_SOURCE,
      Value_source = NOF_VALUE_SOURCE,
      Median_source = NOF_VALUE_SOURCE,
      Rank_source = NOF_VALUE_SOURCE,
      Rank_direction_note = NOF_RANK_DIRECTION,
      Exclusions = "Score variants (OF1xxx, OF4xxx) excluded from display; missing/suppressed/non-numeric Value excluded",
      Caveat = paste(
        "Median and rank are NHS England published comparators for the peer group — not recalculated by this demo.",
        "demo_nof_overview.csv is a truncated convenience sample (first 50 RDY rows).",
        "Tie handling follows NHS England source Rank column — not reimplemented here."
      ),
      stringsAsFactors = FALSE
    )
  })
  do.call(rbind, rows)
}

write_nof_audit_csv <- function(audit_df) {
  out <- file.path(metadata_dir, "public_report_audit_nof_overview.csv")
  write.csv(audit_df, out, row.names = FALSE)
  cat("Written:", basename(out), "\n")
  invisible(out)
}

write_nof_audit_md <- function(audit_df, latest_quarter) {
  lines <- c(
    "# NOF performance overview — audit and verification guide",
    "",
    paste("Generated:", format(Sys.time(), "%Y-%m-%d %H:%M:%S")),
    paste("Display quarter:", latest_quarter),
    "",
    "> Public-data demonstration only. Not an official Dorset HealthCare report.",
    "",
    "## How to verify a figure manually",
    "",
    "1. Open `site/public-data/metadata/public_report_audit_nof_overview.csv` or the HTML report audit section for the metric.",
    "2. Note the `RDY_row_identifier` (Trust_code | Quarter | Metric_ID | Reporting_date).",
    "3. Open the processed RDY extract (`Processed_extract_path` in the audit CSV) and locate that row.",
    "4. Open the raw NOF data CSV (`Raw_source_file` in the audit CSV) and search for `Trust_code=RDY` and the same `Metric_ID`, `Quarter`, and `Reporting_date`.",
    "5. Confirm `Value`, `Median_value`, and `Rank` match exactly — these are **NHS England published fields**, not recalculated by this demo.",
    "6. Count comparator rows in the raw file for the same Quarter + Metric_ID + Reporting_date with a numeric Value — this should match `Comparator_rows_used`.",
    "7. Check NHS England metric metadata for rank direction and clinical/operational meaning before any operational use.",
    "",
    "## Median and rank",
    "",
    "- **Median_value** and **Rank** come directly from the NHS England NOF data CSV.",
    "- This demo does **not** recompute league tables, medians, or ranks.",
    "- Rank 1 is published by NHS England as best in the MH/community trust peer group for that metric (per NOF methodology).",
    "- **Polarity is not independently verified here** — confirm against NHS England definitions.",
    "",
    "## Worked example: OF0005",
    ""
  )

  ex1 <- audit_df[audit_df$Metric_ID == "OF0005", , drop = FALSE]
  if (nrow(ex1) > 0) {
    ex1 <- ex1[1, , drop = FALSE]
    lines <- c(lines,
      paste("- **Metric:** OF0005 —", ex1$Metric_description),
      paste("- **RDY row:**", ex1$RDY_row_identifier),
      paste("- **Raw file:**", ex1$Raw_source_file),
      paste("- **Comparator rows:**", ex1$Comparator_rows_used),
      paste("- **Median/rank source:**", ex1$Median_source),
      ""
    )
  }

  lines <- c(lines, "## Worked example: OF0079", "")

  ex2 <- audit_df[audit_df$Metric_ID == "OF0079", , drop = FALSE]
  if (nrow(ex2) > 0) {
    ex2 <- ex2[1, , drop = FALSE]
    lines <- c(lines,
      paste("- **Metric:** OF0079 —", ex2$Metric_description),
      paste("- **RDY row:**", ex2$RDY_row_identifier),
      paste("- **Raw file:**", ex2$Raw_source_file),
      paste("- **Comparator rows:**", ex2$Comparator_rows_used),
      paste("- **Caveat:** Finance metric rank direction requires NHS England definition check before operational interpretation."),
      ""
    )
  }

  lines <- c(lines,
    "## Human reviewer checklist",
    "",
    "- Confirm publication quarter and whether a newer NOF release supersedes these figures.",
    "- Check provisional/final status and any NHS England revisions.",
    "- Verify RDY matching against local ODS register.",
    "- Do not treat rank or median comparisons as significance tests.",
    "- Obtain accountable sign-off before operational or Board use.",
    ""
  )

  out <- file.path(metadata_dir, "public_report_audit_nof_overview.md")
  writeLines(lines, out, useBytes = TRUE)
  cat("Written:", basename(out), "\n")
  invisible(out)
}

audit_metric_details <- function(row) {
  fields <- c(
    "Metric_ID", "Metric_description", "Quarter", "Reporting_date", "Source_dataset",
    "Raw_source_file", "Raw_source_url", "Processed_extract_path", "Demo_csv_path",
    "RDY_filter", "RDY_row_identifier", "Comparator_group", "Comparator_rows_used",
    "Value_source", "Calculation_for_median", "Calculation_for_rank",
    "Rank_direction_note", "Exclusions", "Caveat"
  )
  items <- vapply(fields, function(f) {
    val <- if (f %in% names(row)) as.character(row[[f]]) else ""
    paste0("<dt>", esc(f), "</dt><dd>", esc(val), "</dd>")
  }, character(1))
  paste0(
    '<details class="nhs-audit-details"><summary>', esc(row$Metric_ID), " — ",
    esc(trimws(as.character(row$Metric_description))), '</summary>',
    '<dl class="nhs-audit-dl">', paste(items, collapse = ""), '</dl></details>'
  )
}

nof_audit_verify_body <- function(audit_df, raw_display_path, raw_url) {
  summary_cols <- c(
    "Metric_ID", "Quarter", "RDY_row_identifier", "Comparator_rows_used",
    "Value_source", "Rank_direction_note"
  )
  summary_df <- audit_df[, intersect(summary_cols, names(audit_df)), drop = FALSE]

  details_html <- paste(vapply(seq_len(nrow(audit_df)), function(i) {
    audit_metric_details(audit_df[i, , drop = FALSE])
  }, character(1)), collapse = "\n")

  paste0(
    '<p>Every metric in the key figures table is traceable to NHS England published fields.',
    ' Median and rank are <strong>not recalculated</strong> by this demo.</p>',
    '<div class="nhs-source-links"><p><strong>View source data:</strong></p>',
    '<ul class="nhs-list-compact">',
    '<li><a href="../public-data/processed/demo_nof_overview.csv">demo_nof_overview.csv</a> (truncated convenience sample)</li>',
    '<li><a href="../public-data/metadata/public_report_audit_nof_overview.csv">public_report_audit_nof_overview.csv</a></li>',
    '<li><a href="../docs-html/public-data/metadata/public_report_audit_nof_overview.html">public_report_audit_nof_overview.md</a></li>',
    '<li><a href="../public-data/DATA_SOURCE_REGISTER.csv">DATA_SOURCE_REGISTER.csv</a></li>',
    '<li><a href="../docs-html/public-data/PUBLIC_REPORTS_METHOD.html">PUBLIC_REPORTS_METHOD.md</a></li>',
    '<li><a href="../governance-and-benefits.html">Governance and benefits</a></li>',
    '</ul>',
    '<p><strong>Raw source file</strong> (not linked — large file): <code>', esc(raw_display_path), '</code></p>',
    if (nzchar(raw_url)) paste0('<p><strong>Source URL:</strong> <a href="', esc(raw_url), '" rel="noopener">', esc(raw_url), '</a></p>') else "",
    '</div>',
    '<details class="nhs-verify-details"><summary>Technical audit detail</summary>',
    '<div class="nhs-audit-summary">', html_table(summary_df, max_rows = 50), '</div>',
    '</details>',
    '<details class="nhs-verify-details"><summary>Per-metric source trace</summary>',
    details_html,
    '<p class="nhs-audit-note"><em>Tie handling follows the NHS England source Rank column — not reimplemented in this demo.</em></p>',
    '</details>'
  )
}

traceability_verify_body <- function(intro, demo_files, filter_note_ids, inspection_ids = character()) {
  demo_items <- vapply(demo_files, function(d) {
    paste0('<li><a href="../public-data/processed/', esc(d), '">', esc(d), '</a></li>')
  }, character(1))
  filter_items <- vapply(filter_note_ids, function(id) {
    fn <- paste0("filter_note_", id, ".txt")
    paste0('<li><a href="../public-data/metadata/', fn, '">', esc(fn), '</a></li>')
  }, character(1))
  inspect_items <- vapply(inspection_ids, function(id) {
    fn <- paste0("inspection_", id, ".txt")
    paste0('<li><a href="../public-data/metadata/', fn, '">', esc(fn), '</a></li>')
  }, character(1))

  paste0(
    '<p>', intro, '</p>',
    '<div class="nhs-source-links">',
    '<p><strong>Demo CSV(s):</strong></p><ul class="nhs-list-compact">', paste(demo_items, collapse = ""), '</ul>',
    '<p><strong>Filter / inspection notes:</strong></p><ul class="nhs-list-compact">',
    paste(c(filter_items, inspect_items), collapse = ""), '</ul>',
    '<p><strong>Method documentation:</strong> ',
    '<a href="../docs-html/public-data/PUBLIC_REPORTS_METHOD.html">PUBLIC_REPORTS_METHOD.md</a> · ',
    '<a href="../public-data/DATA_SOURCE_REGISTER.csv">DATA_SOURCE_REGISTER.csv</a> · ',
    '<a href="../governance-and-benefits.html">Governance and benefits</a></p>',
    '</div>'
  )
}

is_suppressed <- function(x) {
  x <- trimws(as.character(x))
  is.na(x) | x == "" | x == "*" | toupper(x) == "NA"
}

to_num <- function(x) {
  x <- trimws(as.character(x))
  x[x %in% c("", "*", "NA", "N/A")] <- NA
  suppressWarnings(as.numeric(x))
}

pct_change <- function(cur, prev) {
  if (is.na(cur) || is.na(prev) || prev == 0) return(NA)
  round(100 * (cur - prev) / prev, 1)
}

kpi_row <- function(items) {
  cards <- vapply(items, function(it) {
    paste0(
      '<div class="nhs-kpi"><span class="nhs-kpi-value">', esc(it$value),
      '</span><span class="nhs-kpi-label">', esc(it$label), '</span></div>'
    )
  }, character(1))
  paste0('<div class="nhs-kpi-row">', paste(cards, collapse = ""), '</div>')
}

html_table <- function(df, max_rows = 20, hide_cols = character()) {
  if (is.null(df) || nrow(df) == 0) {
    return('<p><em>No tabular data available for this section.</em></p>')
  }
  if (length(hide_cols) > 0) {
    df <- df[, setdiff(names(df), hide_cols), drop = FALSE]
  }
  if (nrow(df) > max_rows) df <- df[seq_len(max_rows), , drop = FALSE]
  cols <- names(df)
  hdr <- paste0("<th scope=\"col\">", esc(cols), "</th>", collapse = "")
  rows <- apply(df, 1, function(r) {
    paste0("<td>", esc(r), "</td>", collapse = "")
  })
  body <- paste0("<tr>", rows, "</tr>", collapse = "\n")
  paste0(
    '<div class="nhs-table-wrap"><table><thead><tr>', hdr,
    '</tr></thead><tbody>', body, '</tbody></table></div>'
  )
}

bar_chart <- function(labels, values, title = "Chart", max_bars = 10,
                      sort_by_value = TRUE, period_caption = NULL) {
  if (length(labels) == 0 || all(is.na(values))) {
    return('<p><em>Insufficient numeric data for chart.</em></p>')
  }
  if (isTRUE(sort_by_value)) {
    ord <- order(values, decreasing = TRUE)
    labels <- labels[ord]
    values <- values[ord]
  }
  if (length(labels) > max_bars) {
    labels <- labels[seq_len(max_bars)]
    values <- values[seq_len(max_bars)]
  }
  mx <- max(values, na.rm = TRUE)
  if (!is.finite(mx) || mx <= 0) mx <- 1
  bars <- mapply(function(lab, val) {
    pct <- max(2, round(100 * val / mx))
    paste0(
      '<div class="nhs-bar-row"><span class="nhs-bar-label">', esc(lab),
      '</span><div class="nhs-bar-track"><div class="nhs-bar-fill" style="width:',
      pct, '%;" role="presentation"></div></div></div>'
    )
  }, labels, values, SIMPLIFY = TRUE, USE.NAMES = FALSE)
  caption_html <- period_caption_html(period_caption)
  paste0(
    '<div class="nhs-chart"><h3>', esc(title), '</h3>',
    caption_html,
    paste(bars, collapse = "\n"),
    '</div>'
  )
}

period_caption_html <- function(window_label, extract_label = NULL) {
  if (is.null(window_label) || !nzchar(window_label)) return("")
  extra <- if (!is.null(extract_label) && nzchar(extract_label)) {
    paste0(" <span class=\"nhs-period-extract\">(", esc(extract_label), ")</span>")
  } else {
    ""
  }
  paste0(
    '<p class="nhs-period-caption"><strong>Period:</strong> ',
    esc(window_label), extra, '</p>'
  )
}

provider_scope_badge_html <- function() {
  paste0(
    '<p class="nhs-scope-badge" role="note">',
    '<strong>Scope:</strong> Provider / RDY rows only — not Dorset population or ICB-resident views.',
    '</p>'
  )
}

bottom_line_section <- function(text) {
  if (is.null(text) || !nzchar(trimws(text))) return("")
  paste0(
    '<section class="nhs-section nhs-bottom-line">',
    '<h2>Bottom line</h2>',
    '<p>', esc(text), '</p>',
    '</section>'
  )
}

why_this_is_useful_section <- function(bullets) {
  if (length(bullets) == 0) return("")
  paste0(
    '<section class="nhs-section nhs-why-useful">',
    '<h2>Why this is useful</h2>',
    bullet_list(bullets),
    '</section>'
  )
}

priority_callout_html <- function(items) {
  if (length(items) == 0) return("")
  items_html <- paste0("<li>", esc(items), "</li>", collapse = "")
  paste0(
    '<div class="nhs-priority-callout" role="note">',
    '<p><strong>Priority review flags</strong></p>',
    '<ul class="nhs-list-compact">', items_html, '</ul>',
    '</div>'
  )
}

standard_metadata_cite <- function(meta) {
  if (is.null(meta) || length(meta) == 0) return("")
  url <- meta$source_url %||% ""
  title <- meta$source_title %||% ""
  pub <- meta$source_publication_date %||% ""
  accessed <- meta$accessed_date %||% ""
  conf <- meta$confidence %||% ""
  parts <- c(
    if (nzchar(title)) title,
    if (nzchar(pub)) paste0("published ", pub),
    if (nzchar(accessed)) paste0("accessed ", accessed),
    if (nzchar(conf)) paste0("confidence: ", conf)
  )
  cite_text <- paste(parts, collapse = "; ")
  if (nzchar(url)) {
    paste0(
      '<cite class="nhs-standard-cite">',
      '<a href="', esc(url), '" rel="noopener">', esc(cite_text), '</a>',
      '</cite>'
    )
  } else {
    paste0('<cite class="nhs-standard-cite">', esc(cite_text), '</cite>')
  }
}

validation_badge_html <- function(status, note = NULL) {
  if (is.null(status) || !nzchar(status)) return("—")
  css <- switch(
    status,
    "Definition check required" = "unclear",
    "Finance sign-off required" = "unclear",
    "Local owner confirmation needed" = "review",
    "Pathway / data-definition check required" = "unclear",
    "Source validation only" = "validation",
    "na"
  )
  title_attr <- if (!is.null(note) && nzchar(note)) {
    paste0(' title="', esc(note), '"')
  } else {
    ""
  }
  paste0(
    '<span class="nhs-validation-badge nhs-validation-badge--', esc(css), '"',
    title_attr, '>', esc(status), '</span>'
  )
}

REPORT_BADGE_META <- "Agent-assisted analytical brief &middot; Public aggregate data &middot; RDY &middot; Demonstration only"

RDY_FILTER_SHORT <- paste0(
  "RDY filter: ODS code <strong>RDY</strong> and case-insensitive trust name variants ",
  "(Dorset HealthCare / Dorset Healthcare University NHS Foundation Trust). ",
  "See <code>site/public-data/metadata/filter_note_*.txt</code> for per-source notes."
)

std_caveat <- paste0(
  '<div class="nhs-caveat" role="note">',
  '<strong>Public-data demonstration report:</strong> ',
  'This is not an official Dorset HealthCare report. It uses public aggregate data only ',
  'and requires human review and local owner confirmation before operational use.',
  '</div>'
)

bullet_list <- function(items) {
  if (length(items) == 0) return("")
  items_html <- paste0("<li>", esc(items), "</li>", collapse = "")
  paste0('<ul class="nhs-list-compact">', items_html, '</ul>')
}

agent_prompt_box <- function(excerpt_text) {
  paste0('<pre class="nhs-prompt-excerpt">', esc(excerpt_text), '</pre>')
}

agent_process_box <- function(steps) {
  steps_html <- paste0("<li>", esc(steps), "</li>", collapse = "")
  paste0(
    '<div class="nhs-agent-process">',
    '<ol>', steps_html, '</ol>',
    '</div>'
  )
}

cannot_conclude_box <- function(items) {
  paste0(
    '<div class="nhs-cannot-conclude" role="note">',
    bullet_list(items),
    '</div>'
  )
}

short_human_check <- function(text) {
  text <- trimws(as.character(text))
  if (!nzchar(text)) return("")
  parts <- strsplit(text, "\\. ", perl = TRUE)[[1]]
  result <- if (length(parts) >= 1 && nzchar(parts[1])) parts[1] else text
  result <- trimws(result)
  if (!grepl("\\.$", result)) paste0(result, ".") else result
}

format_peer_position_short <- function(median, rank) {
  median <- trimws(as.character(median))
  rank <- trimws(as.character(rank))
  if (is.na(to_num(median)) && is.na(to_num(rank))) {
    return("Median and rank not published for this metric in the extract.")
  }
  paste0("Peer median: ", median, ". Published rank: ", rank, ". Not a local target.")
}

format_initial_reading <- function(flag_class, position_text, extra = NULL) {
  flag_read <- switch(
    flag_class %||% "review",
    strength = "Confirm locally before describing as positive performance.",
    definition = "Finance or definition check required.",
    review = "Local review needed.",
    watch = "Interpret cautiously — confirm cohort and scope.",
    validation = "Source validation only — not a performance standing.",
    "Local review needed."
  )
  paste0(trimws(position_text), " ", flag_read, if (!is.null(extra) && nzchar(extra)) paste0(" ", extra) else "")
}

scope_section <- function(can_items = list(), cannot_items = list()) {
  if (length(can_items) == 0 && length(cannot_items) == 0) return("")
  can_html <- if (length(can_items) > 0) {
    paste0("<p><strong>This report can:</strong></p>", bullet_list(can_items))
  } else {
    ""
  }
  cannot_html <- if (length(cannot_items) > 0) {
    paste0("<p><strong>This report cannot:</strong></p>", bullet_list(cannot_items))
  } else {
    ""
  }
  paste0(
    '<section class="nhs-section">',
    '<h2>What this report can and cannot tell us</h2>',
    can_html,
    cannot_html,
    '<p><em>For that reason, this should be treated as a first-draft prompt for review, not a final performance judgement.</em></p>',
    "</section>"
  )
}

headline_reading_section <- function(bullets) {
  if (length(bullets) == 0) return("")
  paste0(
    '<section class="nhs-section">',
    "<h2>Headline reading</h2>",
    '<div class="nhs-headline-reading">',
    bullet_list(bullets),
    "</div></section>"
  )
}

findings_group_section <- function(groups) {
  if (is.null(groups) || length(groups) == 0) return("")
  groups_html <- paste(vapply(groups, function(g) {
    items_html <- paste(vapply(g$items, function(item) {
      owner <- if (!is.null(item$owner) && nzchar(item$owner)) {
        paste0('<p class="nhs-finding-owner"><strong>Human check:</strong> ', esc(item$owner), "</p>")
      } else {
        ""
      }
      paste0(
        '<article class="nhs-finding-item">',
        "<h4>", esc(item$title), "</h4>",
        "<p>", esc(item$body), "</p>",
        owner,
        "</article>"
      )
    }, character(1)), collapse = "")
    paste0('<div class="nhs-findings-group"><h3>', esc(g$title), "</h3>", items_html, "</div>")
  }, character(1)), collapse = "")
  paste0(
    '<section class="nhs-section">',
    "<h2>Key findings by review area</h2>",
    groups_html,
    "</section>"
  )
}

first_draft_analysis <- function(paragraphs) {
  paras <- paste0("<p>", esc(paragraphs), "</p>", collapse = "")
  paste0('<section class="nhs-section"><h2>First-draft analysis</h2>', paras, '</section>')
}

human_review_warning <- function() {
  paste0(
    '<div class="nhs-warning" role="note"><strong>Human review required:</strong> ',
    'Confirm definitions, publication status and local owner sign-off before any operational use.</div>'
  )
}

verify_section <- function(intro_html, body_html) {
  paste0(
    '<section class="nhs-section nhs-verify-block"><h2>Audit trail and source checks</h2>',
    '<div class="nhs-verify-intro">', intro_html, '</div>',
    body_html,
    '</section>'
  )
}

verify_intro_short <- function(demo_link_html = "", trace_sentence = NULL) {
  trace <- trace_sentence %||%
    "Open the linked demo CSV or audit file, locate the RDY row, and confirm the value matches the published source."
  paste0(
    '<p>', trace, '</p>',
    '<ul class="nhs-list-compact">',
    if (nzchar(demo_link_html)) demo_link_html else "",
    '<li><a href="../public-data/DATA_SOURCE_REGISTER.csv">Source register</a></li>',
    '<li><a href="../docs-html/public-data/PUBLIC_REPORTS_METHOD.html">Method document</a></li>',
    '</ul>'
  )
}

collapsible_details <- function(summary, content_html, extra_class = "") {
  if (!nzchar(content_html)) return("")
  paste0(
    '<details class="nhs-support-details ', extra_class, '">',
    '<summary>', esc(summary), '</summary>',
    '<div class="nhs-support-details-body">', content_html, '</div>',
    '</details>'
  )
}

commentary_cards_block <- function(cards) {
  if (length(cards) == 0) return("")
  paste0('<div class="nhs-metric-commentary">', paste(cards, collapse = "\n"), '</div>')
}

wrap_trend_collapsible <- function(trend_html, summary = "Trend detail (charts and tables)") {
  if (!nzchar(trend_html)) return("")
  collapsible_details(summary, trend_html)
}

what_agent_asked_section <- function(question, dataset_line = NULL) {
  dataset_html <- if (!is.null(dataset_line) && nzchar(dataset_line)) {
    paste0("<p><strong>Dataset:</strong> ", esc(dataset_line), "</p>")
  } else {
    ""
  }
  paste0(
    '<section class="nhs-section"><h2>What the agent was asked to do</h2>',
    '<div class="nhs-agent-box">',
    "<p><strong>Business question:</strong> ", esc(question), "</p>",
    dataset_html,
    "<p>This is a <strong>first-draft analytical brief</strong> for human review — not an approved performance report.</p>",
    "</div>",
    "</section>"
  )
}

data_used_section <- function(config) {
  trend_line <- config$trend_available %||% ""
  trend_html <- if (nzchar(trend_line)) {
    paste0("<li><strong>Historic trend data:</strong> ", esc(trend_line), "</li>")
  } else {
    ""
  }
  paste0(
    '<section class="nhs-section"><h2>Data used</h2>',
    config$data_used_html,
    '<ul class="nhs-list-compact nhs-data-meta">',
    "<li><strong>Period covered:</strong> ", esc(config$period), "</li>",
    "<li><strong>RDY filter:</strong> ODS code <strong>RDY</strong>; Dorset HealthCare trust name variants (see filter notes)</li>",
    trend_html,
    "</ul></section>"
  )
}

agent_summary_section <- function(bullets) {
  if (length(bullets) == 0) return("")
  paste0(
    '<section class="nhs-section"><h2>Draft interpretation</h2>',
    bullet_list(bullets),
    "</section>"
  )
}

human_checks_section <- function(items) {
  if (is.null(items) || length(items) == 0) return("")
  if (is.list(items[[1]]) && !is.null(items[[1]]$q)) {
    bullets <- vapply(items, function(x) {
      paste0(x$q, " — ", x$expl)
    }, character(1))
  } else {
    bullets <- unlist(items)
  }
  paste0(
    '<section class="nhs-section"><h2>Human validation checklist</h2>',
    bullet_list(bullets),
    "</section>"
  )
}

standard_human_checks <- function(extra = list()) {
  base <- list(
    list(
      q = "Is this the same definition used locally?",
      expl = "Public datasets may use national definitions. Local dashboards may use different filters, denominators or reporting dates."
    ),
    list(
      q = "Is this still the latest position?",
      expl = "Public data may be delayed. Local operational data may have moved on since the reporting period shown."
    ),
    list(
      q = "Is the figure affected by data quality, suppression, coding or service model?",
      expl = "A public figure can be affected by small numbers, exclusions, coding rules or how services are configured."
    ),
    list(
      q = "Who owns the local narrative?",
      expl = "The accountable service, finance, workforce, quality or BI owner should confirm the explanation before use."
    ),
    list(
      q = "Is this suitable for board/service reporting, or only a prompt for further review?",
      expl = "These briefs are first drafts — confirm whether the figure is ready for formal reporting or needs more local work."
    )
  )
  c(base, extra)
}

NOF_METRIC_SPEC_URL <- "https://www.england.nhs.uk/long-read/nhs-oversight-framework-csv-metadata-file/"
NOF_SPEC_ACCESSED <- "2026-06-21"
NOF_SPEC_META <- list(
  source_url = NOF_METRIC_SPEC_URL,
  source_title = "NHS Oversight Framework technical metric specification",
  source_publication_date = "Q4 2025/26 league tables",
  accessed_date = NOF_SPEC_ACCESSED,
  confidence = "confirmed"
)

TT_STANDARDS_URL <- "https://digital.nhs.uk/data-and-information/publications/statistical/mental-health-services/monthly-statistics-on-adult-early-intervention-in-psychosis-and-nhs-talking-therapies-for-anxiety-and-depression"
TT_STANDARDS_META <- list(
  source_url = TT_STANDARDS_URL,
  source_title = "NHS Talking Therapies monthly statistics — access standards",
  source_publication_date = "April 2026",
  accessed_date = "2026-06-21",
  confidence = "confirmed"
)

CHS_WAITING_URL <- "https://www.england.nhs.uk/statistics/statistical-work-areas/community-health-services-waiting-lists/"
CHS_WAITING_META <- list(
  source_url = CHS_WAITING_URL,
  source_title = "NHS England community health services waiting lists",
  source_publication_date = "2025/26 planning guidance",
  accessed_date = "2026-06-21",
  confidence = "confirmed"
)

NOF_METRIC_SPEC <- list(
  OF0005 = list(
    polarity = "lower_better",
    standard = "Lower is better; 0% long waits is the ideal direction.",
    validation_status = "Local owner confirmation needed",
    scoring_note = "Long-wait community metric — confirm cohort and denominator."
  ),
  OF0079 = list(
    polarity = "unknown",
    standard = "0% or surplus is best category per NHS England scoring; confirm sign convention locally.",
    validation_status = "Finance sign-off required",
    scoring_note = "Planned surplus/deficit excluding deficit support funding, divided by turnover/allocation."
  ),
  OF0081 = list(
    polarity = "unknown",
    standard = "On-plan or better variance is favourable per NHS England scoring; confirm locally.",
    validation_status = "Finance sign-off required",
    scoring_note = "YTD actual less planned surplus/deficit, divided by turnover/allocation."
  ),
  OF0082 = list(
    polarity = "lower_better",
    standard = "Lower sickness absence is generally preferable.",
    validation_status = "Local owner confirmation needed"
  ),
  OF0041 = list(
    polarity = "higher_better",
    standard = "Higher year-on-year CYP access change may indicate growth; confirm cohort.",
    validation_status = "Local owner confirmation needed"
  ),
  OF0061 = list(
    polarity = "higher_better",
    standard = "Higher staff survey sub-score is generally preferable.",
    validation_status = "Local owner confirmation needed"
  ),
  OF0084 = list(
    polarity = "higher_better",
    standard = "Higher engagement sub-score is generally preferable.",
    validation_status = "Local owner confirmation needed"
  ),
  OF0057 = list(
    polarity = "higher_better",
    standard = "≥70% avoids lowest scoring band (score 4.00); higher is better.",
    validation_status = "Local owner confirmation needed",
    scoring_note = "UCR 2-hour performance — quarterly metric."
  ),
  OF0016 = list(
    polarity = "higher_better",
    standard = "Higher is better — MH crisis face-to-face within 24 hours.",
    validation_status = "Local owner confirmation needed"
  ),
  OF0086 = list(
    polarity = "lower_better",
    standard = "100 ≈ expected cost; lower is better per NHS England specification.",
    validation_status = "Finance sign-off required"
  ),
  OF0063 = list(
    polarity = "lower_better",
    standard = "Lower is better — adult MH inpatients with length of stay over 60 days.",
    validation_status = "Local owner confirmation needed",
    priority = TRUE
  )
)

nof_judgement_text <- function(metric_id, row, spec, trend_obj = NULL) {
  val_n <- to_num(row$Value)
  med_n <- to_num(row$Median_value)
  rank_n <- to_num(row$Rank)
  pol <- spec$polarity %||% "unknown"
  parts <- character()
  if (isTRUE(spec$priority %||% FALSE)) {
    parts <- c(parts, "Priority attention — large gap vs peer median.")
  }
  if (pol == "higher_better" && !is.na(val_n) && !is.na(med_n)) {
    if (val_n < med_n) parts <- c(parts, "Below peer median.")
    if (metric_id == "OF0057" && !is.na(val_n) && val_n >= 70) {
      parts <- c(parts, "Above 70% scoring threshold but below peer median.")
    }
  }
  if (pol == "lower_better" && !is.na(val_n) && !is.na(med_n)) {
    if (val_n > med_n) parts <- c(parts, "Above peer median — review locally.")
  }
  if (!is.null(trend_obj) && isTRUE(trend_obj$available)) {
    chg <- trend_obj$absolute_change
    if (!is.na(chg)) {
      dir <- if (chg > 0) "rising" else if (chg < 0) "falling" else "stable"
      if (pol == "higher_better" && dir == "falling") parts <- c(parts, "Short trend is falling.")
      if (pol == "lower_better" && dir == "rising") parts <- c(parts, "Short trend is rising.")
    }
  }
  if (metric_id %in% c("OF0079", "OF0081")) {
    parts <- c(parts, "Published spec suggests surplus/on-plan is favourable — finance owner should confirm plan version.")
  }
  if (length(parts) == 0) "Local review needed — confirm definition and local position."
  else paste(unique(parts), collapse = " ")
}

nof_trend_direction_label <- function(trend_obj, polarity) {
  if (is.null(trend_obj) || !isTRUE(trend_obj$available) || trend_obj$n_periods < 2) {
    return(list(label = "Not available", css = "na"))
  }
  chg <- trend_obj$absolute_change
  pct <- trend_obj$percent_change
  stable <- (!is.na(pct) && abs(pct) <= 2) ||
    (is.na(pct) && abs(chg) < max(1, abs(trend_obj$previous_value) * 0.02))
  if (stable) return(list(label = "Stable", css = "stable"))
  if (is.na(chg) || chg == 0) return(list(label = "Unclear", css = "unclear"))
  if (polarity == "higher_better") {
    label <- if (chg > 0) "Rising" else "Falling"
  } else if (polarity == "lower_better") {
    label <- if (chg < 0) "Falling" else "Rising"
  } else {
    label <- if (chg > 0) "Rising" else "Falling"
  }
  list(label = label, css = trend_badge_css(label))
}

nof_agent_flag_badge <- function(flag_label, flag_class) {
  paste0(
    '<span class="nhs-agent-flag nhs-agent-flag--', esc(flag_class), '">',
    esc(flag_label), '</span>'
  )
}

nof_format_value_compare <- function(row) {
  paste0(
    "RDY value ", trimws(as.character(row$Value)),
    " vs peer median ", trimws(as.character(row$Median_value)),
    " (published rank ", trimws(as.character(row$Rank)), ")"
  )
}

nof_how_to_read_table <- function() {
  paste0(
    '<h3>How to read this table</h3>',
    '<ul class="nhs-list-compact">',
    '<li>Each row is one NHS Oversight Framework metric for Dorset HealthCare (RDY) in the published peer group.</li>',
    '<li><strong>Value</strong> is RDY&rsquo;s published metric value from the NHS England file — not recalculated by this demo.</li>',
    '<li><strong>Median_value</strong> is the published peer median for the same metric, quarter and reporting date.</li>',
    '<li><strong>Rank</strong> is the published NHS England rank (1 = best in peer group per NOF methodology).</li>',
    '<li>Whether higher or lower is better varies by metric — confirm polarity against the ',
    '<a href="', NOF_METRIC_SPEC_URL, '" rel="noopener">official NHS England NOF technical metric specification</a> ',
    'before any operational use. This specification was not bulk-downloaded in the public-data pipeline.</li>',
    '<li>This table is part of a first-draft agent brief for demonstration — not an official Dorset HealthCare performance report.</li>',
    '</ul>'
  )
}

nof_commentary_lookup <- function() {
  list(
    OF0005 = list(
      flag = "Potential strength", flag_class = "strength",
      plain_meaning = "Percentage of patients waiting over 52 weeks for community services.",
      interpret = function(row) {
        paste0(
          "This is a long-wait metric where lower values are generally preferable. ",
          nof_format_value_compare(row), ". ",
          "RDY reports 0.00% compared with a peer median of 0.19%. ",
          "The agent would note this as a potential strength relative to peers, ",
          "but the denominator, local waiting-list definition and data quality must be checked before any operational use."
        )
      },
      human_check = "Confirm cohort, denominator, reporting period (Mar-26) and whether local community waiting lists align with the national definition."
    ),
    OF0079 = list(
      flag = "Definition check required", flag_class = "definition",
      plain_meaning = "Planned surplus/deficit for the financial year.",
      interpret = function(row) {
        paste0(
          "This finance metric relates to the Trust&rsquo;s planned financial position. ",
          nof_format_value_compare(row), ". ",
          "Positive and negative values have specific meanings in NHS finance reporting — ",
          "the agent does not assume surplus/deficit interpretation without the official metric definition. ",
          "Flag for finance-owner confirmation."
        )
      },
      human_check = "Finance team to confirm sign convention, plan version (2025/26 plan) and whether this matches internal board reporting."
    ),
    OF0081 = list(
      flag = "Definition check required", flag_class = "definition",
      plain_meaning = "Variance year-to-date to financial plan.",
      interpret = function(row) {
        paste0(
          "This shows how far year-to-date performance varies from the approved plan. ",
          nof_format_value_compare(row), ". ",
          "The agent does not treat a higher or lower variance as automatically favourable without checking NHS England&rsquo;s metric definition and local finance narrative."
        )
      },
      human_check = "Finance owner to confirm Month 12 2025/26 YTD basis, plan assumptions and whether amendments have been published."
    ),
    OF0082 = list(
      flag = "Review locally", flag_class = "review",
      plain_meaning = "Sickness absence rate across the workforce.",
      interpret = function(row) {
        paste0(
          "Workforce sickness absence — lower values are usually preferable. ",
          nof_format_value_compare(row), ". ",
          "RDY is below the peer median (5.47 vs 6.22), which may indicate relatively lower absence, ",
          "but reporting period (Q3 2025/26) and local workforce context should be confirmed."
        )
      },
      human_check = "Workforce/HR lead to confirm reporting period, inclusion rules and whether local sickness dashboards use the same definition."
    ),
    OF0041 = list(
      flag = "Watch / clarify", flag_class = "watch",
      plain_meaning = "Annual change in the number of children and young people accessing NHS-funded mental health services.",
      interpret = function(row) {
        paste0(
          "This measures year-on-year change in CYP mental health access (Apr 25–Mar 26 vs prior year). ",
          nof_format_value_compare(row), ". ",
          "A higher figure may indicate growth in access relative to peers, but the agent would not treat this as demand or performance without confirming cohort, comparison year and service configuration."
        )
      },
      human_check = "CYP mental health lead to confirm numerator, ICB/resident vs provider scope and whether local access data supports the direction of change."
    ),
    OF0061 = list(
      flag = "Review locally", flag_class = "review",
      plain_meaning = "NHS Staff Survey sub-score on raising concerns.",
      interpret = function(row) {
        paste0(
          "Staff survey theme — higher scores usually indicate more positive staff experience on this theme. ",
          nof_format_value_compare(row), ". ",
          "RDY is slightly above the peer median. The agent would not treat small differences as meaningful without checking survey methodology and response rates."
        )
      },
      human_check = "Workforce/OD lead to confirm 2025 Staff Survey methodology, response rate and whether local culture survey data aligns."
    ),
    OF0084 = list(
      flag = "Review locally", flag_class = "review",
      plain_meaning = "NHS Staff Survey engagement theme sub-score.",
      interpret = function(row) {
        paste0(
          "Staff engagement theme from the NHS Staff Survey — higher is usually preferable. ",
          nof_format_value_compare(row), ". ",
          "RDY is marginally above the peer median. The agent would flag for local review rather than over-interpreting a small gap."
        )
      },
      human_check = "Confirm 2025 survey cohort, theme definition and whether the difference is material in local workforce planning."
    ),
    OF0057 = list(
      flag = "Review locally", flag_class = "review",
      plain_meaning = "Urgent Community Response 2-hour performance.",
      interpret = function(row) {
        paste0(
          "Proportion of urgent community response contacts meeting the 2-hour standard — higher is generally preferable for access performance. ",
          nof_format_value_compare(row), ". ",
          "RDY is below the peer median (79.28 vs 87.18). ",
          "The agent would flag this for local review and ask whether the latest local UCR position, referral volumes and service model explain the gap — not assume a single cause."
        )
      },
      human_check = "Community urgent care/service owner to confirm UCR definition, Q4 2025/26 reporting period and local operational position."
    ),
    OF0016 = list(
      flag = "Review locally", flag_class = "review",
      plain_meaning = "Percentage of patients in mental health crisis receiving face-to-face contact within 24 hours.",
      interpret = function(row) {
        paste0(
          "Mental health crisis access metric — higher is generally preferable. ",
          nof_format_value_compare(row), ". ",
          "RDY is below the peer median (65.54 vs 71.97). ",
          "The agent would flag this as an access and patient-safety themed metric needing local narrative and definition check."
        )
      },
      human_check = "Crisis pathway owner to confirm crisis cohort, face-to-face definition, Q4 2025/26 period and alignment with local crisis standards."
    ),
    OF0086 = list(
      flag = "Review locally", flag_class = "review",
      plain_meaning = "Relative difference in costs (National Cost Collection Index, adjusted for Market Forces Factor).",
      interpret = function(row) {
        paste0(
          "Relative difference in costs compares provider actual cost with expected cost. ",
          "A value of 100 is approximately expected cost; values above 100 indicate costs above expected; ",
          "values below 100 indicate costs below expected. NHS England describes lower as better for this metric. ",
          nof_format_value_compare(row), ". ",
          "RDY&rsquo;s published value is above both 100 and the peer median, so the agent would flag this for finance/productivity review ",
          "rather than treating it as a definitive conclusion about efficiency or overspend."
        )
      },
      human_check = "Finance/productivity owner to confirm 2024/25 cost index basis, MFF adjustment, peer group and local cost improvement narrative."
    ),
    OF0063 = list(
      flag = "Review locally", flag_class = "review",
      plain_meaning = "Percentage of adult mental health inpatients with length of stay over 60 days.",
      interpret = function(row) {
        paste0(
          "Long-stay inpatient metric — lower values are generally preferable. ",
          nof_format_value_compare(row), ". ",
          "RDY&rsquo;s value (46.55) is substantially above the peer median (24.52). ",
          "Because lower appears better for long-stay metrics, the agent would flag this for local review. ",
          "A human reviewer would need to confirm the cohort, denominator, reporting period and whether the public figure aligns with local inpatient reporting."
        )
      },
      human_check = "Acute/inpatient mental health lead to confirm >60-day definition, Q4 2025/26 cohort and local bed management data."
    )
  )
}

nof_metric_commentary_card <- function(row, entry) {
  metric_label <- paste0(trimws(as.character(row$Metric_ID)), " — ",
                         trimws(as.character(row$Metric_description)))
  interp <- if (is.function(entry$interpret)) entry$interpret(row) else entry$interpret
  paste0(
    '<article class="nhs-metric-card">',
    '<p class="nhs-metric-card-title"><strong>', esc(metric_label), '</strong> ',
    nof_agent_flag_badge(entry$flag, entry$flag_class), '</p>',
    '<dl>',
    '<dt>Plain-English meaning</dt><dd>', esc(entry$plain_meaning), '</dd>',
    '<dt>RDY value and peer median</dt><dd>', esc(nof_format_value_compare(row)), '</dd>',
    '<dt>Agent flag</dt><dd>', esc(entry$flag), '</dd>',
    '<dt>Initial interpretation</dt><dd>', interp, '</dd>',
    '<dt>Human check required</dt><dd>', esc(entry$human_check), '</dd>',
    '</dl></article>'
  )
}

nof_metric_commentary_section <- function(ranked_df) {
  lookup <- nof_commentary_lookup()
  cards <- vapply(seq_len(nrow(ranked_df)), function(i) {
    row <- ranked_df[i, , drop = FALSE]
    id <- trimws(as.character(row$Metric_ID))
    entry <- lookup[[id]]
    if (is.null(entry)) {
      paste0(
        '<article class="nhs-metric-card"><p><strong>', esc(id), '</strong></p>',
        '<p><em>No commentary template for this metric — check NHS England definitions.</em></p></article>'
      )
    } else {
      nof_metric_commentary_card(row, entry)
    }
  }, character(1))
  commentary_cards_block(cards)
}

nof_agent_reading <- function(entry, row) {
  if (is.null(entry)) return("Definition check and local review needed.")
  val_n <- to_num(row$Value)
  med_n <- to_num(row$Median_value)
  pos <- if (!is.na(val_n) && !is.na(med_n)) {
    if (val_n > med_n) "RDY is above the peer median." else if (val_n < med_n) "RDY is below the peer median." else "RDY matches the peer median."
  } else {
    ""
  }
  extra <- if (trimws(as.character(row$Metric_ID)) == "OF0086") {
    "For this metric, a value of 100 is approximately expected cost."
  } else {
    NULL
  }
  format_initial_reading(entry$flag_class, pos, extra)
}

nof_position_phrase <- function(row) {
  val_n <- to_num(row$Value)
  med_n <- to_num(row$Median_value)
  val <- trimws(as.character(row$Value))
  med <- trimws(as.character(row$Median_value))
  rank <- trimws(as.character(row$Rank))
  if (!is.na(val_n) && !is.na(med_n)) {
    if (val_n > med_n) pos <- "above"
    else if (val_n < med_n) pos <- "below"
    else pos <- "in line with"
    paste0(
      "RDY is shown as ", val, " against a peer median of ", med,
      ", with a published rank of ", rank, ". RDY is ", pos, " the peer median."
    )
  } else {
    paste0("RDY is shown as ", val, " with published rank ", rank, ".")
  }
}

nof_group_item <- function(row, entry) {
  id <- trimws(as.character(row$Metric_ID))
  title <- if (!is.null(entry)) entry$plain_meaning else trimws(as.character(row$Metric_description))
  body <- paste0(
    nof_position_phrase(row), " ",
    switch(
      id,
      OF0005 = "This may be a positive position, but the local owner should confirm the cohort, denominator, reporting period and whether the national definition matches local waiting list reporting.",
      OF0079 = "This needs finance review before interpretation. The owner should confirm the sign convention, plan version and whether the public figure matches internal board reporting.",
      OF0081 = "Finance owner should confirm Month 12 YTD basis, plan assumptions and whether amendments have been published.",
      OF0082 = "The workforce lead should confirm the reporting period, inclusion rules and whether local sickness dashboards use the same definition.",
      OF0041 = "The CYP mental health lead should confirm numerator, ICB/resident vs provider scope and whether local access data supports the direction of change.",
      OF0061 = "The OD or workforce lead should confirm survey methodology, response rate, cohort and whether the difference is meaningful for local workforce planning.",
      OF0084 = "The OD or workforce lead should confirm survey methodology, response rate, cohort and whether the difference is meaningful for local workforce planning.",
      OF0057 = "The community urgent care owner should confirm the UCR definition, reporting period and current operational position.",
      OF0016 = "The crisis pathway owner should confirm the cohort, face-to-face definition and alignment with local crisis standards.",
      OF0086 = "For this metric, a value of 100 is approximately expected cost. Finance or productivity leads should confirm the cost index basis, peer group and local productivity narrative.",
      OF0063 = "The inpatient mental health lead should confirm the definition, cohort and whether the public figure matches local bed management data.",
      "Confirm metric definition and local owner review before operational use."
    )
  )
  owner <- if (!is.null(entry)) entry$human_check else "Confirm metric definition against NHS England NOF specification."
  list(title = title, body = body, owner = owner)
}

nof_build_grouped_findings <- function(audit_source, lookup) {
  ids_for <- function(metric_ids) {
    lapply(metric_ids, function(id) {
      idx <- which(trimws(audit_source$Metric_ID) == id)
      if (length(idx) == 0) return(NULL)
      row <- audit_source[idx[1], , drop = FALSE]
      entry <- lookup[[id]]
      nof_group_item(row, entry)
    })
  }
  compact_items <- function(items) {
    items <- items[!vapply(items, is.null, logical(1))]
    if (length(items) == 0) NULL else items
  }
  groups <- list(
    list(
      title = "Potential strengths, subject to validation",
      items = compact_items(ids_for(c("OF0005", "OF0079")))
    ),
    list(
      title = "Areas for local review",
      items = compact_items(ids_for(c("OF0057", "OF0016", "OF0063", "OF0086")))
    ),
    list(
      title = "Finance / definition checks",
      items = compact_items(ids_for(c("OF0079", "OF0081", "OF0086")))
    ),
    list(
      title = "Workforce indicators",
      items = compact_items(ids_for(c("OF0082", "OF0061", "OF0084")))
    ),
    list(
      title = "Metrics needing cautious interpretation",
      items = compact_items(ids_for(c("OF0041")))
    )
  )
  groups[vapply(groups, function(g) length(g$items %||% list()) > 0, logical(1))]
}

load_mhsds_time_series <- function() {
  files <- list.files(processed_dir, pattern = "^rdy_mhsds_monthly.*time_series.*\\.csv$", full.names = TRUE)
  if (length(files) == 0) return(NULL)
  pref <- files[grepl("Apr2025.*Perf_2026|Apr_Perf_2026", basename(files), ignore.case = TRUE)]
  pick <- if (length(pref) > 0) pref[1] else files[1]
  tryCatch(read.csv(pick, stringsAsFactors = FALSE, check.names = FALSE), error = function(e) NULL)
}

load_mhsds_access_trend <- function() {
  load_trend_file("trend_mhsds_access_rdy.csv")
}

compute_six_month_stats <- function(series, measure_id, label, note = "") {
  empty <- list(
    measure_id = measure_id, label = label, note = note,
    available = FALSE, n_periods = 0L, n_numeric = 0L, n_suppressed = 0L,
    window_start = NA_character_, window_end = NA_character_
  )
  if (is.null(series) || nrow(series) == 0) return(empty)
  numeric_rows <- series[!is.na(series$value), , drop = FALSE]
  n_supp <- sum(series$value_status == "suppressed", na.rm = TRUE)
  n_numeric <- nrow(numeric_rows)
  window_start <- format(min(series$period), "%b %Y")
  window_end <- format(max(series$period), "%b %Y")
  base <- list(
    measure_id = measure_id,
    label = label,
    note = note,
    available = n_numeric >= 2L,
    n_periods = nrow(series),
    n_numeric = n_numeric,
    n_suppressed = n_supp,
    window_start = window_start,
    window_end = window_end,
    series = series
  )
  if (n_numeric < 1L) return(c(base, list(
    latest_value = NA_real_, latest_period = window_end,
    previous_value = NA_real_, previous_period = NA_character_,
    mom_abs = NA_real_, mom_pct = NA_real_,
    six_month_abs = NA_real_, six_month_pct = NA_real_,
    six_month_avg = NA_real_, six_month_high = NA_real_, six_month_low = NA_real_,
    trend_reading = "unclear"
  )))
  latest_row <- series[nrow(series), , drop = FALSE]
  prev_row <- if (nrow(series) >= 2L) series[nrow(series) - 1L, , drop = FALSE] else latest_row
  first_num <- numeric_rows[1, , drop = FALSE]
  last_num <- numeric_rows[nrow(numeric_rows), , drop = FALSE]
  latest_val <- latest_row$value[1]
  prev_val <- prev_row$value[1]
  mom_abs <- if (!is.na(latest_val) && !is.na(prev_val)) latest_val - prev_val else NA_real_
  mom_pct <- pct_change(latest_val, prev_val)
  six_abs <- if (nrow(numeric_rows) >= 2L) last_num$value[1] - first_num$value[1] else NA_real_
  six_pct <- pct_change(last_num$value[1], first_num$value[1])
  c(base, list(
    latest_value = latest_val,
    latest_period = format(latest_row$period[1], "%b %Y"),
    latest_status = latest_row$value_status[1],
    previous_value = prev_val,
    previous_period = format(prev_row$period[1], "%b %Y"),
    previous_status = prev_row$value_status[1],
    mom_abs = mom_abs,
    mom_pct = mom_pct,
    six_month_abs = six_abs,
    six_month_pct = six_pct,
    six_month_avg = if (n_numeric >= 1L) round(mean(numeric_rows$value), 1) else NA_real_,
    six_month_high = if (n_numeric >= 1L) max(numeric_rows$value) else NA_real_,
    six_month_low = if (n_numeric >= 1L) min(numeric_rows$value) else NA_real_,
    trend_reading = classify_mhsds_trend_reading(numeric_rows)
  ))
}

classify_mhsds_trend_reading <- function(numeric_rows) {
  if (is.null(numeric_rows) || nrow(numeric_rows) < 2L) return("unclear")
  vals <- numeric_rows$value
  if (length(vals) < 2L) return("unclear")
  first_v <- vals[1]
  last_v <- vals[length(vals)]
  pct <- pct_change(last_v, first_v)
  if (is.na(pct)) return("unclear")
  if (abs(pct) <= 3) return("broadly stable")
  dirs <- sign(diff(vals))
  dirs <- dirs[dirs != 0]
  if (length(dirs) >= 3L && length(unique(dirs)) > 1L) {
    cv <- stats::sd(vals) / mean(vals)
    if (is.finite(cv) && cv > 0.08) return("volatile")
  }
  if (pct > 3) return("rising")
  if (pct < -3) return("falling")
  "broadly stable"
}

format_mh_value <- function(val, status = "numeric") {
  if (identical(status, "suppressed")) return("*")
  if (length(val) == 0L || is.na(val)) return("—")
  format(val, big.mark = ",")
}

format_mh_change <- function(abs_chg, pct_chg) {
  if (is.na(abs_chg) && is.na(pct_chg)) return("—")
  abs_txt <- if (is.na(abs_chg)) "—" else paste0(if (abs_chg >= 0) "+" else "", format(abs_chg, big.mark = ","))
  pct_txt <- if (is.na(pct_chg)) "" else paste0(" (", if (pct_chg >= 0) "+" else "", pct_chg, "%)")
  paste0(abs_txt, pct_txt)
}

mhsds_trend_reading_badge <- function(reading) {
  css <- switch(
    reading,
    rising = "worsening",
    falling = "improving",
    "broadly stable" = "stable",
    volatile = "unclear",
    unclear = "unclear",
    "unclear"
  )
  label <- switch(
    reading,
    rising = "Rising",
    falling = "Falling",
    "broadly stable" = "Broadly stable",
    volatile = "Volatile",
    unclear = "Unclear",
    "Unclear"
  )
  paste0('<span class="nhs-trend-badge nhs-trend-badge--', css, '">', esc(label), '</span>')
}

MHSDS_MEASURE_META <- list(
  MHS23 = list(
    label = "MHS23 — Open referrals at end of reporting period",
    note = "Stock measure (caseload-style open referrals). Higher counts are not automatically worse.",
    stock = TRUE,
    target = "No simple target",
    interpretation = "Rising open-referral stock; not automatically good or bad."
  ),
  MHS01 = list(
    label = "MHS01 — People with an open referral with services at end of reporting period",
    note = "Stock measure. Renamed nationally from April 2026 (previously 'people in contact'). Movement may reflect caseload, activity, coding or discharge.",
    stock = TRUE,
    target = "No simple target",
    interpretation = "May reflect caseload, activity, coding or discharge — not proof of access."
  ),
  MHS29 = list(
    label = "MHS29 — Contacts in reporting period",
    note = "Activity measure (contact volume). More contacts do not automatically mean better access.",
    stock = FALSE,
    target = "No simple target",
    interpretation = "Activity broadly flat or moving — not proof of access improvement."
  ),
  MHS69 = list(
    label = "MHS69 — CYP with at least two contacts (before 18th birthday)",
    note = "Access-style cohort measure. Financial-year counting logic may affect month-on-month movement — validate before use.",
    stock = FALSE,
    target = "Access-style metric; validate definition",
    interpretation = "Do not infer improvement without CYP data owner check — FY reset logic may apply."
  )
)

mh_desired_direction <- function(measure_id) {
  meta <- MHSDS_MEASURE_META[[measure_id]]
  if (is.null(meta)) return("No simple target")
  meta$target %||% "No simple target"
}

mh_interpretation_text <- function(measure_id, stats) {
  meta <- MHSDS_MEASURE_META[[measure_id]]
  base <- if (!is.null(meta)) meta$interpretation else ""
  if (measure_id == "MHS69" && identical(stats$trend_reading, "volatile")) {
    paste0(
      "MAJOR VALIDATION FLAG: very large month-on-month movement. ",
      "MHS69 has financial-year counting logic — do not read as sudden operational improvement. ",
      base
    )
  } else {
    base
  }
}

mh_six_month_summary_table <- function(stats_list) {
  hdr <- paste0(
    "<th scope=\"col\">", esc(c(
      "Measure", "Latest", "Previous month", "MoM change", "Six-month change",
      "Six-month avg", "Six-month high", "Six-month low", "Trend reading",
      "Desired direction / target", "Interpretation"
    )), "</th>", collapse = ""
  )
  rows <- vapply(names(stats_list), function(mid) {
    s <- stats_list[[mid]]
    cells <- c(
      esc(s$label),
      esc(paste0(format_mh_value(s$latest_value, s$latest_status %||% "numeric"), " (", s$latest_period %||% "—", ")")),
      esc(paste0(format_mh_value(s$previous_value, s$previous_status %||% "numeric"), " (", s$previous_period %||% "—", ")")),
      esc(format_mh_change(s$mom_abs, s$mom_pct)),
      esc(format_mh_change(s$six_month_abs, s$six_month_pct)),
      esc(format_mh_value(s$six_month_avg)),
      esc(format_mh_value(s$six_month_high)),
      esc(format_mh_value(s$six_month_low)),
      mhsds_trend_reading_badge(s$trend_reading %||% "unclear"),
      esc(mh_desired_direction(mid)),
      esc(mh_interpretation_text(mid, s))
    )
    paste0("<tr>", paste0("<td>", cells, "</td>", collapse = ""), "</tr>")
  }, character(1))
  paste0(
    '<div class="nhs-table-wrap"><table><thead><tr>', hdr,
    '</tr></thead><tbody>', paste(rows, collapse = "\n"), '</tbody></table></div>'
  )
}

line_trend_chart <- function(series, title) {
  if (is.null(series) || nrow(series) < 2L) {
    return('<p><em>Insufficient periods for trend chart.</em></p>')
  }
  numeric_idx <- which(!is.na(series$value))
  if (length(numeric_idx) < 2L) {
    return('<p><em>Too few numeric months for trend chart — suppressed or missing values excluded.</em></p>')
  }
  labels <- format(series$period, "%b %y")
  vals <- series$value
  w <- 320
  h <- 120
  pad <- 24
  plot_w <- w - 2 * pad
  plot_h <- h - 2 * pad
  mx <- max(vals[numeric_idx], na.rm = TRUE)
  mn <- min(vals[numeric_idx], na.rm = TRUE)
  if (!is.finite(mx) || !is.finite(mn)) return('<p><em>Insufficient numeric data for chart.</em></p>')
  if (mx == mn) mx <- mn + 1
  n <- nrow(series)
  xs <- pad + (seq_len(n) - 1) * plot_w / max(1, n - 1)
  ys <- pad + plot_h - (vals - mn) / (mx - mn) * plot_h
  ys[!is.finite(ys)] <- NA
  pts <- numeric_idx
  poly <- paste(paste(xs[pts], ys[pts], sep = ","), collapse = " ")
  circles <- mapply(function(i) {
    if (is.na(vals[i])) return("")
    fill <- if (identical(series$value_status[i], "suppressed")) "#ccc" else "#005eb8"
    paste0(
      '<circle cx="', round(xs[i], 1), '" cy="', round(ys[i], 1),
      '" r="3" fill="', fill, '"/>'
    )
  }, seq_len(n), SIMPLIFY = TRUE, USE.NAMES = FALSE)
  xlabels <- paste0(
    '<text x="', round(xs, 1), '" y="', h - 4, '" text-anchor="middle" font-size="9">',
    esc(labels), '</text>'
  )
  paste0(
    '<div class="nhs-chart"><h3>', esc(title), '</h3>',
    '<svg viewBox="0 0 ', w, ' ', h, '" width="100%" max-width="360" role="img" aria-label="',
    esc(title), '">',
    '<polyline fill="none" stroke="#005eb8" stroke-width="2" points="', poly, '"/>',
    paste(circles[nzchar(circles)], collapse = ""),
    paste(xlabels, collapse = ""),
    '</svg></div>'
  )
}

build_mhsds_six_month_stats <- function(trend_df, window_months = 6L) {
  build_window_stats(trend_df, MHSDS_MEASURE_META, window_months)
}

extract_windowed_series <- function(trend_df, measure_id = NULL, measure_name = NULL,
                                    window_months = 6L) {
  if (is.null(trend_df) || nrow(trend_df) == 0) return(NULL)
  sub <- trend_df
  if (!is.null(measure_id) && "measure_id" %in% names(sub)) {
    sub <- sub[trimws(sub$measure_id) == measure_id, , drop = FALSE]
  }
  if (!is.null(measure_name) && "measure_name" %in% names(sub)) {
    sub <- sub[grepl(measure_name, sub$measure_name, fixed = TRUE), , drop = FALSE]
  }
  if (nrow(sub) == 0) return(NULL)
  sub$period <- parse_stacked_period(sub$reporting_period_start)
  if (all(is.na(sub$period)) && "publication_period" %in% names(sub)) {
    sub$period <- parse_stacked_period(sub$publication_period)
  }
  sub <- sub[!is.na(sub$period), , drop = FALSE]
  if (nrow(sub) == 0) return(NULL)
  sub <- sub[order(sub$period), , drop = FALSE]
  sub <- sub[!duplicated(sub$period), , drop = FALSE]
  if (nrow(sub) > window_months) {
    sub <- sub[(nrow(sub) - window_months + 1L):nrow(sub), , drop = FALSE]
  }
  sub$value <- if ("value_status" %in% names(sub)) {
    ifelse(sub$value_status == "numeric", to_num(sub$metric_value), NA_real_)
  } else {
    to_num(sub$metric_value)
  }
  if (!"value_status" %in% names(sub)) {
    sub$value_status <- ifelse(is.na(sub$value), "missing", "numeric")
  }
  sub
}

extract_mhsds_measure_series <- function(trend_df, measure_id, window_months = 6L) {
  extract_windowed_series(trend_df, measure_id = measure_id, window_months = window_months)
}

build_window_stats <- function(trend_df, measure_specs, window_months = 6L) {
  stats <- lapply(names(measure_specs), function(key) {
    meta <- measure_specs[[key]]
    mid <- meta$measure_id %||% key
    use_measure_id <- if (!is.null(meta$measure_id)) {
      meta$measure_id
    } else if (is.null(meta$measure_name) || !nzchar(meta$measure_name)) {
      key
    } else {
      NULL
    }
    series <- extract_windowed_series(
      trend_df,
      measure_id = use_measure_id,
      measure_name = meta$measure_name %||% NULL,
      window_months = window_months
    )
    compute_six_month_stats(series, mid, meta$label, meta$note %||% "")
  })
  names(stats) <- names(measure_specs)
  stats
}

trend_reading_badge <- function(reading) {
  mhsds_trend_reading_badge(reading)
}

window_summary_table <- function(stats_list, window_label = "Six-month") {
  hdr <- paste0(
    "<th scope=\"col\">", esc(c(
      "Measure", "Latest", "Previous period", "Period-on-period change",
      paste0(window_label, " change"),
      paste0(window_label, " avg"), paste0(window_label, " high"),
      paste0(window_label, " low"), "Trend reading", "Note"
    )), "</th>", collapse = ""
  )
  rows <- vapply(stats_list, function(s) {
    cells <- c(
      esc(s$label),
      esc(paste0(format_mh_value(s$latest_value, s$latest_status %||% "numeric"), " (", s$latest_period %||% "—", ")")),
      esc(paste0(format_mh_value(s$previous_value, s$previous_status %||% "numeric"), " (", s$previous_period %||% "—", ")")),
      esc(format_mh_change(s$mom_abs, s$mom_pct)),
      esc(format_mh_change(s$six_month_abs, s$six_month_pct)),
      esc(format_mh_value(s$six_month_avg)),
      esc(format_mh_value(s$six_month_high)),
      esc(format_mh_value(s$six_month_low)),
      trend_reading_badge(s$trend_reading %||% "unclear"),
      esc(s$note)
    )
    paste0("<tr>", paste0("<td>", cells, "</td>", collapse = ""), "</tr>")
  }, character(1))
  paste0(
    '<div class="nhs-table-wrap"><table><thead><tr>', hdr,
    '</tr></thead><tbody>', paste(rows, collapse = "\n"), '</tbody></table></div>'
  )
}

window_stats_charts <- function(stats_list) {
  paste(vapply(stats_list, function(s) {
    if (is.null(s$series) || nrow(s$series) < 2L) return("")
    line_trend_chart(
      s$series,
      paste0(s$label, " (", s$window_start, " \u2013 ", s$window_end, ")")
    )
  }, character(1)), collapse = "")
}

window_stats_section <- function(stats_list, source_note, window_label = "Six-month",
                                 caveats = character()) {
  if (length(stats_list) == 0 || !any(vapply(stats_list, function(s) isTRUE(s$available), logical(1)))) {
    return(trend_not_available_section(source_note))
  }
  caveat_html <- if (length(caveats) > 0) {
    paste0("<ul>", paste0("<li>", esc(caveats), "</li>", collapse = ""), "</ul>")
  } else ""
  paste0(
    '<section class="nhs-section"><h2>', esc(window_label), ' trend summary</h2>',
    '<p>', esc(source_note), '</p>',
    window_summary_table(stats_list, window_label),
    window_stats_charts(stats_list),
    caveat_html,
    '</section>'
  )
}

latest_from_trend <- function(trend_df, measure_id = NULL, measure_name = NULL) {
  series <- extract_windowed_series(trend_df, measure_id, measure_name, window_months = 999L)
  if (is.null(series) || nrow(series) == 0) return(NULL)
  latest <- series[nrow(series), , drop = FALSE]
  list(
    value = latest$value[1],
    period = format(latest$period[1], "%b %Y"),
    status = latest$value_status[1]
  )
}

CSDS_WINDOW_MEASURES <- list(
  assess = list(
    measure_name = "Assessment",
    label = "CSDS — Assessment (CareActivities)",
    note = "Activity counts reflect coded CSDS submissions — not unique patients."
  ),
  clin = list(
    measure_name = "Clinical Intervention",
    label = "CSDS — Clinical Intervention (CareActivities)",
    note = "Largest activity type where numeric — confirm service scope locally."
  )
)

TT_WINDOW_MEASURES <- list(
  M001 = list(
    label = "M001 — Referrals received",
    note = "Referral counts move with demand and recording — descriptive only."
  ),
  M031 = list(
    label = "M031 — People accessing services",
    note = "Access counts differ from referrals received — do not conflate."
  ),
  M053 = list(
    label = "M053 — % accessing within 6 weeks (finished course)",
    note = "Percentage measure — denominator checks required."
  )
)

NOF_TREND_METRICS_META <- list(
  OF0005 = list(label = "OF0005 — % waiting over 52 weeks (community)", note = "Lower is better; check cohort definition."),
  OF0057 = list(label = "OF0057 — Urgent community response within 2 hours", note = "Higher is better; Q4 metric."),
  OF0016 = list(label = "OF0016 — MH crisis face-to-face within 24 hours", note = "Higher is better."),
  OF0086 = list(label = "OF0086 — Relative difference in costs", note = "Finance metric — local interpretation required.")
)

load_tt_trend <- function() {
  tt <- load_trend_file("trend_talking_therapies_rdy.csv")
  if (!is.null(tt) && nrow(tt) > 0) return(tt)
  ts <- load_tt_time_series()
  if (is.null(ts)) return(NULL)
  stacked <- list()
  for (mid in names(TT_WINDOW_MEASURES)) {
    sub <- extract_tt_rdy_ts(ts, mid)
    if (is.null(sub) || nrow(sub) < 2L) next
    if (nrow(sub) > 6L) sub <- sub[(nrow(sub) - 5L):nrow(sub), , drop = FALSE]
    for (i in seq_len(nrow(sub))) {
      r <- sub[i, , drop = FALSE]
      stacked[[length(stacked) + 1]] <- data.frame(
        source_id = "talking_therapies_monthly",
        publication_period = format(r$period[1], "%Y-%m"),
        reporting_period_start = r$REPORTING_PERIOD_START[1],
        reporting_period_end = r$REPORTING_PERIOD_END[1],
        org_code = "RDY",
        org_name = r$ORG_NAME2[1],
        measure_id = mid,
        measure_name = r$MEASURE_NAME[1],
        metric_value = r$value[1],
        metric_value_raw = as.character(r$value[1]),
        value_status = "numeric",
        source_file = "time_series_fallback",
        caveats = "From bundled time-series extract.",
        stringsAsFactors = FALSE
      )
    }
  }
  if (length(stacked) == 0) return(NULL)
  do.call(rbind, stacked)
}

load_nof_trend <- function() {
  load_trend_file("trend_nof_rdy.csv")
}

load_tt_time_series <- function() {
  load_rdy_glob("^rdy_talking_therapies.*time_series\\.csv$")
}

parse_period_start <- function(x) {
  x <- trimws(as.character(x))
  d <- suppressWarnings(as.Date(x, format = "%d/%m/%Y"))
  if (length(d) == 0 || all(is.na(d))) {
    d <- suppressWarnings(as.Date(x, format = "%Y-%m-%d"))
  }
  d
}

extract_mhsds_rdy_ts <- function(df, measure_id, breakdown = "Provider") {
  if (is.null(df)) return(NULL)
  sub <- df[trimws(df$MEASURE_ID) == measure_id, , drop = FALSE]
  if ("BREAKDOWN" %in% names(sub)) {
    sub <- sub[trimws(sub$BREAKDOWN) == breakdown, , drop = FALSE]
  }
  sub <- sub[trimws(sub$PRIMARY_LEVEL) == "RDY", , drop = FALSE]
  if (nrow(sub) == 0) return(NULL)
  sub$period <- parse_period_start(sub$REPORTING_PERIOD_START)
  sub$value <- to_num(sub$MEASURE_VALUE)
  sub <- sub[!is.na(sub$period) & !is.na(sub$value), , drop = FALSE]
  sub[order(sub$period), , drop = FALSE]
}

extract_tt_rdy_ts <- function(df, measure_id) {
  if (is.null(df)) return(NULL)
  sub <- df[
    trimws(df$MEASURE_ID) == measure_id &
      trimws(df$ORG_CODE2) == "RDY" &
      trimws(df$GROUP_TYPE) == "Provider",
    ,
    drop = FALSE
  ]
  if (nrow(sub) == 0) return(NULL)
  sub$period <- parse_period_start(sub$REPORTING_PERIOD_START)
  sub$value <- to_num(sub$MEASURE_VALUE)
  sub <- sub[!is.na(sub$period) & !is.na(sub$value), , drop = FALSE]
  sub[order(sub$period), , drop = FALSE]
}

compute_period_trend <- function(ts_sub, measure_label = "") {
  if (is.null(ts_sub) || nrow(ts_sub) < 2) {
    return(list(
      available = FALSE,
      n_periods = if (is.null(ts_sub)) 0L else as.integer(nrow(ts_sub)),
      measure_label = measure_label
    ))
  }
  latest <- ts_sub[nrow(ts_sub), , drop = FALSE]
  prev <- ts_sub[nrow(ts_sub) - 1L, , drop = FALSE]
  cur_v <- latest$value
  prev_v <- prev$value
  list(
    available = TRUE,
    n_periods = nrow(ts_sub),
    measure_label = measure_label,
    latest_period = format(latest$period, "%b %Y"),
    previous_period = format(prev$period, "%b %Y"),
    latest_value = cur_v,
    previous_value = prev_v,
    absolute_change = cur_v - prev_v,
    percent_change = pct_change(cur_v, prev_v),
    rolling_mean = if (nrow(ts_sub) >= 3) round(mean(ts_sub$value), 1) else NA_real_,
    series = ts_sub
  )
}

trend_summary_table <- function(trends_list) {
  rows <- lapply(trends_list, function(t) {
    if (is.null(t) || !isTRUE(t$available)) return(NULL)
    data.frame(
      Measure = t$measure_label,
      Latest = paste0(format(t$latest_value, big.mark = ","), " (", t$latest_period, ")"),
      Previous = paste0(format(t$previous_value, big.mark = ","), " (", t$previous_period, ")"),
      Absolute_change = paste0(
        if (t$absolute_change >= 0) "+" else "",
        format(t$absolute_change, big.mark = ",")
      ),
      Percent_change = if (is.na(t$percent_change)) {
        "—"
      } else {
        paste0(if (t$percent_change >= 0) "+" else "", t$percent_change, "%")
      },
      Periods_in_extract = t$n_periods,
      stringsAsFactors = FALSE
    )
  })
  rows <- rows[!vapply(rows, is.null, logical(1))]
  if (length(rows) == 0) return(NULL)
  do.call(rbind, rows)
}

trend_not_available_section <- function(what_needed) {
  paste0(
    '<section class="nhs-section nhs-trend-section">',
    '<h2>Trend analysis not available from current extract</h2>',
    '<p>The downloaded public data for this report does not contain multiple comparable periods ',
    'for the measures in the key figures table, or only a latest-period snapshot is present.</p>',
    '<p><strong>What would be needed:</strong></p>',
    bullet_list(what_needed),
    '</section>'
  )
}

trend_section <- function(trends_list, source_note, caveats = character()) {
  any_avail <- any(vapply(trends_list, function(t) isTRUE(t$available), logical(1)))
  if (!any_avail) {
    return(trend_not_available_section(source_note))
  }
  tbl <- trend_summary_table(trends_list)
  charts <- vapply(trends_list, function(t) {
    if (is.null(t) || !isTRUE(t$available) || t$n_periods < 2) return("")
    labels <- format(t$series$period, "%b %y")
    bar_chart(labels, t$series$value, paste0(t$measure_label, " — monthly trend (descriptive)"),
              sort_by_value = FALSE)
  }, character(1))
  roll_notes <- vapply(trends_list, function(t) {
    if (is.null(t) || !isTRUE(t$available)) return("")
    rm <- t$rolling_mean %||% NA_real_
    if (is.na(rm)) return("")
    paste0(
      t$measure_label, ": rolling mean across ", t$n_periods,
      " months = ", format(rm, big.mark = ","),
      " (descriptive only — not a performance target)."
    )
  }, character(1))
  roll_notes <- roll_notes[nzchar(roll_notes)]
  caveats_html <- if (length(caveats) > 0) {
    paste0('<ul class="nhs-list-compact nhs-trend-caveats">',
           paste0("<li>", esc(caveats), "</li>", collapse = ""), "</ul>")
  } else {
    ""
  }
  paste0(
    '<section class="nhs-section nhs-trend-section">',
    '<h2>Trend analysis (from downloaded public time series)</h2>',
    '<p>Descriptive period-on-period change only — not causal. Figures may be provisional; ',
    'suppression, rounding and definition changes may apply between months.</p>',
    '<p><strong>Source:</strong> ', esc(source_note), '</p>',
    html_table(tbl),
    paste(charts, collapse = "\n"),
    if (length(roll_notes) > 0) paste0("<p>", esc(roll_notes), "</p>", collapse = "") else "",
    caveats_html,
    '</section>'
  )
}

STANDARD_PROCESS_SUFFIX <- c(
  "Filter to RDY using ODS code and Dorset HealthCare trust name variants (see filter notes)",
  "Detect current vs historic periods from reporting columns or stacked trend files",
  "Select key figures from named measures, top-N tables or source-presence checks — not exhaustive lists",
  "Decide trend analysis only when ≥2 comparable periods exist and values are not suppressed",
  "Avoid overclaiming — no causal language, no fabricated targets, no recalculated peer comparators",
  "Document verification paths and what a human reviewer must still confirm"
)

format_comparator <- function(comparator_type, details = "") {
  if (nzchar(details)) {
    return(details)
  }
  switch(
    comparator_type,
    official_standard = "Official standard or target from the published source (if present in extract).",
    peer_median = "Peer median and published rank from NHS England source file — not a local target.",
    previous_period = "Latest period compared with the previous comparable period in the downloaded public extract.",
    descriptive_history = "Descriptive history across multiple published rows — not a numeric performance trend.",
    none = "No verified target or comparator in the current public extract.",
    validation_only = "Source validation only — confirms RDY presence or participation, not performance standing.",
    "No comparator in the current public extract."
  )
}

trend_badge_css <- function(label) {
  switch(
    label,
    "Improving" = "improving",
    "Worsening" = "worsening",
    "Broadly stable" = "stable",
    "Mixed / unclear" = "unclear",
    "Definition check required" = "unclear",
    "Not available from current extract" = "na",
    "Source validation only" = "validation",
    "na"
  )
}

classify_trend_direction <- function(trend_obj = NULL, polarity = "unknown",
                                     override_label = NULL, override_note = NULL,
                                     stable_pct_threshold = 2) {
  if (!is.null(override_label) && nzchar(as.character(override_label)[1])) {
    return(list(
      label = override_label,
      note = override_note %||% "",
      css = trend_badge_css(override_label)
    ))
  }
  if (identical(polarity, "validation_only")) {
    return(list(
      label = "Source validation only",
      note = "Trend direction is not meaningful for this source-validation figure.",
      css = "validation"
    ))
  }
  if (identical(polarity, "definition")) {
    return(list(
      label = "Definition check required",
      note = "Metric polarity or meaning must be confirmed before interpreting direction.",
      css = "unclear"
    ))
  }
  if (is.null(trend_obj) || !isTRUE(trend_obj$available) || trend_obj$n_periods < 2) {
    n <- if (is.null(trend_obj)) 0L else trend_obj$n_periods %||% 0L
    note <- if (n <= 1) {
      "Trend direction is marked as unavailable because the current extract contains fewer than two comparable periods."
    } else {
      "Trend could not be computed from the available rows."
    }
    return(list(label = "Not available from current extract", note = note, css = "na"))
  }
  if (identical(polarity, "unknown") || identical(polarity, "none")) {
    chg <- trend_obj$absolute_change
    pct <- trend_obj$percent_change
    note <- paste0(
      "Latest ", format(trend_obj$latest_value, big.mark = ","), " (", trend_obj$latest_period, ") vs ",
      format(trend_obj$previous_value, big.mark = ","), " (", trend_obj$previous_period, "). ",
      "Direction is descriptive only — metric polarity is not verified in this demo."
    )
    return(list(label = "Mixed / unclear", note = note, css = "unclear"))
  }
  chg <- trend_obj$absolute_change
  pct <- trend_obj$percent_change
  stable <- (!is.na(pct) && abs(pct) <= stable_pct_threshold) ||
    (is.na(pct) && abs(chg) < max(1, abs(trend_obj$previous_value) * 0.02))
  if (stable) {
    return(list(
      label = "Broadly stable",
      note = paste0(
        "Change between ", trend_obj$previous_period, " and ", trend_obj$latest_period,
        " is small (", if (!is.na(pct)) paste0(if (pct >= 0) "+" else "", pct, "%") else paste0(if (chg >= 0) "+" else "", format(chg, big.mark = ",")),
        ") — descriptive only."
      ),
      css = "stable"
    ))
  }
  higher_better <- identical(polarity, "higher_better")
  lower_better <- identical(polarity, "lower_better")
  improving <- if (higher_better) chg > 0 else if (lower_better) chg < 0 else NA
  label <- if (isTRUE(improving)) "Improving" else if (isFALSE(improving)) "Worsening" else "Mixed / unclear"
  list(
    label = label,
    note = paste0(
      format(trend_obj$latest_value, big.mark = ","), " (", trend_obj$latest_period, ") vs ",
      format(trend_obj$previous_value, big.mark = ","), " (", trend_obj$previous_period, "). ",
      "Descriptive period-on-period change only — not causal."
    ),
    css = trend_badge_css(label)
  )
}

trend_badge_html <- function(trend_info) {
  paste0(
    '<span class="nhs-trend-badge nhs-trend-badge--', esc(trend_info$css), '" title="',
    esc(trend_info$note), '">', esc(trend_info$label), '</span>'
  )
}

comparator_label_html <- function(comparator_type) {
  tag <- switch(
    comparator_type,
    official_standard = "Official standard",
    peer_median = "Peer median",
    previous_period = "Previous period",
    descriptive_history = "Descriptive history",
    validation_only = "Source validation",
    "No comparator"
  )
  paste0('<span class="nhs-comparator-label">', esc(tag), '</span> ')
}

format_kfe_latest <- function(value_text, period_text = NULL, suppressed = FALSE) {
  if (suppressed) return("* (suppressed in public extract)")
  if (is.null(value_text) || (length(value_text) == 1 && (is.na(value_text) || trimws(as.character(value_text)) == ""))) {
    return("—")
  }
  v <- as.character(value_text)
  if (!is.null(period_text) && nzchar(period_text)) paste0(v, " (", period_text, ")") else v
}

kfe_from_trend <- function(trend_obj, value_text = NULL, period_text = NULL) {
  if (!is.null(value_text)) {
    format_kfe_latest(value_text, period_text)
  } else if (isTRUE(trend_obj$available)) {
    format_kfe_latest(format(trend_obj$latest_value, big.mark = ","), trend_obj$latest_period)
  } else {
    "—"
  }
}

key_figures_explained_section <- function(specs, intro = NULL, supporting_html = "",
                                        title = "Main metric table", show_trend = TRUE,
                                        show_what = FALSE, trend_note = NULL,
                                        period_caption = NULL,
                                        show_standard = FALSE, show_peer = FALSE,
                                        show_validation = FALSE,
                                        comparator_header = "Peer position / comparator") {
  if (length(specs) == 0) return("")
  intro_text <- intro %||%
    paste(
      "Plain-English explanation of the most useful measures in this brief.",
      "Trend direction uses downloaded public historic data only where at least two comparable periods exist."
    )
  trend_note_html <- if (!is.null(trend_note) && nzchar(trend_note)) {
    paste0("<p><em>", esc(trend_note), "</em></p>")
  } else {
    ""
  }
  period_html <- period_caption_html(period_caption)
  rows_html <- vapply(specs, function(spec) {
    std_text <- spec$standard_detail %||% ""
    if (nzchar(std_text) && !is.null(spec$standard_metadata)) {
      std_text <- paste0(std_text, " ", standard_metadata_cite(spec$standard_metadata))
    }
    peer_text <- spec$peer_detail %||% ""
    if (!nzchar(peer_text) && nzchar(spec$comparator_detail %||% "")) {
      if (identical(spec$comparator_type %||% "", "peer_median")) {
        peer_text <- spec$comparator_detail
      }
    }
    cmp_text <- if (!show_standard && !show_peer) {
      if (nzchar(spec$comparator_detail %||% "")) {
        spec$comparator_detail
      } else {
        format_comparator(spec$comparator_type %||% "none", "")
      }
    } else {
      ""
    }
    trend_cell <- if (show_trend) {
      if (!is.null(spec$trend_label) && nzchar(spec$trend_label)) {
        trend_badge_html(list(
          label = spec$trend_label,
          note = spec$trend_note %||% "",
          css = trend_badge_css(spec$trend_label)
        ))
      } else {
        trend_info <- classify_trend_direction(
          trend_obj = spec$trend,
          polarity = spec$polarity %||% "unknown",
          override_label = spec$trend_override,
          override_note = spec$trend_note
        )
        trend_badge_html(trend_info)
      }
    } else {
      ""
    }
    judgement <- spec$judgement %||% spec$interpretation %||% ""
    row_parts <- c(
      paste0("<th scope=\"row\">", esc(spec$figure), "</th>"),
      if (show_what) paste0("<td>", esc(spec$what), "</td>") else character(),
      paste0("<td>", esc(spec$latest), "</td>"),
      if (show_standard) paste0("<td>", std_text, "</td>") else character(),
      if (show_peer) paste0("<td>", esc(peer_text), "</td>") else character(),
      if (!show_standard && !show_peer) paste0("<td>", esc(cmp_text), "</td>") else character(),
      if (show_trend) paste0("<td>", trend_cell, "</td>") else character(),
      if (show_validation) {
        paste0("<td>", validation_badge_html(
          spec$validation_status %||% "",
          spec$validation_note
        ), "</td>")
      } else {
        character()
      },
      paste0("<td>", esc(judgement), "</td>"),
      paste0("<td class=\"nhs-human-check\">", esc(spec$human_check), "</td>")
    )
    paste0("<tr>", paste(row_parts, collapse = ""), "</tr>")
  }, character(1))
  header_parts <- c(
    '<th scope="col">Figure / measure</th>',
    if (show_what) '<th scope="col">What it means</th>' else character(),
    '<th scope="col">Latest value</th>',
    if (show_standard) '<th scope="col">Standard / expected</th>' else character(),
    if (show_peer) '<th scope="col">Peer median</th>' else character(),
    if (!show_standard && !show_peer) {
      paste0('<th scope="col">', esc(comparator_header), '</th>')
    } else {
      character()
    },
    if (show_trend) '<th scope="col">Trend</th>' else character(),
    if (show_validation) '<th scope="col">Validation status</th>' else character(),
    '<th scope="col">Judgement</th>',
    '<th scope="col">Human check</th>'
  )
  table_class <- "nhs-kfe-table"
  if (!show_what && !show_trend && !show_standard && !show_peer && !show_validation) {
    table_class <- "nhs-kfe-table nhs-kfe-table--brief"
  }
  paste0(
    '<section class="nhs-section key-figures-explained">',
    '<h2>', esc(title), '</h2>',
    '<p>', intro_text, '</p>',
    period_html,
    trend_note_html,
    '<div class="nhs-table-wrap"><table class="', table_class, '">',
    '<thead><tr>',
    paste(header_parts, collapse = ""),
    '</tr></thead><tbody>',
    paste(rows_html, collapse = "\n"),
    '</tbody></table></div>',
    supporting_html,
    '</section>'
  )
}

extend_process_steps <- function(steps) {
  c(steps, STANDARD_PROCESS_SUFFIX)
}

how_to_read_section <- function(items) {
  paste0(
    '<section class="nhs-section"><h2>How to read this report</h2>',
    bullet_list(items),
    '</section>'
  )
}

build_commentary_card <- function(title, flag, flag_class, dl_pairs) {
  dl_html <- paste(vapply(names(dl_pairs), function(nm) {
    val <- dl_pairs[[nm]]
    paste0("<dt>", esc(nm), "</dt><dd>", esc(paste(as.character(val), collapse = " ")), "</dd>")
  }, character(1)), collapse = "")
  paste0(
    '<article class="nhs-metric-card">',
    '<p class="nhs-metric-card-title"><strong>', esc(title), '</strong> ',
    nof_agent_flag_badge(flag, flag_class), '</p>',
    '<dl>', dl_html, '</dl></article>'
  )
}

measure_commentary_section <- function(cards, intro = NULL) {
  commentary_cards_block(cards)
}

theme_commentary_section <- function(cards, intro = NULL) {
  commentary_cards_block(cards)
}

bp_questions_html <- function(questions) {
  if (is.null(questions) || length(questions) == 0) return("")
  if (is.list(questions[[1]]) && !is.null(questions[[1]]$q)) {
    items <- vapply(questions, function(x) {
      paste0(
        "<li><strong>", esc(x$q), "</strong>",
        '<p class="nhs-bp-expl">', esc(x$expl), "</p></li>"
      )
    }, character(1))
    paste0('<ul class="nhs-list-compact nhs-bp-questions">', paste(items, collapse = ""), "</ul>")
  } else {
    bullet_list(unlist(questions))
  }
}

standard_bp_questions <- function() {
  list(
    list(
      q = "Is this the same definition used locally?",
      expl = paste(
        "Public datasets may use national definitions. Local dashboards may use different filters,",
        "denominators or reporting dates."
      )
    ),
    list(
      q = "Is this still the latest position?",
      expl = paste(
        "Public data may be a delayed publication. Local operational data may have moved on",
        "since the reporting period shown."
      )
    ),
    list(
      q = "Who owns the narrative?",
      expl = paste(
        "The accountable service, finance, workforce, quality or BI owner should confirm",
        "the explanation before the finding is used."
      )
    ),
    list(
      q = "Is this a performance issue, a data quality issue, or a service model issue?",
      expl = paste(
        "A public figure can be affected by coding, exclusions, small numbers, local pathways",
        "or publication rules."
      )
    )
  )
}

agent_brief_sections <- function(config, key_findings_html, verify_body_html,
                               supporting_html = "") {
  grouped_collapsed <- if (!is.null(config$grouped_findings) && length(config$grouped_findings) > 0) {
    findings_group_section(config$grouped_findings)
  } else {
    ""
  }
  summary_collapsed <- if (!is.null(config$agent_summary) && length(config$agent_summary) > 0) {
    agent_summary_section(config$agent_summary)
  } else {
    ""
  }
  audit_trail_extra <- paste0(
    if (nzchar(grouped_collapsed)) {
      collapsible_details("Key findings by review area (detail)", grouped_collapsed)
    } else {
      ""
    },
    if (nzchar(summary_collapsed)) {
      collapsible_details("Draft interpretation (detail)", summary_collapsed)
    } else {
      ""
    },
    if (!is.null(config$prompt_excerpt) && nzchar(config$prompt_excerpt)) {
      collapsible_details("Prompt excerpt", agent_prompt_box(config$prompt_excerpt))
    } else {
      ""
    },
    supporting_html
  )
  scope <- config$scope %||% list()
  paste0(
    what_agent_asked_section(config$question, config$dataset_line),
    if (isTRUE(config$provider_scope)) provider_scope_badge_html() else "",
    data_used_section(config),
    scope_section(scope$can %||% list(), scope$cannot %||% list()),
    headline_reading_section(config$headline %||% list()),
    if (!is.null(config$priority_callout)) config$priority_callout else "",
    key_findings_html,
    if (!is.null(config$why_useful)) why_this_is_useful_section(config$why_useful) else "",
    human_checks_section(config$human_checks),
    if (!is.null(config$bottom_line)) bottom_line_section(config$bottom_line) else "",
    verify_section(
      config$verify_intro %||% verify_intro_short(),
      paste0(verify_body_html, audit_trail_extra)
    ),
    human_review_warning()
  )
}

`%||%` <- function(x, y) if (is.null(x) || length(x) == 0 || (length(x) == 1 && is.na(x))) y else x

write_public_report <- function(filename, title, subtitle, body) {
  html <- paste0(
    '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">',
    '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
    '<title>', esc(title), '</title>',
    '<link rel="stylesheet" href="../assets/styles.css">',
    '<link rel="stylesheet" href="../assets/nhs-report.css">',
    '</head><body class="nhs-report">',
    '<a href="#main-content" class="skip-link">Skip to main content</a>',
    '<div class="nhs-report-nav"><a href="../draft-reports.html">&larr; All reports</a>',
    '<a href="../agent-operating-model.html">Agent operating model</a>',
    '<a href="../docs-html/public-data/PUBLIC_REPORTS_METHOD.html">Report method</a></div>',
    '<header class="nhs-report-header"><p class="report-meta">', REPORT_BADGE_META, '</p>',
    '<h1>', esc(title), '</h1><p class="report-meta">', esc(subtitle), '</p></header>',
    '<main id="main-content" class="nhs-report-main">',
    std_caveat, body,
    '<p><a href="../draft-reports.html">&larr; Back to draft reports</a></p>',
    '</main>',
    '<footer class="nhs-report-footer"><p><strong>Human review required.</strong> ',
    'Public-data demonstration only. Not an official Dorset HealthCare report. ',
    'Not NHS-endorsed. No patient-identifiable information.</p>',
    '<p style="margin-top:0.75rem;opacity:0.85;">Regenerate: Rscript site/R/03_render_public_reports.R</p>',
    '</footer><script src="../assets/site.js"></script></body></html>'
  )
  writeLines(html, file.path(reports_dir, filename), useBytes = TRUE)
  cat("Written:", filename, "\n")
}

# --- A. Public performance overview (NOF) ------------------------------------

build_performance_overview <- function() {
  nof_full <- load_nof_full_rdy()
  nof_demo <- load_demo("demo_nof_overview.csv", required = TRUE)
  nof_raw <- load_nof_raw()
  reg_row <- read_register_row("nof_mh_community")
  assurance <- load_demo("demo_assurance_profile.csv")

  if (is.null(nof_full) && is.null(nof_demo)) {
    write_public_report(
      "public-performance-overview.html",
      "Dorset HealthCare Public Performance Overview",
      "Source data not available",
      '<section class="nhs-section"><h2>Data availability</h2><p><code>demo_nof_overview.csv</code> was not found. Run the public-data pipeline first.</p></section>'
    )
    return(invisible(NULL))
  }

  nof <- if (!is.null(nof_full)) nof_full else nof_demo
  latest_q <- nof_latest_quarter(nof)

  display <- nof[
    trimws(nof$Quarter) == latest_q &
      vapply(nof$Metric_ID, is_nof_raw_metric, logical(1)) &
      !is.na(to_num(nof$Value)),
    ,
    drop = FALSE
  ]
  display$Value_num <- to_num(display$Value)
  display$Rank_num <- to_num(display$Rank)

  ranked <- display[!is.na(display$Rank_num), , drop = FALSE]
  rank_table <- ranked[order(ranked$Rank_num), c(
    "Quarter", "Metric_ID", "Metric_description", "Domain", "Value", "Median_value", "Rank"
  )]
  audit_source <- ranked[order(ranked$Rank_num), , drop = FALSE]

  n_metrics <- nrow(display)
  n_domains <- length(unique(display$Domain))
  avg_rank <- if (nrow(ranked) > 0) round(mean(ranked$Rank_num), 1) else NA

  domain_summary <- aggregate(Value_num ~ Domain, display, function(x) length(x))
  names(domain_summary) <- c("Domain", "Metric_count")

  audit_df <- build_nof_audit_df(audit_source, nof_raw, reg_row)
  write_nof_audit_csv(audit_df)
  write_nof_audit_md(audit_df, latest_q)

  raw_paths <- split_register_paths(if (!is.null(reg_row)) reg_row$downloaded_file_path else "")
  raw_data_path <- raw_paths[grepl("-data\\.csv", raw_paths, ignore.case = TRUE)][1]
  if (length(raw_data_path) == 0 || is.na(raw_data_path)) raw_data_path <- raw_paths[1]
  raw_display <- path_for_display(raw_data_path)
  raw_url <- if (!is.null(reg_row)) reg_row$source_url else ""

  kpis <- list(
    list(value = n_metrics, label = paste("Raw NOF metrics,", latest_q)),
    list(value = n_domains, label = "Performance domains"),
    list(value = if (is.na(avg_rank)) "—" else avg_rank, label = "Mean published rank (where ranked)"),
    list(value = nrow(ranked), label = "Metrics with published rank")
  )

  assurance_html <- if (!is.null(assurance)) {
    html_table(assurance[, c("source", "label", "rdy_rows")])
  } else {
    "<p><em>Assurance summary not available.</em></p>"
  }

  period_text <- paste(latest_q, "raw metrics (OF0xxx); reporting dates vary by metric (see Reporting_date column).")

  nof_trend <- load_nof_trend()
  nof_q_stats <- if (!is.null(nof_trend) && nrow(nof_trend) > 0) {
    build_window_stats(nof_trend, NOF_TREND_METRICS_META, 3L)
  } else {
    list()
  }
  nof_has_q_trend <- length(nof_q_stats) > 0 &&
    any(vapply(nof_q_stats, function(s) isTRUE(s$available), logical(1)))
  nof_trend_window <- if (nof_has_q_trend) {
    paste0(nof_q_stats[[1]]$window_start, " \u2013 ", nof_q_stats[[1]]$window_end)
  } else {
    latest_q
  }

  nof_trend_for_metric <- function(metric_id) {
    if (is.null(nof_trend)) return(list(available = FALSE, n_periods = 0L))
    meta <- NOF_TREND_METRICS_META[[metric_id]]
    label <- if (!is.null(meta)) meta$label else metric_id
    extract_stacked_trend(nof_trend, measure_id = metric_id, label = paste0(label, " (quarterly)"))
  }

  key_figures <- paste0(
    kpi_row(kpis),
    '<p><em>Mean published rank uses NHS England Rank column — not recomputed. See &ldquo;How to read this table&rdquo; below for column definitions.</em></p>',
    '<h3>Metrics by domain (', esc(latest_q), ')</h3>', html_table(domain_summary),
    nof_how_to_read_table(),
    '<h3>Raw NOF metrics with NHS England published rank and peer median (', esc(latest_q), ')</h3>',
    html_table(rank_table, 50)
  )

  commentary_html <- nof_metric_commentary_section(audit_source)

  nof_lookup <- nof_commentary_lookup()
  nof_kfe_specs <- lapply(seq_len(nrow(audit_source)), function(i) {
    row <- audit_source[i, , drop = FALSE]
    id <- trimws(as.character(row$Metric_ID))
    entry <- nof_lookup[[id]]
    spec <- NOF_METRIC_SPEC[[id]]
    plain <- if (!is.null(entry)) entry$plain_meaning else trimws(as.character(row$Metric_description))
    human <- if (!is.null(entry)) entry$human_check else "Confirm metric definition and rank polarity against NHS England NOF specification."
    rank_txt <- trimws(as.character(row$Rank))
    median_txt <- trimws(as.character(row$Median_value))
    val_txt <- trimws(as.character(row$Value))
    peer_pos <- if (is.na(to_num(median_txt)) && is.na(to_num(rank_txt))) {
      "Median and rank not published for this metric in the extract."
    } else {
      format_peer_position_short(median_txt, rank_txt)
    }
    nof_trend_obj <- if (id %in% names(NOF_TREND_METRICS_META)) nof_trend_for_metric(id) else list(available = FALSE)
    pol <- if (!is.null(spec)) spec$polarity else "unknown"
    trend_lbl <- if (isTRUE(nof_trend_obj$available)) {
      nof_trend_direction_label(nof_trend_obj, pol)$label
    } else {
      "Not available"
    }
    list(
      figure = paste0(id, " — ", plain),
      what = plain,
      latest = paste0(val_txt, " (", latest_q, ")"),
      standard_detail = if (!is.null(spec)) spec$standard else "Confirm against NHS England NOF specification.",
      standard_metadata = NOF_SPEC_META,
      peer_detail = peer_pos,
      trend = if (isTRUE(nof_trend_obj$available)) nof_trend_obj else NULL,
      polarity = pol,
      trend_label = trend_lbl,
      trend_override = if (isTRUE(nof_trend_obj$available)) NULL else "Not available",
      trend_note = if (isTRUE(nof_trend_obj$available)) {
        paste0("Quarter-over-quarter from trend_nof_rdy.csv (", nof_trend_window, ") — descriptive only.")
      } else {
        paste0("Cross-sectional snapshot for ", latest_q, " — no historic NOF trend for this metric.")
      },
      validation_status = if (!is.null(spec)) spec$validation_status else "Definition check required",
      judgement = if (!is.null(spec)) {
        nof_judgement_text(id, row, spec, nof_trend_obj)
      } else {
        nof_agent_reading(entry, row)
      },
      human_check = short_human_check(human)
    )
  })

  kfe_supporting <- paste0(
    collapsible_details("Full metric table and domain summary", key_figures),
    collapsible_details("Additional metric commentary cards", commentary_html),
    if (nof_has_q_trend) {
      wrap_trend_collapsible(
        window_stats_section(
          nof_q_stats,
          paste0("Quarter-over-quarter Value trends from trend_nof_rdy.csv (", nof_trend_window, "). Median/rank not trended."),
          "Quarter-over-quarter",
          c(
            "Trend available for 4 headline metrics only; all others are latest-quarter snapshots.",
            "Descriptive quarter-on-quarter change only — not causal."
          )
        ),
        "Quarter-over-quarter headline metrics"
      )
    } else ""
  )

  nof_trend_note <- if (nof_has_q_trend) {
    paste(
      "Trend is available for 4 headline metrics only (OF0005, OF0057, OF0016, OF0086).",
      "Most metrics remain latest-quarter snapshots.",
      "Peer median and published rank are NHS England pass-through fields — not recalculated."
    )
  } else {
    paste(
      "There is no multi-quarter NOF trend in the current extract.",
      "Peer median and published rank are NHS England fields — not recalculated."
    )
  }

  kfe_html <- key_figures_explained_section(
    nof_kfe_specs,
    paste(
      "For each metric: latest RDY value, published standard where known, peer median/rank,",
      "quarter-over-quarter trend where available, and a first-draft judgement for human review."
    ),
    supporting_html = "",
    show_trend = TRUE,
    show_standard = TRUE,
    show_peer = TRUE,
    show_validation = TRUE,
    trend_note = nof_trend_note,
    period_caption = paste0("Latest quarter snapshot: ", latest_q,
                            if (nof_has_q_trend) paste0("; QoQ trend window: ", nof_trend_window) else "")
  )

  config <- list(
    question = paste(
      "Using the latest public NHS Oversight Framework file for mental health and community trusts,",
      "prepare a first-draft RDY performance brief: which raw metrics exist,",
      "what do published median/rank fields show, and what must a human verify?"
    ),
    dataset_line = "NHS Oversight Framework MH/community CSV (RDY)",
    prompt_excerpt = paste(
      "Locate NHS England NOF MH/community trust CSV. Filter Trust_code=RDY.",
      "Use latest quarter raw metrics (OF0xxx) only. Do not recalculate median or rank.",
      "Summarise domains and ranked metrics descriptively. Flag rank-direction uncertainty.",
      "No causal claims. Include verification paths to raw rows.",
      sep = "\n"
    ),
    scope = list(
      can = c(
        "Show where RDY appears to sit against published peer median and rank fields.",
        "Help identify areas that may need local review."
      ),
      cannot = c(
        "Explain why a metric is high or low.",
        "Confirm whether the public figure matches the latest internal position, local definitions or operational context."
      )
    ),
    headline = c(
      "The clearest public-data review flags are long-stay adult mental health inpatients (OF0063: 46.55 vs peer median 24.52, rank 47), UCR 2-hour performance (below peer median), mental health crisis 24-hour face-to-face contact, and relative cost index.",
      if (nof_has_q_trend) "UCR is also showing a short falling trend in the quarter-over-quarter extract." else NULL,
      if (nof_has_q_trend) {
        paste0("Trend is available for 4 headline metrics only (", nof_trend_window, "); most metrics remain latest-quarter snapshots.")
      } else {
        "There is no multi-quarter NOF trend in this extract."
      }
    ),
    priority_callout = priority_callout_html(c(
      "OF0063 — adult MH inpatients with length of stay over 60 days: RDY 46.55 vs peer median 24.52 (rank 47). Priority validation before any review use.",
      "OF0057 — UCR 2-hour performance: below peer median; confirm local UCR position and whether short trend is falling.",
      "OF0016 — crisis face-to-face within 24 hours: below peer median — crisis pathway owner review.",
      "OF0086 — relative cost index: above 100 and peer median — finance/productivity review."
    )),
    why_useful = c(
      "The agent has not replaced local judgement.",
      "It rapidly found the public RDY rows, summarised peer position, checked metric polarity where possible, highlighted likely review areas, and produced an audit trail for a human owner."
    ),
    bottom_line = paste(
      "The public NOF file suggests RDY's clearest review priorities are long-stay adult mental health inpatients,",
      "urgent community response performance, crisis 24-hour contact, and relative cost index.",
      "Several metrics look strong on published rank but still need local definition checks before any operational use.",
      "A Business & Performance Partner should validate these figures with service and finance owners before drawing conclusions."
    ),
    grouped_findings = nof_build_grouped_findings(audit_source, nof_lookup),
    data_used_html = paste0(
      '<ul class="nhs-list-compact">',
      '<li>NHS Oversight Framework (<code>demo_nof_overview.csv</code> + full RDY processed extract)</li>',
      if (nof_has_q_trend) '<li><code>trend_nof_rdy.csv</code> — quarter-over-quarter headline metrics</li>' else "",
      '<li>Assurance index (<code>demo_assurance_profile.csv</code>) where available</li>',
      '</ul>'
    ),
    period = if (nof_has_q_trend) paste0(latest_q, " snapshot; quarter-over-quarter trend ", nof_trend_window) else period_text,
    trend_available = if (nof_has_q_trend) {
      paste0("Yes — quarter-over-quarter Value trends (", nof_trend_window, " from trend_nof_rdy.csv); median/rank latest quarter only")
    } else {
      "Not available — cross-sectional NOF snapshot only"
    },
    agent_summary = c(
      paste0("Across ", latest_q, ", ", n_metrics, " raw NOF metrics span ", n_domains, " domains — several show strong published ranks (e.g. OF0005, OF0079) but need definition checks."),
      "The agent flags OF0063, OF0057, OF0016 and OF0086 for local review where RDY sits below peer median on access/long-stay or above on cost index.",
      "Finance metrics (OF0079, OF0081, OF0086) need finance-owner interpretation — the agent does not assume surplus/deficit meaning.",
      "This snapshot cannot explain why performance changed or what action is needed without local validation."
    ),
    human_checks = standard_human_checks(),
    verify_intro = verify_intro_short(
      paste0(
        '<li><a href="../public-data/processed/demo_nof_overview.csv">demo_nof_overview.csv</a></li>',
        '<li><a href="../public-data/metadata/public_report_audit_nof_overview.csv">Audit CSV</a></li>',
        '<li><a href="../docs-html/public-data/metadata/public_report_audit_nof_overview.html">Audit MD</a></li>'
      ),
      "Each ranked metric traces to the NHS England NOF file via the audit CSV — median and rank are pass-through fields, not recalculated."
    )
  )

  verify_body <- nof_audit_verify_body(audit_df, raw_display, raw_url)

  body <- paste0(
    agent_brief_sections(config, kfe_html, verify_body, kfe_supporting),
    collapsible_details("Related assurance sources index", assurance_html)
  )

  write_public_report(
    "public-performance-overview.html",
    "Worked example: AI-assisted analysis of NHS Oversight Framework data",
    paste("NHS Oversight Framework — RDY first-draft brief (", latest_q, ")", sep = ""),
    body
  )
}

# --- B. Mental health access profile (MHSDS) ---------------------------------

build_mh_profile <- function() {
  access_trend <- load_mhsds_access_trend()
  if (is.null(access_trend) || nrow(access_trend) == 0) {
    gap <- file.path(metadata_dir, "mhsds_trend_gap_note.md")
    gap_html <- if (file.exists(gap)) {
      paste0("<p>See <code>", esc(path_for_display(gap)), "</code> for gap details.</p>")
    } else {
      "<p>Run <code>Rscript site/public-data/05_download_historic_public_data.R</code> first.</p>"
    }
    write_public_report(
      "public-mh-access-profile.html",
      "Worked example: AI-assisted MHSDS public-data briefing",
      "MHSDS — six-month trend data not available",
      paste0(
        '<section class="nhs-section"><h2>Data availability</h2>',
        "<p><code>trend_mhsds_access_rdy.csv</code> was not found or is empty.</p>",
        gap_html, "</section>"
      )
    )
    return(invisible(NULL))
  }

  mh <- load_demo("demo_mhsds_activity.csv", required = FALSE)
  n_supp_demo <- if (!is.null(mh)) sum(is_suppressed(mh$MEASURE_VALUE)) else 0L

  mh_stats <- build_mhsds_six_month_stats(access_trend, 6L)
  names(mh_stats) <- names(MHSDS_MEASURE_META)

  trend_window <- paste0(
    mh_stats[[1]]$window_start, " \u2013 ", mh_stats[[1]]$window_end,
    " (", mh_stats[[1]]$n_periods, " months, provisional MHSDS, Provider/RDY)"
  )

  rising <- vapply(mh_stats, function(s) identical(s$trend_reading, "rising"), logical(1))
  falling <- vapply(mh_stats, function(s) identical(s$trend_reading, "falling"), logical(1))
  stable <- vapply(mh_stats, function(s) identical(s$trend_reading, "broadly stable"), logical(1))
  volatile <- vapply(mh_stats, function(s) identical(s$trend_reading, "volatile"), logical(1))
  unclear_measures <- names(mh_stats)[vapply(mh_stats, function(s) {
    s$n_numeric < 2L || identical(s$trend_reading, "unclear") || s$n_suppressed > 0L
  }, logical(1))]

  stock_rising <- any(c("MHS23", "MHS01") %in% names(mh_stats)[rising])
  contacts_flat <- "MHS29" %in% names(mh_stats)[stable] || identical(mh_stats$MHS29$trend_reading, "broadly stable")

  headline_bullets <- c(
    paste0(
      "MHSDS access and activity profile for Dorset HealthCare (RDY as provider), ",
      mh_stats[[1]]$window_start, " to ", mh_stats[[1]]$window_end, "."
    ),
    if (stock_rising && contacts_flat) {
      "Caseload-style measures (MHS23, MHS01) rose over six months while contact volume (MHS29) was broadly stable. This may indicate increased open-referral pressure, reduced contact intensity, coding effects, discharge effects, or case mix — local validation needed."
    } else {
      NULL
    },
    if (any(volatile) && "MHS69" %in% names(mh_stats)[volatile]) {
      "MHS69 shows volatile movement including a very large April increase — treat as a major validation flag, not operational improvement. Financial-year counting logic may apply."
    } else {
      NULL
    },
    "MHS01 was renamed nationally from April 2026 to 'people with an open referral' (previously 'people in contact') — not necessarily proof of contact or access.",
    "Provider and ICB-resident breakdowns must not be summed into one trust-wide headline."
  )
  headline_bullets <- headline_bullets[!vapply(headline_bullets, is.null, logical(1))]

  summary_table_html <- paste0(
    '<section class="nhs-section"><h2>Six-month headline measures (Provider/RDY)</h2>',
    period_caption_html(trend_window),
    '<p>Descriptive statistics from <code>trend_mhsds_access_rdy.csv</code>; suppressed values excluded from averages and charts.</p>',
    mh_six_month_summary_table(mh_stats),
    "</section>"
  )

  charts_html <- paste0(
    '<section class="nhs-section"><h2>Six-month trend charts</h2>',
    paste(vapply(mh_stats, function(s) {
      if (is.null(s$series) || s$n_numeric < 2L) {
        return(paste0("<p><em>", esc(s$label), ": too few numeric months for chart.</em></p>"))
      }
      line_trend_chart(s$series, paste0(s$label, " (", s$window_start, " \u2013 ", s$window_end, ")"))
    }, character(1)), collapse = "\n"),
    "</section>"
  )

  caveats_html <- paste0(
    '<div class="nhs-warning" role="note"><strong>Caveats (read before interpreting)</strong>',
    "<ul class=\"nhs-list-compact\">",
    "<li>MHSDS monthly data are <strong>provisional</strong> and may revise on final refresh.</li>",
    "<li>Suppressed values (<code>*</code>) are not treated as zero and are excluded from averages and charts.</li>",
    "<li>Figures are <strong>Provider/RDY</strong> rows only — not ICB-resident population views.</li>",
    "<li>Stock measures (open referrals, in-contact counts) and activity measures (contacts) must not be read as access performance without local validation.</li>",
    if (n_supp_demo > 0) {
      paste0("<li>Latest-month demo extract contains ", n_supp_demo, " suppressed cells across all measures — see audit trail.</li>")
    } else {
      ""
    },
    "</ul></div>"
  )

  mhsds_ts <- load_mhsds_time_series()
  ts_files <- list.files(processed_dir, pattern = "^rdy_mhsds_monthly.*time_series.*\\.csv$")
  ts_file_note <- if (length(ts_files) > 0) ts_files[1] else NULL

  supporting_html <- ""
  if (!is.null(mh)) {
    prov <- mh[trimws(mh$PRIMARY_LEVEL) == "RDY" & trimws(mh$BREAKDOWN) == "Provider", , drop = FALSE]
    if (nrow(prov) > 0) {
      prov$MV <- to_num(prov$MEASURE_VALUE)
      top_measures <- prov[!is.na(prov$MV), c("MEASURE_ID", "MEASURE_NAME", "MEASURE_VALUE", "BREAKDOWN")]
      top_measures <- top_measures[order(-prov$MV[!is.na(prov$MV)]), ]
      if (nrow(top_measures) > 10) top_measures <- top_measures[seq_len(10), , drop = FALSE]
      demo_period <- paste(unique(mh$REPORTING_PERIOD_START), unique(mh$REPORTING_PERIOD_END), sep = " to ")
      supporting_html <- collapsible_details(
        "Technical audit — latest-month demo extract and source files",
        paste0(
          "<p>Latest demo month: ", esc(demo_period), ". Six-month headline table uses stacked trend file only.</p>",
          html_table(top_measures, 10),
          if (!is.null(ts_file_note)) paste0("<p>Time-series fallback file: <code>", esc(ts_file_note), "</code></p>") else "",
          "<p>Pipeline: <code>site/public-data/05_download_historic_public_data.R</code>; ",
          "render: <code>site/R/03_render_public_reports.R</code></p>"
        )
      )
    }
  }

  mh_kfe_html <- paste0(caveats_html, summary_table_html, charts_html)

  config <- list(
    question = paste(
      "From six months of public MHSDS Provider/RDY data, summarise access and activity measures,",
      "describe descriptive trends, and list what a mental health data owner must confirm."
    ),
    dataset_line = "MHSDS Monthly Statistics — stacked Provider/RDY trend file",
    prompt_excerpt = paste(
      "Load trend_mhsds_access_rdy.csv for MHS23, MHS01, MHS29, MHS69.",
      "Compute six-month descriptive stats and trend readings.",
      "No causal claims. Do not sum provider and resident breakdowns.",
      sep = "\n"
    ),
    scope = list(
      can = c(
        "Summarise six months of key MHSDS access and activity measures for RDY as provider.",
        "Show latest vs previous month and six-month descriptive change.",
        "Flag suppression, provisional data and breakdown scope limits."
      ),
      cannot = c(
        "Support operational access conclusions without local MHSDS validation.",
        "Combine provider and ICB-resident breakdowns into one headline.",
        "Explain why trends changed or infer access improvement from contact counts alone."
      )
    ),
    headline = headline_bullets,
    provider_scope = TRUE,
    priority_callout = if ("MHS69" %in% names(mh_stats)[volatile]) {
      priority_callout_html(c(
        paste0(
          "MHS69 shows very large volatile movement (latest: ",
          format_mh_value(mh_stats$MHS69$latest_value), "). ",
          "Do not read as sudden operational improvement. Financial-year counting logic, ",
          "contact recording, or submission timing may explain the April movement."
        )
      ))
    } else {
      NULL
    },
    why_useful = c(
      "The agent assembled six months of Provider/RDY MHSDS measures with descriptive trends and suppression handling.",
      "It flagged where stock measures and activity measures tell different stories, and where validation is needed before operational use."
    ),
    bottom_line = paste(
      "Over six months, open-referral stock measures rose while contact volume was broadly stable.",
      "That pattern may signal caseload pressure but cannot prove whether access is improving or worsening.",
      "MHS69 needs particular validation before any access finding.",
      "A mental health data owner should confirm definitions and whether these Provider/RDY figures match local reporting."
    ),
    grouped_findings = list(
      list(
        title = "Stock measures (caseload-style)",
        items = list(
          list(
            title = "MHS23 — Open referrals",
            body = paste0(
              "Latest: ", format_mh_value(mh_stats$MHS23$latest_value, mh_stats$MHS23$latest_status),
              ". Six-month reading: ", mh_stats$MHS23$trend_reading,
              ". Open referrals are not new demand — higher counts are not automatically worse."
            ),
            owner = "MHSDS/data owner to confirm open-referral definition."
          ),
          list(
            title = "MHS01 — People in contact",
            body = paste0(
              "Latest: ", format_mh_value(mh_stats$MHS01$latest_value, mh_stats$MHS01$latest_status),
              ". Six-month reading: ", mh_stats$MHS01$trend_reading,
              ". Movement may reflect activity, discharge or coding."
            ),
            owner = "Confirm local in-contact definition matches MHSDS."
          )
        )
      ),
      list(
        title = "Activity measures",
        items = list(
          list(
            title = "MHS29 — Contacts in reporting period",
            body = paste0(
              "Latest: ", format_mh_value(mh_stats$MHS29$latest_value, mh_stats$MHS29$latest_status),
              ". Six-month reading: ", mh_stats$MHS29$trend_reading,
              ". Contact volume does not automatically mean better access."
            ),
            owner = "Service/BI owner to confirm contact counting rules."
          ),
          list(
            title = "MHS69 — CYP with two contacts",
            body = paste0(
              "Latest: ", format_mh_value(mh_stats$MHS69$latest_value, mh_stats$MHS69$latest_status),
              if (mh_stats$MHS69$n_suppressed > 0) paste0(" (", mh_stats$MHS69$n_suppressed, " suppressed month(s) in window).") else ".",
              " Confirm cohort and resident vs provider scope."
            ),
            owner = "CYP mental health lead."
          )
        )
      )
    ),
    data_used_html = paste0(
      '<ul class="nhs-list-compact">',
      '<li><strong>Primary:</strong> <code>trend_mhsds_access_rdy.csv</code> — MHS23, MHS01, MHS29, MHS69 (Provider/RDY)</li>',
      if (!is.null(mh)) '<li><strong>Context:</strong> <code>demo_mhsds_activity.csv</code> — latest-month slice for suppression audit</li>' else "",
      if (!is.null(ts_file_note)) paste0('<li><strong>Fallback reference:</strong> <code>', esc(ts_file_note), '</code></li>') else "",
      "</ul>"
    ),
    period = trend_window,
    trend_available = paste0(
      "Yes — six-month stacked trend (", mh_stats[[1]]$n_periods, " months per measure from trend_mhsds_access_rdy.csv)"
    ),
    agent_summary = c(
      paste0("Six-month MHSDS brief (", trend_window, ") for RDY Provider rows."),
      if (any(rising)) paste0("Rising over the window: ", paste(names(mh_stats)[rising], collapse = ", "), ".") else NULL,
      if (any(falling)) paste0("Falling over the window: ", paste(names(mh_stats)[falling], collapse = ", "), ".") else NULL,
      if (any(stable)) paste0("Broadly stable: ", paste(names(mh_stats)[stable], collapse = ", "), ".") else NULL,
      "Descriptive trends only — not causal. Local MHSDS owner validation required before operational use."
    ),
    human_checks = standard_human_checks(list(
      list(
        q = "Does the local MHSDS definition match these public Provider/RDY measures?",
        expl = "Confirm MHS23 open referrals and MHS01 in-contact definitions with the data owner."
      ),
      list(
        q = "Are suppressed or missing months material for your reporting?",
        expl = paste0(
          "Check value_status in trend file — ",
          paste(vapply(mh_stats, function(s) paste0(s$measure_id, ": ", s$n_suppressed, " suppressed"), character(1)), collapse = "; "),
          "."
        )
      ),
      list(
        q = "Should Provider or ICB-resident breakdowns be used for this question?",
        expl = "This brief uses Provider/RDY only — do not sum with resident breakdowns."
      )
    )),
    verify_intro = verify_intro_short(
      '<li><a href="../public-data/processed/trend_mhsds_access_rdy.csv">trend_mhsds_access_rdy.csv</a></li>',
      "Six-month figures trace to stacked MHSDS main-data monthly files — no synthetic values."
    )
  )
  config$agent_summary <- config$agent_summary[!vapply(config$agent_summary, is.null, logical(1))]

  verify_body <- traceability_verify_body(
    "Six-month statistics computed from trend_mhsds_access_rdy.csv (value_status=numeric rows only for averages/charts).",
    c("trend_mhsds_access_rdy.csv", if (!is.null(mh)) "demo_mhsds_activity.csv" else NULL, ts_file_note),
    "mhsds_monthly",
    "mhsds_monthly"
  )

  body <- agent_brief_sections(config, mh_kfe_html, verify_body, supporting_html)

  write_public_report(
    "public-mh-access-profile.html",
    "Worked example: AI-assisted MHSDS public-data briefing",
    paste0("MHSDS access and activity profile — six-month public-data brief (",
           mh_stats[[1]]$window_start, " to ", mh_stats[[1]]$window_end, ")"),
    body
  )
}

# --- C. Community services (CSDS) --------------------------------------------

build_csds_profile <- function() {
  csds <- load_demo("demo_csds_activity.csv", required = TRUE)
  if (is.null(csds)) {
    write_public_report("public-community-services-profile.html", "Public Community Services Profile",
      "Source data not available", '<section class="nhs-section"><p>Missing demo_csds_activity.csv</p></section>')
    return(invisible(NULL))
  }

  period <- paste(unique(csds$REPORTING_PERIOD_START), "to", unique(csds$REPORTING_PERIOD_END))
  activity <- csds[csds$COUNT_OF == "CareActivities" & csds$DIMENSION == "ActivityType", , drop = FALSE]
  activity$MV <- to_num(activity$MEASURE_VALUE)

  act_summary <- activity[!is.na(activity$MV), c("MEASURE_DESC", "MEASURE_VALUE", "MEASURE_VALUE_0_18", "MEASURE_VALUE_19_64", "MEASURE_VALUE_65_PLUS")]
  act_summary <- act_summary[order(-to_num(act_summary$MEASURE_VALUE)), ]
  if (nrow(act_summary) > 10) act_summary <- act_summary[seq_len(10), , drop = FALSE]

  total_contacts <- sum(activity$MV, na.rm = TRUE)
  n_rows <- nrow(csds)
  n_periods <- length(unique(csds$REPORTING_PERIOD_START))

  get_act <- function(desc) {
    row <- activity[activity$MEASURE_DESC == desc, , drop = FALSE]
    if (nrow(row) == 0) return(NA)
    row$MV[1]
  }
  assess_n <- get_act("Assessment")
  clin_n <- get_act("Clinical Intervention")

  csds_trend <- load_trend_file("trend_csds_activity_rdy.csv")
  csds_window_stats <- if (!is.null(csds_trend)) build_window_stats(csds_trend, CSDS_WINDOW_MEASURES, 6L) else list()
  assess_latest <- latest_from_trend(csds_trend, measure_name = "Assessment")
  clin_latest <- latest_from_trend(csds_trend, measure_name = "Clinical Intervention")
  if (!is.null(assess_latest) && !is.na(assess_latest$value)) assess_n <- assess_latest$value
  if (!is.null(clin_latest) && !is.na(clin_latest$value)) clin_n <- clin_latest$value
  latest_month_label <- if (!is.null(assess_latest)) assess_latest$period else {
    if (!is.null(clin_latest)) clin_latest$period else format(parse_period_start(unique(csds$REPORTING_PERIOD_START)[1]), "%b %Y")
  }

  trend_assess <- extract_stacked_trend(csds_trend, measure_name = "Assessment", label = "CSDS — Assessment (CareActivities)")
  trend_clin <- extract_stacked_trend(csds_trend, measure_name = "Clinical Intervention", label = "CSDS — Clinical Intervention (CareActivities)")
  csds_trend_note <- if (!is.null(csds_trend)) {
    paste0(
      "trend_csds_activity_rdy.csv (",
      if (length(csds_window_stats) > 0 && isTRUE(csds_window_stats[[1]]$available)) {
        paste0(csds_window_stats[[1]]$window_start, " \u2013 ", csds_window_stats[[1]]$window_end)
      } else {
        "historic stack from script 05"
      },
      ")"
    )
  } else {
    "Historic CSDS trend file not found — run site/public-data/05_download_historic_public_data.R"
  }
  trend_window <- if (length(csds_window_stats) > 0 && nzchar(csds_window_stats[[1]]$window_start %||% "")) {
    paste0(csds_window_stats[[1]]$window_start, " \u2013 ", csds_window_stats[[1]]$window_end)
  } else {
    latest_month_label
  }
  csds_trend_line <- function(t) {
    if (isTRUE(t$available)) {
      paste0(
        "Latest vs previous month: ",
        format(t$latest_value, big.mark = ","), " (", t$latest_period, ") vs ",
        format(t$previous_value, big.mark = ","), " (", t$previous_period, ")"
      )
    } else if (!is.null(csds_trend)) {
      "Historic trend file present but fewer than two comparable periods for this measure."
    } else {
      "Trend not available — historic stack not yet generated."
    }
  }

  csds_has_trend <- isTRUE(trend_assess$available) || isTRUE(trend_clin$available) ||
    any(vapply(csds_window_stats, function(s) isTRUE(s$available), logical(1)))

  kpis <- list(
    list(value = format(total_contacts, big.mark = ","), label = paste("Care activity total (", latest_month_label, ")", sep = "")),
    list(value = nrow(activity), label = "Activity type rows"),
    list(value = n_rows, label = "Total RDY rows in extract"),
    list(value = latest_month_label, label = "Latest publication month")
  )

  other_n <- get_act("Other")
  csds_full_periods <- if (!is.null(csds_trend)) {
    length(unique(csds_trend$reporting_period_start))
  } else {
    0L
  }
  assess_trend_reading <- if (length(csds_window_stats) > 0 && !is.null(csds_window_stats$assess)) {
    csds_window_stats$assess$trend_reading %||% "unclear"
  } else {
    "unclear"
  }
  clin_trend_reading <- if (length(csds_window_stats) > 0 && !is.null(csds_window_stats$clin)) {
    csds_window_stats$clin$trend_reading %||% "unclear"
  } else {
    "unclear"
  }
  assess_six_pct <- if (length(csds_window_stats) > 0 && !is.null(csds_window_stats$assess)) {
    csds_window_stats$assess$six_month_pct
  } else {
    NA
  }
  clin_six_pct <- if (length(csds_window_stats) > 0 && !is.null(csds_window_stats$clin)) {
    csds_window_stats$clin$six_month_pct
  } else {
    NA
  }

  csds_trend_label <- function(reading) {
    switch(reading,
      rising = "Rising",
      falling = "Falling",
      "broadly stable" = "Stable",
      volatile = "Volatile",
      "Mixed / unclear"
    )
  }

  other_pct <- if (!is.na(other_n) && total_contacts > 0) round(100 * other_n / total_contacts, 0) else NA

  key_figures <- paste0(
    kpi_row(kpis),
    period_caption_html(paste0("Latest month: ", latest_month_label),
                        if (csds_full_periods > 6) paste0("Full extract: ", csds_full_periods, " months in trend_csds_activity_rdy.csv") else NULL),
    '<h3>Care activities by type (', esc(latest_month_label), ')</h3>', html_table(act_summary, 10),
    bar_chart(act_summary$MEASURE_DESC, to_num(act_summary$MEASURE_VALUE),
              paste0("Care activities by type (RDY, ", latest_month_label, ")"),
              period_caption = latest_month_label)
  )

  commentary_cards <- c(
    build_commentary_card(
      "Care activities — Assessment",
      if (is.na(assess_n) || assess_n == 0) "Watch / clarify" else "Review locally",
      if (is.na(assess_n) || assess_n == 0) "watch" else "review",
      list(
        "Plain-English meaning" = "Count of assessment-type care activities recorded in CSDS for the month.",
        "Latest value" = if (is.na(assess_n)) "—" else format(assess_n, big.mark = ","),
        "Comparator / trend" = csds_trend_line(trend_assess),
        "Agent flag" = if (is.na(assess_n) || assess_n == 0) "Watch / clarify" else "Review locally",
        "Cautious interpretation" = "Activity counts reflect coded CSDS submissions — not unique patients.",
        "Human check required" = "Community services/BI owner to confirm activity coding."
      )
    ),
    build_commentary_card(
      "Care activities — Clinical Intervention",
      "Review locally", "review",
      list(
        "Plain-English meaning" = "Direct clinical intervention activities in community services for the month.",
        "Latest value" = if (is.na(clin_n)) "—" else format(clin_n, big.mark = ","),
        "Comparator / trend" = csds_trend_line(trend_clin),
        "Agent flag" = "Review locally",
        "Cautious interpretation" = "Largest activity type where numeric — confirm service scope locally.",
        "Human check required" = "Directorate lead to confirm which services feed this measure."
      )
    )
  )

  csds_kfe_specs <- list(
    list(
      figure = "Care activities — Assessment",
      what = "Count of assessment-type care activities recorded in CSDS for the month (CareActivities / ActivityType).",
      latest = if (is.na(assess_n)) "—" else format_kfe_latest(format(assess_n, big.mark = ","), latest_month_label),
      comparator_type = "previous_period",
      comparator_detail = if (isTRUE(trend_assess$available)) {
        paste0(
          "Previous period: ", format(trend_assess$previous_value, big.mark = ","),
          " (", trend_assess$previous_period, "). No official standard in public CSDS extract."
        )
      } else {
        "Latest month only — no multi-period trend for Assessment."
      },
      trend = trend_assess,
      polarity = "unknown",
      trend_label = csds_trend_label(assess_trend_reading),
      judgement = if (!is.na(assess_six_pct) && assess_six_pct < 0) {
        "Up month-on-month but lower than start of trend window — may reflect seasonality, coding or service mix."
      } else {
        "Activity counts reflect coded CSDS submissions — not unique patients or completed care pathways."
      },
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Community services/BI owner to confirm activity coding and whether this matches local community dashboards.")
    ),
    list(
      figure = "Care activities — Clinical Intervention",
      what = "Direct clinical intervention activities in community services for the month.",
      latest = if (is.na(clin_n)) "—" else format_kfe_latest(format(clin_n, big.mark = ","), latest_month_label),
      comparator_type = "previous_period",
      comparator_detail = if (isTRUE(trend_clin$available)) {
        paste0(
          "Previous period: ", format(trend_clin$previous_value, big.mark = ","),
          " (", trend_clin$previous_period, "). Historic stack: trend_csds_activity_rdy.csv."
        )
      } else {
        csds_trend_note
      },
      trend = trend_clin,
      polarity = "unknown",
      trend_label = csds_trend_label(clin_trend_reading),
      judgement = if (!is.na(clin_six_pct) && clin_six_pct < 0) {
        "Up month-on-month but lower than start of trend window — confirm service scope locally."
      } else {
        "Confirm service scope locally before review use."
      },
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check(paste("Directorate lead to confirm which services feed this measure and coding QA for", latest_month_label, "."))
    ),
    list(
      figure = "Care activities — Other (ActivityType)",
      what = "Largest activity category in the ActivityType slice — limits interpretability of other types.",
      latest = if (is.na(other_n)) "—" else format_kfe_latest(format(other_n, big.mark = ","), latest_month_label),
      comparator_type = "none",
      comparator_detail = if (!is.na(other_pct)) paste0(other_pct, "% of ActivityType total — data-quality/interpretability flag.") else "See activity table.",
      trend = NULL,
      trend_label = "Not available",
      judgement = "Largest category is 'Other' — local CSDS owners should confirm legitimate coding vs mapping limitations.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("CSDS return owner to confirm whether 'Other' reflects legitimate coding or inconsistent activity-type recording.")
    ),
    list(
      figure = "Total care activities (ActivityType slice)",
      what = "Sum of numeric CareActivities rows in the ActivityType dimension for the latest demo month.",
      latest = format_kfe_latest(format(total_contacts, big.mark = ","), latest_month_label),
      comparator_type = "none",
      comparator_detail = "Aggregate sum for one month — not trended as a single total in historic stack.",
      trend = NULL,
      polarity = "unknown",
      trend_label = "Not available",
      judgement = "Public CSDS aggregates cannot support team-level or pathway conclusions without local drill-down.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Confirm full RDY processed extract contains all measures needed — demo slice may not surface every row.")
    ),
    list(
      figure = "Community waiting-time performance",
      what = "Access/waiting performance is not available from CSDS activity counts alone.",
      latest = "Not in this CSDS activity slice",
      comparator_type = "official_standard",
      comparator_detail = paste0(
        "Community waiting lists require CHS SitRep / NHS waiting-list data. ",
        "National planning ambition: 80% under 18 weeks by 2028/29. ",
        standard_metadata_cite(CHS_WAITING_META)
      ),
      trend = NULL,
      trend_label = "Not available",
      judgement = "Use NHS community health services waiting-list publication for access performance — not this activity report.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Community services lead to confirm local waiting-list reporting source.")
    )
  )

  csds_kfe_html <- key_figures_explained_section(
    csds_kfe_specs,
    paste(
      "CSDS community activity profile for RDY — activity counts only, not access or waiting performance.",
      if (csds_has_trend) paste0("Six-month summary: ", trend_window, ".") else ""
    ),
    show_trend = TRUE,
    show_validation = TRUE,
    period_caption = if (csds_has_trend) {
      paste0("Six-month summary: ", trend_window,
             if (csds_full_periods > 6) paste0("; full extract: ", csds_full_periods, " months") else "")
    } else {
      paste0("Latest month: ", latest_month_label)
    },
    comparator_header = "Previous period / comparator"
  )

  csds_window_html <- if (length(csds_window_stats) > 0 && csds_has_trend) {
    window_stats_section(
      csds_window_stats,
      paste("Descriptive six-month statistics from trend_csds_activity_rdy.csv (", trend_window, ").", sep = ""),
      "CSDS six-month",
      c(
        if (csds_full_periods > 6) paste0("Full trend extract contains ", csds_full_periods, " months — table above uses six-month window only.") else NULL,
        "Provisional CSDS monthly data; ActivityType/CareActivities slice only.",
        "Trend describes direction of change only — not operational cause."
      )
    )
  } else {
    trend_not_available_section(c(
      "Additional monthly CSDS public files for consecutive months",
      "Consistent measure and ActivityType filters across periods"
    ))
  }

  csds_trend_html <- if (isTRUE(trend_assess$available) || isTRUE(trend_clin$available)) {
    trend_section(
      list(trend_assess, trend_clin),
      csds_trend_note,
      c(
        "Provisional CSDS monthly data; ActivityType/CareActivities slice only.",
        "Trend describes direction of change only — not operational cause."
      )
    )
  } else {
    ""
  }

  supporting_html <- paste0(
    collapsible_details("Supporting tables and charts", key_figures),
    collapsible_details("Additional measure commentary", measure_commentary_section(commentary_cards)),
    wrap_trend_collapsible(csds_window_html, "CSDS six-month trend summary"),
    if (nzchar(csds_trend_html)) wrap_trend_collapsible(csds_trend_html, "CSDS Assessment and Clinical Intervention trend detail") else ""
  )

  chs_signpost <- paste0(
    "Community waiting-time performance requires CHS SitRep / NHS waiting-list data — not CSDS activity counts. ",
    standard_metadata_cite(CHS_WAITING_META)
  )

  config <- list(
    question = paste(
      "Using public CSDS for", latest_month_label, ", explain community activity measures,",
      "show descriptive six-month trends where the historic stack supports them,",
      "and what a business partner should validate locally."
    ),
    dataset_line = "CSDS Monthly Statistics (RDY provider rows)",
    provider_scope = TRUE,
    prompt_excerpt = paste(
      "Filter CSDS monthly CSV to RDY. Inspect CareActivities by ActivityType.",
      "Use trend_csds_activity_rdy.csv for month-on-month trends where ≥2 periods exist.",
      "No causal language.",
      sep = "\n"
    ),
    scope = list(
      can = c(
        paste("Describe", latest_month_label, "community activity totals and key activity types for RDY."),
        "Show descriptive six-month trends for Assessment and Clinical Intervention where the historic stack supports them."
      ),
      cannot = c(
        "Prove referral demand, waiting times or team performance from public aggregate CSDS.",
        "Support pathway or team-level conclusions without local drill-down."
      )
    ),
    headline = c(
      if (csds_has_trend && !is.na(assess_six_pct) && assess_six_pct < 0) {
        paste0(
          "Both Assessment and Clinical Intervention increased in ", latest_month_label, " compared with the previous month, ",
          "but both remain lower than the start of the six-month window (Assessment ", round(assess_six_pct, 1), "%, Clinical Intervention ",
          if (!is.na(clin_six_pct)) round(clin_six_pct, 1) else "—", "%). Local validation needed."
        )
      } else {
        paste0("For ", period, ", care activity totals sum to ", format(total_contacts, big.mark = ","), " in the ActivityType slice.")
      },
      if (!is.na(other_pct)) paste0("'Other' is the largest activity category (", format(other_n, big.mark = ","), ", ", other_pct, "% of total) — limits interpretability.") else NULL,
      chs_signpost,
      "Public aggregate CSDS activity counts cannot prove referral demand, waiting times or team performance."
    ),
    priority_callout = if (!is.na(other_pct) && other_pct >= 40) {
      priority_callout_html(c(
        paste0("'Other' accounts for ", other_pct, "% of ActivityType activities (", format(other_n, big.mark = ","), ") — major data-quality/interpretability flag."),
        if (csds_has_trend) "Assessment and Clinical Intervention rose month-on-month but remain below six-month start values." else NULL
      ))
    } else {
      NULL
    },
    why_useful = c(
      "The agent extracted RDY CSDS activity-type counts and computed descriptive six-month trends where the historic stack supports them.",
      "It flagged where activity data cannot answer access or waiting questions, and where coding limits interpretation."
    ),
    bottom_line = paste(
      "This is an activity profile, not a community services performance report.",
      "March activity rose for key types but six-month movement is down for Assessment and Clinical Intervention.",
      "Waiting-time performance needs separate CHS waiting-list data.",
      "A community services lead should validate coding, especially the large 'Other' category, before any operational use."
    ),
    grouped_findings = list(
      list(
        title = "Activity with trend support",
        items = list(
          list(
            title = "Care activities — Assessment",
            body = paste0(
              "Assessment-type care activities for ", latest_month_label, ": ",
              if (is.na(assess_n)) "—" else format(assess_n, big.mark = ","), ". ",
              csds_trend_line(trend_assess)
            ),
            owner = "Community services/BI owner to confirm activity coding."
          ),
          list(
            title = "Care activities — Clinical Intervention",
            body = paste0(
              "Direct clinical intervention activities: ",
              if (is.na(clin_n)) "—" else format(clin_n, big.mark = ","), ". ",
              csds_trend_line(trend_clin)
            ),
            owner = "Directorate lead to confirm which services feed this measure."
          )
        )
      ),
      list(
        title = "Aggregate limits",
        items = list(
          list(
            title = "Total care activities and age bands",
            body = paste0(
              "Total ActivityType slice sums to ", format(total_contacts, big.mark = ","),
              ". Age-band splits vary by activity type and are not trended in the current stack."
            ),
            owner = "Validate CYP vs adult splits with the CSDS return owner before directorate use."
          )
        )
      )
    ),
    data_used_html = paste0(
      '<ul class="nhs-list-compact">',
      '<li>CSDS (<code>demo_csds_activity.csv</code> — ', esc(latest_month_label), ')</li>',
      if (!is.null(csds_trend)) '<li><code>trend_csds_activity_rdy.csv</code> — historic CareActivities stack</li>' else "",
      '</ul>'
    ),
    period = if (csds_has_trend) paste0(trend_window, " (six-month trend); latest month ", latest_month_label) else period,
    trend_available = if (csds_has_trend) {
      paste0("Yes — six-month historic stack (", csds_window_stats[[1]]$n_periods %||% trend_assess$n_periods, " months in trend_csds_activity_rdy.csv)")
    } else {
      "Limited — latest month only or insufficient comparable periods"
    },
    agent_summary = c(
      paste0("For ", period, ", care activity totals sum to ", format(total_contacts, big.mark = ","), " in the ActivityType slice."),
      if (csds_has_trend) paste0("Historic CSDS stack (", trend_window, ") supports six-month descriptive comparison for Assessment and Clinical Intervention — not causal conclusions.") else "Trend analysis not available from current extract.",
      "Public aggregate CSDS cannot prove referral demand, waiting times or team performance.",
      "Sparse or zero activity in the demo slice needs local coding confirmation."
    ),
    human_checks = standard_human_checks(),
    verify_intro = verify_intro_short(
      '<li><a href="../public-data/processed/demo_csds_activity.csv">demo_csds_activity.csv</a></li>',
      paste0("Latest values from trend file where available; demo CSV period ", period, ". Trend deltas from trend_csds_activity_rdy.csv.")
    )
  )

  verify_body <- traceability_verify_body(
    "See linked demo CSV and filter notes.",
    c("demo_csds_activity.csv", if (!is.null(csds_trend)) "trend_csds_activity_rdy.csv" else NULL),
    "csds_monthly"
  )

  body <- agent_brief_sections(config, csds_kfe_html, verify_body, supporting_html)

  write_public_report("public-community-services-profile.html",
    "Worked example: AI-assisted CSDS community activity briefing",
    paste0("CSDS community activity profile — RDY, ", latest_month_label, " with descriptive trend"), body)
}

# --- D. Talking Therapies ------------------------------------------------------

build_talking_therapies <- function() {
  tt <- load_demo("demo_talking_therapies.csv", required = TRUE)
  if (is.null(tt)) {
    write_public_report("public-talking-therapies-profile.html", "Public NHS Talking Therapies Profile",
      "Source data not available", '<section class="nhs-section"><p>Missing demo_talking_therapies.csv</p></section>')
    return(invisible(NULL))
  }

  period <- paste(unique(tt$REPORTING_PERIOD_START), "to", unique(tt$REPORTING_PERIOD_END))
  prov <- tt[grepl("^RDY$", trimws(tt$ORG_CODE2)) & trimws(tt$GROUP_TYPE) == "Provider", , drop = FALSE]
  prov$MV <- to_num(prov$MEASURE_VALUE_SUPPRESSED)
  n_supp <- sum(is_suppressed(prov$MEASURE_VALUE_SUPPRESSED))

  access_ids <- c("M001", "M002", "M019", "M020", "M021", "M022")
  access <- prov[prov$MEASURE_ID %in% access_ids, c("MEASURE_ID", "MEASURE_NAME", "MEASURE_VALUE_SUPPRESSED")]
  access$MV <- to_num(access$MEASURE_VALUE_SUPPRESSED)

  refs <- prov[prov$MEASURE_ID == "M001", "MEASURE_VALUE_SUPPRESSED"]
  refs_n <- to_num(refs)[1]
  accessing_n <- to_num(prov[prov$MEASURE_ID == "M031", "MEASURE_VALUE_SUPPRESSED"])[1]

  self_ref_n <- to_num(prov[prov$MEASURE_ID == "M002", "MEASURE_VALUE_SUPPRESSED"])[1]
  m055_val <- to_num(prov[prov$MEASURE_ID == "M055", "MEASURE_VALUE_SUPPRESSED"])[1]

  wait_rows_all <- prov[prov$MEASURE_ID %in% c("M019", "M020", "M021", "M022"), ]
  wait_rows_021 <- prov[prov$MEASURE_ID %in% c("M019", "M020", "M021"), ]
  wait_total_all <- sum(to_num(wait_rows_all$MEASURE_VALUE_SUPPRESSED), na.rm = TRUE)
  wait_total_021 <- sum(to_num(wait_rows_021$MEASURE_VALUE_SUPPRESSED), na.rm = TRUE)
  m022_val <- to_num(prov[prov$MEASURE_ID == "M022", "MEASURE_VALUE_SUPPRESSED"])[1]

  tt_trend <- load_tt_trend()
  tt_ts <- load_tt_time_series()
  ts_file_note <- if (!is.null(tt_trend) && nrow(tt_trend) > 0) {
    "trend_talking_therapies_rdy.csv (or time-series fallback)"
  } else if (!is.null(tt_ts)) {
    basename(list.files(processed_dir, pattern = "^rdy_talking_therapies.*time_series\\.csv$")[1])
  } else {
    "No Talking Therapies trend or time-series file in processed/"
  }

  tt_window_stats <- if (!is.null(tt_trend) && nrow(tt_trend) > 0) {
    build_window_stats(tt_trend, TT_WINDOW_MEASURES, 6L)
  } else {
    list()
  }
  trend_window <- if (length(tt_window_stats) > 0 && nzchar(tt_window_stats[[1]]$window_start %||% "")) {
    paste0(tt_window_stats[[1]]$window_start, " \u2013 ", tt_window_stats[[1]]$window_end)
  } else {
    period
  }

  m001_latest <- latest_from_trend(tt_trend, measure_id = "M001")
  m031_latest <- latest_from_trend(tt_trend, measure_id = "M031")
  m053_latest <- latest_from_trend(tt_trend, measure_id = "M053")
  if (!is.null(m001_latest) && !is.na(m001_latest$value)) refs_n <- m001_latest$value
  if (!is.null(m031_latest) && !is.na(m031_latest$value)) accessing_n <- m031_latest$value
  if (!is.null(m053_latest) && !is.na(m053_latest$value)) m053_val <- m053_latest$value

  trend_m001 <- compute_period_trend(extract_tt_rdy_ts(tt_ts, "M001"), "M001 — referrals received")
  trend_m031 <- compute_period_trend(extract_tt_rdy_ts(tt_ts, "M031"), "M031 — people accessing services")
  trend_m053 <- compute_period_trend(extract_tt_rdy_ts(tt_ts, "M053"), "M053 — % accessing within 6 weeks (finished course)")
  if (length(tt_window_stats) > 0) {
    if (isTRUE(tt_window_stats$M001$available)) {
      trend_m001 <- list(
        available = TRUE,
        n_periods = tt_window_stats$M001$n_periods,
        measure_label = "M001 — referrals received",
        latest_period = tt_window_stats$M001$latest_period,
        previous_period = tt_window_stats$M001$previous_period,
        latest_value = tt_window_stats$M001$latest_value,
        previous_value = tt_window_stats$M001$previous_value,
        absolute_change = tt_window_stats$M001$mom_abs,
        percent_change = tt_window_stats$M001$mom_pct,
        series = tt_window_stats$M001$series
      )
    }
    if (isTRUE(tt_window_stats$M031$available)) {
      trend_m031 <- list(
        available = TRUE,
        n_periods = tt_window_stats$M031$n_periods,
        measure_label = "M031 — people accessing services",
        latest_period = tt_window_stats$M031$latest_period,
        previous_period = tt_window_stats$M031$previous_period,
        latest_value = tt_window_stats$M031$latest_value,
        previous_value = tt_window_stats$M031$previous_value,
        absolute_change = tt_window_stats$M031$mom_abs,
        percent_change = tt_window_stats$M031$mom_pct,
        series = tt_window_stats$M031$series
      )
    }
    if (isTRUE(tt_window_stats$M053$available)) {
      trend_m053 <- list(
        available = TRUE,
        n_periods = tt_window_stats$M053$n_periods,
        measure_label = "M053 — % accessing within 6 weeks (finished course)",
        latest_period = tt_window_stats$M053$latest_period,
        previous_period = tt_window_stats$M053$previous_period,
        latest_value = tt_window_stats$M053$latest_value,
        previous_value = tt_window_stats$M053$previous_value,
        absolute_change = tt_window_stats$M053$mom_abs,
        percent_change = tt_window_stats$M053$mom_pct,
        series = tt_window_stats$M053$series
      )
    }
  }

  m053_val <- to_num(prov[prov$MEASURE_ID == "M053", "MEASURE_VALUE_SUPPRESSED"][1])
  if (!is.null(m053_latest) && !is.na(m053_latest$value)) m053_val <- m053_latest$value

  trend_line <- function(t) {
    if (isTRUE(t$available)) {
      paste0(
        format(t$latest_value, big.mark = ","), " (", t$latest_period, ") vs ",
        format(t$previous_value, big.mark = ","), " (", t$previous_period, ")"
      )
    } else {
      "Trend not available from current extract"
    }
  }

  tt_data_points <- if (length(tt_window_stats) > 0 && !is.null(tt_window_stats[[1]]$n_periods)) {
    tt_window_stats[[1]]$n_periods
  } else if (isTRUE(trend_m001$available)) {
    trend_m001$n_periods
  } else {
    NA_integer_
  }
  trend_window_label <- if (!is.na(tt_data_points)) {
    paste0("Six available monthly data points: ", trend_window, " (non-consecutive months in extract)")
  } else {
    period
  }

  self_ref_pct <- if (!is.na(refs_n) && !is.na(self_ref_n) && refs_n > 0) {
    round(100 * self_ref_n / refs_n, 0)
  } else {
    NA
  }

  m053_start <- if (isTRUE(trend_m053$available) && !is.na(trend_m053$previous_value)) {
    trend_m053$series$value[1]
  } else {
    NA
  }
  m053_judgement <- if (!is.na(m053_val)) {
    paste0(
      "RDY at ", m053_val, "% remains above the 75% six-week access standard",
      if (!is.na(m053_start) && m053_val < m053_start) {
        paste0(" but has fallen from ", m053_start, "% in the selected trend window — pathway review needed.")
      } else {
        " — confirm denominator locally."
      }
    )
  } else {
    "Pathway / data-definition check required."
  }

  kpis <- list(
    list(value = if (is.na(refs_n)) "—" else format(refs_n, big.mark = ","), label = "Referrals received (M001)"),
    list(value = if (is.na(self_ref_n)) "—" else format(self_ref_n, big.mark = ","), label = "Self-referrals (M002)"),
    list(value = if (is.na(m053_val)) "—" else paste0(m053_val, "%"), label = "Six-week access (M053)"),
    list(value = if (is.na(wait_total_all)) "—" else format(wait_total_all, big.mark = ","), label = "Open referrals no activity (M019–M022)")
  )

  key_figures <- paste0(
    kpi_row(kpis),
    period_caption_html(paste0("Latest month: ", if (isTRUE(trend_m053$available)) trend_m053$latest_period else period)),
    '<h3>Access and waiting measures (', esc(if (isTRUE(trend_m053$available)) trend_m053$latest_period else period), ')</h3>',
    html_table(access, 10),
    bar_chart(access$MEASURE_NAME, access$MV,
              paste0("Selected Talking Therapies measures (RDY, ",
                     if (isTRUE(trend_m053$available)) trend_m053$latest_period else period, ")"),
              period_caption = if (isTRUE(trend_m053$available)) trend_m053$latest_period else period)
  )

  commentary_cards <- c(
    build_commentary_card(
      "M001 — Referrals received",
      if (isTRUE(trend_m001$available)) "Watch / clarify" else "Trend not available",
      if (isTRUE(trend_m001$available)) "watch" else "definition",
      list(
        "Plain-English meaning" = "Count of new referrals received in the month.",
        "Latest value" = if (is.na(refs_n)) "—" else format(refs_n, big.mark = ","),
        "Comparator / trend" = trend_line(trend_m001),
        "Agent flag" = if (isTRUE(trend_m001$available)) "Watch / clarify" else "Trend not available",
        "Cautious interpretation" = "Referral counts move with demand and recording — descriptive only.",
        "Human check required" = "IAPT/data owner to confirm M001 matches local reporting."
      )
    ),
    build_commentary_card(
      "M053 — Six-week access (finished course)",
      "Definition check required", "definition",
      list(
        "Plain-English meaning" = "Percentage accessing within 6 weeks among those finishing treatment.",
        "Latest value" = if (is.na(m053_val)) "—" else paste0(m053_val, "%"),
        "Comparator / trend" = trend_line(trend_m053),
        "Agent flag" = "Definition check required",
        "Cautious interpretation" = "Not clinical quality without outcome definitions.",
        "Human check required" = "Confirm national IAPT access definition locally."
      )
    )
  )

  tt_kfe_specs <- list(
    list(
      figure = "M001 — Referrals received",
      what = "Count of new referrals received in the month — inflow measure, not caseload.",
      latest = if (isTRUE(trend_m001$available)) kfe_from_trend(trend_m001) else format_kfe_latest(if (is.na(refs_n)) "—" else format(refs_n, big.mark = ","), period),
      standard_detail = "No simple inflow target",
      comparator_detail = if (isTRUE(trend_m001$available)) {
        paste0("Previous period: ", format(trend_m001$previous_value, big.mark = ","), " (", trend_m001$previous_period, ").")
      } else {
        "Insufficient periods in Provider time-series extract."
      },
      trend = trend_m001,
      polarity = "unknown",
      trend_label = if (isTRUE(trend_m001$available)) {
        if (trend_m001$absolute_change > 0) "Rising" else if (trend_m001$absolute_change < 0) "Falling" else "Stable"
      } else {
        "Not available"
      },
      judgement = if (!is.na(self_ref_pct)) {
        paste0("Self-referrals (M002) account for ~", self_ref_pct, "% of referrals — confirm whether route mix is expected locally.")
      } else {
        "Referral counts move with demand, pathways and recording — descriptive only."
      },
      validation_status = "Pathway / data-definition check required",
      human_check = short_human_check("Talking Therapies/data owner to confirm M001 matches local referrals reporting for the same period.")
    ),
    list(
      figure = "M002 — Self-referrals",
      what = "Count of self-referrals received in the month.",
      latest = format_kfe_latest(if (is.na(self_ref_n)) "—" else format(self_ref_n, big.mark = ","),
                                if (isTRUE(trend_m001$available)) trend_m001$latest_period else period),
      standard_detail = "No simple target",
      comparator_detail = if (!is.na(self_ref_pct)) paste0("~", self_ref_pct, "% of M001 referrals in same month.") else "See M001.",
      trend = NULL,
      trend_label = "Not available",
      judgement = "Large share of total referrals — confirm expected locally and whether route mix has changed.",
      validation_status = "Pathway / data-definition check required",
      human_check = short_human_check("Confirm whether self-referral volume is expected and stable for local pathways.")
    ),
    list(
      figure = "M053 — Six-week access (finished course)",
      what = "Percentage accessing services within 6 weeks among those finishing a course of treatment.",
      latest = if (isTRUE(trend_m053$available)) {
        paste0(trend_m053$latest_value, "% (", trend_m053$latest_period, ")")
      } else if (is.na(m053_val)) "—" else paste0(m053_val, "% (", period, ")"),
      standard_detail = paste0("National standard: ≥75% six-week access. ", standard_metadata_cite(TT_STANDARDS_META)),
      comparator_detail = "95% within 18 weeks is also a national programme standard — see M055 row.",
      trend = trend_m053,
      polarity = "higher_better",
      trend_label = if (isTRUE(trend_m053$available)) {
        if (trend_m053$absolute_change < 0) "Falling" else if (trend_m053$absolute_change > 0) "Rising" else "Stable"
      } else {
        "Not available"
      },
      judgement = m053_judgement,
      validation_status = "Pathway / data-definition check required",
      human_check = short_human_check("Confirm national Talking Therapies access definition and whether local enter-treatment waits use the same cohort.")
    ),
    list(
      figure = "M055 — Eighteen-week access (finished course)",
      what = "Percentage accessing within 18 weeks among those finishing a course of treatment.",
      latest = if (is.na(m055_val)) "—" else paste0(m055_val, "% (", period, ")"),
      standard_detail = paste0("National standard: ≥95% eighteen-week access. ", standard_metadata_cite(TT_STANDARDS_META)),
      comparator_detail = "From demo_talking_therapies.csv — M055 row.",
      trend = NULL,
      trend_label = "Not available",
      judgement = if (!is.na(m055_val) && m055_val >= 95) {
        "Above 95% eighteen-week standard in latest month — confirm denominator locally."
      } else if (!is.na(m055_val)) {
        "Below 95% eighteen-week standard — pathway owner review needed."
      } else {
        "Eighteen-week access percentage not available in extract."
      },
      validation_status = "Pathway / data-definition check required",
      human_check = short_human_check("Confirm M055 cohort matches local 18-week access reporting.")
    ),
    list(
      figure = "M019–M022 — Open referrals with no activity (waiting bands)",
      what = "Open referrals with no recorded activity by waiting band — stock measures.",
      latest = paste0(
        "M019–M021: ", if (is.na(wait_total_021)) "—" else format(wait_total_021, big.mark = ","),
        "; M019–M022: ", if (is.na(wait_total_all)) "—" else format(wait_total_all, big.mark = ","),
        " (", period, ")"
      ),
      standard_detail = "No simple target — lower bands generally preferable",
      comparator_detail = if (!is.na(m022_val)) paste0("M022 over 120 days: ", format(m022_val, big.mark = ","), " — important pathway risk signal.") else "See band rows.",
      trend = NULL,
      trend_label = "Not available",
      judgement = "910+ day waits (M022) are a key review flag — pathway recording may differ from local waiting lists.",
      validation_status = "Pathway / data-definition check required",
      human_check = short_human_check("Pathway owner to confirm how 'no activity' is coded vs local waiting list and enter-treatment tracking.")
    ),
    list(
      figure = "Suppression (Provider RDY rows)",
      what = paste0("Measures withheld as '*' under disclosure rules in the demo extract."),
      latest = paste0(n_supp, " of ", nrow(prov), " provider rows suppressed"),
      standard_detail = "N/A",
      comparator_detail = "Suppression may vary by month.",
      trend = NULL,
      trend_label = "Not available",
      judgement = "Do not infer from missing cells; small-number referral sources are often suppressed.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Review monthly Talking Therapies DQ report and whether suppressed measures matter for your question.")
    )
  )

  tt_kfe_html <- key_figures_explained_section(
    tt_kfe_specs,
    paste(
      "Access and waiting measures for RDY as provider.",
      paste0(n_supp, " suppressed cells — do not infer from missing values."),
      "This brief covers access and waits only. A complete Talking Therapies performance view would also need recovery, reliable improvement, reliable recovery, finishing-treatment volumes, and average sessions per referral."
    ),
    show_trend = TRUE,
    show_standard = TRUE,
    show_validation = TRUE,
    period_caption = trend_window_label,
    comparator_header = "Previous period / comparator"
  )

  tt_has_window <- length(tt_window_stats) > 0 &&
    any(vapply(tt_window_stats, function(s) isTRUE(s$available), logical(1)))
  rising <- if (tt_has_window) vapply(tt_window_stats, function(s) identical(s$trend_reading, "rising"), logical(1)) else logical()
  falling <- if (tt_has_window) vapply(tt_window_stats, function(s) identical(s$trend_reading, "falling"), logical(1)) else logical()

  supporting_html <- paste0(
    collapsible_details("Supporting tables and charts", key_figures),
    collapsible_details("Additional measure commentary", measure_commentary_section(commentary_cards)),
    if (tt_has_window) {
      wrap_trend_collapsible(window_stats_section(
        tt_window_stats,
        paste0("Talking Therapies statistics from ", ts_file_note, " (", trend_window_label, ")."),
        "Talking Therapies",
        c(
          "NHS Talking Therapies monthly statistics are provisional and may revise.",
          "Trend descriptions are not causal — pathway and recording changes may explain movement."
        )
      ), "Talking Therapies trend summary")
    } else {
      wrap_trend_collapsible(
        trend_section(
          list(trend_m001, trend_m031, trend_m053),
          ts_file_note,
          c(
            "NHS Talking Therapies monthly statistics are provisional and may revise.",
            "Trend descriptions are not causal — pathway and recording changes may explain movement."
          )
        ),
        "Talking Therapies M001/M031/M053 trend detail"
      )
    }
  )

  config <- list(
    question = paste(
      "From public NHS Talking Therapies data for RDY, explain referrals, access and waiting measures,",
      "show trends where the time-series extract supports them, and list what outcome analysis would require."
    ),
    dataset_line = "NHS Talking Therapies Monthly Statistics (RDY provider rows)",
    provider_scope = TRUE,
    prompt_excerpt = paste(
      "Filter Talking Therapies monthly CSV to ORG_CODE2=RDY, Provider group.",
      "Trend from downloaded time-series file only. No recovery inference. No causal claims.",
      sep = "\n"
    ),
    scope = list(
      can = c(
        "Describe referrals, access and waiting measures for RDY as provider.",
        "Show descriptive month-on-month trends where the Provider time series supports them."
      ),
      cannot = c(
        "Infer recovery or clinical outcomes from access measures alone.",
        "Draw conclusions from suppressed cells or missing values."
      )
    ),
    headline = c(
      if (!is.na(m053_val)) {
        paste0("Six-week access (M053) remains above the 75% national standard at ", m053_val, "%, but the selected trend window shows a falling pattern — review before it becomes a breach risk.")
      } else {
        NULL
      },
      if (!is.na(self_ref_pct)) {
        paste0("Self-referrals (M002: ", format(self_ref_n, big.mark = ","), ") account for ~", self_ref_pct, "% of referrals (M001) — confirm whether this route mix is expected locally.")
      } else {
        NULL
      },
      if (!is.na(wait_total_all)) {
        paste0("Open referrals with no activity: M019–M021 total ", format(wait_total_021, big.mark = ","),
               "; M019–M022 total ", format(wait_total_all, big.mark = ","),
               if (!is.na(m022_val)) paste0(" (including ", format(m022_val, big.mark = ","), " over 120 days).") else ".")
      } else {
        NULL
      },
      trend_window_label,
      "This brief covers access and waits only — recovery and outcome measures need a separate analysis."
    ),
    priority_callout = if (!is.na(m053_val) && isTRUE(trend_m053$available) && trend_m053$absolute_change < 0) {
      priority_callout_html(c(
        paste0("M053 six-week access falling in selected trend window (latest ", m053_val, "%) — still above 75% standard but needs pathway-owner review."),
        if (!is.na(m022_val) && m022_val > 0) paste0(format(m022_val, big.mark = ","), " open referrals with no activity over 120 days (M022) — key waiting-band flag.") else NULL
      ))
    } else {
      NULL
    },
    why_useful = c(
      "The agent triangulated referrals, access standards, waiting bands and self-referral mix from public Provider/RDY rows.",
      "It applied national access standards where documented and flagged where human pathway owners must confirm definitions."
    ),
    bottom_line = paste(
      "RDY remains above national six-week and eighteen-week access standards in the latest month,",
      "but six-week access is falling in the selected trend window and should be reviewed.",
      "Self-referrals dominate referral volume and waiting-band totals need pathway-owner validation.",
      "This is a useful first-draft access brief — not a complete Talking Therapies performance picture without outcomes."
    ),
    grouped_findings = list(
      list(
        title = "Access and waiting",
        items = list(
          list(
            title = "M001 — Referrals received",
            body = paste0(
              "New referrals received in the month. Latest: ",
              if (is.na(refs_n)) "—" else format(refs_n, big.mark = ","), ". ",
              trend_line(trend_m001)
            ),
            owner = "IAPT/data owner to confirm M001 matches local reporting."
          ),
          list(
            title = "M031 — People accessing services",
            body = paste0(
              "People who accessed talking therapies during the month. ",
              trend_line(trend_m031), " Access counts differ from referrals received — do not conflate."
            ),
            owner = "Confirm access definition and whether self-referral surge affects month-on-month movement."
          ),
          list(
            title = "M053 — Six-week access (finished course)",
            body = paste0(
              "Percentage accessing within 6 weeks among those finishing treatment. Latest: ",
              if (is.na(m053_val)) "—" else paste0(m053_val, "%"), ". Denominator checks required."
            ),
            owner = "Confirm national IAPT access definition locally."
          )
        )
      ),
      list(
        title = "Suppression and outcomes gap",
        items = list(
          list(
            title = "Waiting bands and suppression",
            body = paste0(
              "M019–M021 sum: ", if (is.na(wait_total_021)) "—" else format(wait_total_021, big.mark = ","),
              "; M019–M022 sum: ", if (is.na(wait_total_all)) "—" else format(wait_total_all, big.mark = ","),
              ". ", n_supp, " suppressed provider rows — do not infer from missing cells."
            ),
            owner = "Pathway owner to confirm 'no activity' coding vs local waiting lists."
          ),
          list(
            title = "Recovery/outcome measures not inferred",
            body = "Recovery/outcome measures (e.g. M192, M186) exist in the full CSV but are not inferred in this access brief.",
            owner = "Separate outcome analysis requires definition checks with the IAPT lead."
          )
        )
      )
    ),
    data_used_html = paste0(
      '<ul class="nhs-list-compact">',
      '<li>NHS Talking Therapies (<code>demo_talking_therapies.csv</code>)</li>',
      if (tt_has_window) '<li><code>trend_talking_therapies_rdy.csv</code> — six-month access stack</li>' else "",
      if (!is.null(tt_ts)) paste0('<li>Time series (<code>', esc(ts_file_note), '</code>)</li>') else "",
      '</ul>'
    ),
    period = trend_window_label,
    trend_available = if (tt_has_window) {
      paste0("Yes — ", tt_window_stats[[1]]$n_periods, " data points from ", ts_file_note)
    } else if (isTRUE(trend_m001$available)) {
      paste0("Yes — ", trend_m001$n_periods, "+ months in Provider time series")
    } else {
      "Limited — insufficient periods in extract"
    },
    agent_summary = c(
      if (!is.na(refs_n)) paste0(format(refs_n, big.mark = ","), " referrals received (M001) — six-month window ", trend_window, ".") else "M001 referrals not numeric in extract.",
      if (tt_has_window && any(rising)) paste0("Rising over six months: ", paste(names(tt_window_stats)[rising], collapse = ", "), ".") else NULL,
      if (tt_has_window && any(falling)) paste0("Falling over six months: ", paste(names(tt_window_stats)[falling], collapse = ", "), ".") else NULL,
      if (!is.na(wait_total_all)) paste0("Open referrals no activity: M019–M022 total ", format(wait_total_all, big.mark = ","), ".") else NULL,
      paste0(n_supp, " suppressed measures — do not infer from missing cells.")
    ),
    human_checks = standard_human_checks(),
    verify_intro = verify_intro_short(
      '<li><a href="../public-data/processed/demo_talking_therapies.csv">demo_talking_therapies.csv</a></li>',
      "Figures trace to demo CSV and time-series file — no derived ranks or peer medians."
    )
  )

  config$headline <- config$headline[!vapply(config$headline, is.null, logical(1))]
  config$agent_summary <- config$agent_summary[!vapply(config$agent_summary, is.null, logical(1))]

  verify_body <- traceability_verify_body(
    "See linked demo CSV and trend/time-series files.",
    c("demo_talking_therapies.csv", if (tt_has_window) "trend_talking_therapies_rdy.csv" else NULL, if (!is.null(tt_ts)) ts_file_note else NULL),
    "talking_therapies"
  )

  body <- agent_brief_sections(config, tt_kfe_html, verify_body, supporting_html)

  write_public_report("public-talking-therapies-profile.html",
    "Worked example: AI-assisted NHS Talking Therapies public-data briefing",
    "NHS Talking Therapies access and waits brief — RDY provider rows", body)
}

# --- E. Assurance profile ----------------------------------------------------

build_assurance_profile <- function() {
  assurance <- load_demo("demo_assurance_profile.csv", required = TRUE)
  dspt <- load_rdy_glob("^rdy_dspt_rdy.*\\.csv$")
  cqc_note <- if (file.exists(file.path(metadata_dir, "cqc_rdy_context_note.txt"))) {
    readLines(file.path(metadata_dir, "cqc_rdy_context_note.txt"), warn = FALSE)
  } else character()

  fft_manual <- file.path(metadata_dir, "fft_manual_download_needed.md")
  fft_trend <- load_trend_file("trend_fft_rdy.csv")
  fft_trend_obj <- if (!is.null(fft_trend) && nrow(fft_trend) > 0) {
    extract_stacked_trend(fft_trend, label = "FFT — org-level aggregate")
  } else {
    list(available = FALSE)
  }

  if (is.null(assurance)) {
    write_public_report("public-assurance-profile.html", "Public Assurance Profile",
      "Source data not available", '<section class="nhs-section"><p>Missing demo_assurance_profile.csv</p></section>')
    return(invisible(NULL))
  }

  dspt_latest <- if (!is.null(dspt) && nrow(dspt) > 0) dspt$Status[1] else "Not available"
  dspt_n <- if (!is.null(dspt)) nrow(dspt) else 0L

  ko41a <- load_rdy_glob("^rdy_ko41a.*Org Level\\.csv$")
  ko41a_new <- if (!is.null(ko41a) && "Complaints_Total_New" %in% names(ko41a)) {
    to_num(ko41a$Complaints_Total_New[1])
  } else {
    NA
  }
  ko41a_upheld <- if (!is.null(ko41a) && "Complaints_Number_Upheld" %in% names(ko41a)) {
    to_num(ko41a$Complaints_Number_Upheld[1])
  } else {
    NA
  }
  ko41a_upheld_rate <- if (!is.na(ko41a_new) && ko41a_new > 0 && !is.na(ko41a_upheld)) {
    round(100 * ko41a_upheld / ko41a_new, 1)
  } else {
    NA
  }

  eric <- load_rdy_glob("^rdy_eric_annual.*trust_data\\.csv$")
  eric_sites <- if (!is.null(eric) && "Total number of sites (No.)" %in% names(eric)) {
    trimws(as.character(eric[["Total number of sites (No.)"]][1]))
  } else {
    NA
  }
  eric_cap_maint <- if (!is.null(eric) && "Capital investment for maintaining (lifecycle) existing buildings (<a3>)" %in% names(eric)) {
    format(to_num(eric[["Capital investment for maintaining (lifecycle) existing buildings (<a3>)"]][1]), big.mark = ",")
  } else {
    NA
  }

  CQC_CONTEXT_META <- list(
    source_url = "https://www.cqc.org.uk/provider/RDY",
    source_title = "CQC provider page — Dorset Healthcare University NHS Foundation Trust",
    source_publication_date = "Main report July 2019; focused inspections to Mar 2026",
    accessed_date = "2026-06-21",
    confidence = "confirmed"
  )
  cqc_summary <- paste0(
    "Overall: Outstanding; Safe/Effective Good; Caring/Well-led Outstanding; Responsive Good. ",
    "Manually curated from public CQC page — regulatory context only. ",
    standard_metadata_cite(CQC_CONTEXT_META)
  )

  themes <- data.frame(
    Source = c("KO41a", "ERIC", "DSPT", "FFT", "CQC"),
    Latest_value = c(
      if (!is.na(ko41a_new)) paste0(ko41a_new, " new complaints; ", ko41a_upheld, " upheld (", ko41a_upheld_rate, "%)") else "RDY org-level row present",
      if (!is.na(eric_sites)) paste0(eric_sites, " sites; lifecycle capital maint £", eric_cap_maint) else "RDY trust row in 2024/25 ERIC",
      paste0("Latest public: ", dspt_latest),
      "No org-level RDY rows in downloaded FFT summary XLSX",
      "Overall Outstanding (main report 2019) — see CQC context"
    ),
    Currentness_risk = c(
      "Annual; may lag operational position",
      "Annual; may be amended",
      "Latest public assessment may not reflect 2025-26 submission (v8 deadline 30 Jun 2026)",
      "Monthly but setting-specific; org-level row may be absent",
      "Ratings may be old; focused inspections may be newer"
    ),
    Who_to_speak_to = c(
      "Complaints / PALS team",
      "Estates / facilities owner",
      "IG / calendar owner",
      "Patient experience lead",
      "Quality / governance lead"
    ),
    stringsAsFactors = FALSE
  )

  kpis <- list(
    list(value = nrow(assurance), label = "Assurance sources indexed"),
    list(value = dspt_n, label = "DSPT history rows"),
    list(value = "2024-25", label = "KO41a period"),
    list(value = "Context", label = "CQC / FFT gaps")
  )

  cqc_html <- if (length(cqc_note) > 0) {
    paste0("<pre style=\"white-space:pre-wrap;font-size:0.85rem;background:#F0F4F5;padding:1rem;border-radius:4px;\">",
           esc(paste(head(cqc_note, 12), collapse = "\n")), "</pre>")
  } else "<p><em>CQC context note not found.</em></p>"

  dspt_html <- if (!is.null(dspt)) html_table(dspt[, c("Status", "Date Published")], 8) else "<p><em>DSPT extract not available.</em></p>"

  key_figures <- paste0(
    kpi_row(kpis),
    period_caption_html("KO41a 2024-25; ERIC 2024/25; DSPT public history; FFT/CQC as noted"),
    '<h3>Assurance source map</h3>', html_table(themes),
    '<h3>DSPT public assessment history (RDY-specific page)</h3>', dspt_html
  )

  dspt_history_note <- if (dspt_n >= 2) {
    paste0(
      "Descriptive history across ", dspt_n, " published assessment rows — ",
      "earliest: ", tail(dspt$Status, 1), " (", tail(dspt$`Date Published`, 1), "); ",
      "latest: ", dspt$Status[1], " (", dspt$`Date Published`[1], "). ",
      "Status labels are annual assurance signals, not operational IG detail."
    )
  } else {
    "Insufficient DSPT history rows for descriptive summary."
  }

  assurance_kfe_specs <- list(
    list(
      figure = "KO41a — Written complaints (annual)",
      what = "Annual statutory written complaints return for 2024-25.",
      latest = if (!is.na(ko41a_new)) {
        paste0(ko41a_new, " new; ", ko41a_upheld, " upheld (2024-25)")
      } else {
        "RDY org-level row present (2024-25 extract)"
      },
      standard_detail = "No simple performance target — annual assurance participation",
      comparator_detail = if (!is.na(ko41a_upheld_rate)) {
        paste0("Upheld rate ~", ko41a_upheld_rate, "%. From rdy_ko41a_*Org Level.csv — Complaints_Total_New, Complaints_Number_Upheld.")
      } else {
        "From rdy_ko41a org-level CSV — source validation."
      },
      trend = NULL,
      trend_label = "Not available",
      judgement = "Participation confirmed — volume and themes need complaints team, not this brief alone.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Complaints team to confirm 2024-25 figures, PALS themes and whether public row matches internal reporting.")
    ),
    list(
      figure = "ERIC — Estates returns (annual)",
      what = "Annual estates and facilities cost/activity return — benchmarking context.",
      latest = if (!is.na(eric_sites)) {
        paste0(eric_sites, " sites; lifecycle capital maintenance £", eric_cap_maint, " (2024/25)")
      } else {
        "RDY trust row in 2024/25 ERIC extract"
      },
      standard_detail = "Benchmarking context — no simple good/bad score",
      comparator_detail = "From rdy_eric_annual_trust_data.csv — Total number of sites, Capital investment for maintaining (lifecycle) existing buildings.",
      trend = NULL,
      trend_label = "Not available",
      judgement = "ERIC supports facilities benchmarking — confirm amendments file and board reporting year.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Estates/facilities owner to confirm amendments file and whether board reporting uses the same year.")
    ),
    list(
      figure = "DSPT — Data Security and Protection Toolkit",
      what = "Annual IG assurance assessment status published on the DSPT organisation page.",
      latest = dspt_latest,
      standard_detail = "Annual assurance label — not operational IG dashboard",
      comparator_detail = if (dspt_n >= 2) {
        paste0("Latest: ", dspt$Status[1], " (", dspt$`Date Published`[1], "). 2025-26 v8 deadline: 30 June 2026.")
      } else {
        paste0(dspt_history_note, " 2025-26 v8 deadline: 30 June 2026.")
      },
      trend = NULL,
      trend_label = "Not available",
      judgement = "Latest public status is 2024-25 Standards met, but confirm 2025-26 submission readiness with IG before assurance use.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("IG/calendar owner to confirm 2025-26 DSPT v8 submission status and internal IG audit programme.")
    ),
    list(
      figure = "FFT — Friends and Family Test (org-level gap)",
      what = "Patient experience survey — FFT is split across setting-specific files.",
      latest = "No org-level RDY rows in downloaded FFT summary XLSX",
      standard_detail = "Setting-specific publication (community MH, mental health at service-category; outpatient at org)",
      comparator_detail = if (file.exists(fft_manual)) {
        "FFT split by setting — org-level workflow gap; see metadata/fft_manual_download_needed.md."
      } else {
        "Org-level data not in current extract — setting-level download may be needed."
      },
      trend = fft_trend_obj,
      trend_label = "Not available",
      judgement = "Missing org-level row is a workflow/design issue — not evidence of poor patient experience.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Patient experience lead to confirm setting-level FFT files and whether service-category rows are more appropriate.")
    ),
    list(
      figure = "CQC — Regulatory context",
      what = "Care Quality Commission provider page — regulatory background only.",
      latest = "Overall Outstanding — main provider report July 2019",
      standard_detail = "Regulatory context — not a performance metric",
      comparator_detail = cqc_summary,
      trend = NULL,
      trend_label = "Not available",
      judgement = "Use as regulatory context only — confirm latest internal action plans with quality/governance lead.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Quality/governance lead to confirm latest inspection status and internal action plans.")
    )
  )

  assurance_kfe_html <- key_figures_explained_section(
    assurance_kfe_specs,
    "Public statutory assurance source map — not a composite performance score.",
    show_trend = FALSE,
    show_validation = TRUE,
    period_caption = "KO41a 2024-25; ERIC 2024/25; DSPT public history; FFT/CQC as noted",
    trend_note = "Assurance sources are mostly annual snapshots or descriptive history — not numeric performance trends."
  )

  commentary_cards <- c(
    build_commentary_card(
      "KO41a — Written complaints (annual)",
      "Review locally", "review",
      list(
        "Plain-English meaning" = "Annual statutory return confirming RDY submitted written complaints data for 2024-25.",
        "Latest value / position" = "RDY org-level row present in downloaded 2024-25 extract",
        "Comparator / trend" = "Single annual snapshot in current extract — complaints trends need multiple years or local data.",
        "Agent flag" = "Review locally",
        "Cautious interpretation" = "Presence in KO41a confirms participation — volume, themes and small numbers need the complaints team, not this brief.",
        "Human check required" = "Complaints team to confirm 2024-25 figures, PALS themes and whether public row matches internal reporting."
      )
    ),
    build_commentary_card(
      "ERIC — Estates returns (annual)",
      "Review locally", "review",
      list(
        "Plain-English meaning" = "Annual estates and facilities cost/activity return — benchmarking and assurance context.",
        "Latest value / position" = "RDY trust row in 2024/25 ERIC extract",
        "Comparator / trend" = "Annual cadence — trend requires multiple ERIC years or local estates dashboards.",
        "Agent flag" = "Review locally",
        "Cautious interpretation" = "ERIC supports facilities benchmarking conversations — not a simple 'good/bad' performance score.",
        "Human check required" = "Estates/facilities owner to confirm amendments file and whether board reporting uses the same year."
      )
    ),
    build_commentary_card(
      "DSPT — Data Security and Protection Toolkit",
      "Potential strength", "strength",
      list(
        "Plain-English meaning" = "Annual IG assurance assessment status published on the DSPT organisation page.",
        "Latest value / position" = dspt_latest,
        "Comparator / trend" = dspt_history_note,
        "Agent flag" = "Potential strength",
        "Cautious interpretation" = paste0(
          "'Standards met' is an annual assurance label — it does not prove day-to-day operational IG compliance. ",
          "Historical rows show movement from 'Approaching standards' (2018-19, 2020-21) to 'Standards met' in recent years — descriptive history only."
        ),
        "Human check required" = "IG/calendar owner to confirm current-year submission status and internal IG audit programme."
      )
    ),
    build_commentary_card(
      "FFT — Friends and Family Test (org-level gap)",
      "Watch / clarify", "watch",
      list(
        "Plain-English meaning" = "Patient experience survey aggregate — org-level FFT rows expected in summary downloads.",
        "Latest value / position" = "No org-level RDY rows in downloaded FFT summary XLSX",
        "Comparator / trend" = if (isTRUE(fft_trend_obj$available)) {
          paste0(
            "Historic FFT stack: ", format(fft_trend_obj$latest_value, big.mark = ","),
            " (", fft_trend_obj$latest_period, ") vs ",
            format(fft_trend_obj$previous_value, big.mark = ","), " (", fft_trend_obj$previous_period, ")."
          )
        } else if (file.exists(fft_manual)) {
          "Org-level trend not available — see metadata/fft_manual_download_needed.md for manual download steps."
        } else {
          "Trend not available until org-level or setting-level data is obtained."
        },
        "Agent flag" = "Watch / clarify",
        "Cautious interpretation" = "Missing org-level FFT is a workflow gap for analysts — not evidence of poor experience.",
        "Human check required" = "Patient experience lead to confirm whether setting-level XLSX download fills the gap."
      )
    ),
    build_commentary_card(
      "CQC — Regulatory context (not statistical data)",
      "Source validation only", "definition",
      list(
        "Plain-English meaning" = "Care Quality Commission provider page captured as context note — inspection and regulatory background.",
        "Latest value / position" = "Context note in metadata — not a performance metric",
        "Comparator / trend" = "Not applicable — qualitative regulatory context only.",
        "Agent flag" = "Source validation only",
        "Cautious interpretation" = "CQC information supports assurance conversations — it must not be treated as a league table or performance proxy.",
        "Human check required" = "Quality/governance lead to confirm latest inspection status and internal action plans."
      )
    )
  )

  dspt_trend_section <- if (dspt_n >= 2) {
    paste0(
      '<section class="nhs-section nhs-trend-section">',
      '<h2>DSPT assessment history (descriptive)</h2>',
      '<p>Multiple published assessment rows exist for RDY — summarised descriptively, not as a numeric performance trend.</p>',
      html_table(dspt[, c("Status", "Date Published")]),
      '<p><em>Annual IG assurance labels only — confirm current submission with the IG owner.</em></p>',
      '</section>'
    )
  } else {
    trend_not_available_section(c(
      "Multiple DSPT assessment rows in the public organisation history",
      "Consistent status labels across publication dates",
      "IG owner confirmation of current-year submission"
    ))
  }

  fft_trend_section <- if (isTRUE(fft_trend_obj$available)) {
    trend_section(
      list(fft_trend_obj),
      "trend_fft_rdy.csv",
      c(
        "FFT response rates vary; small number suppression may apply.",
        "Confirm measure definition and setting scope with patient experience lead."
      )
    )
  } else if (file.exists(fft_manual)) {
    paste0(
      '<section class="nhs-section nhs-trend-section">',
      '<h2>FFT trend not available from current extract</h2>',
      '<p>Public FFT summary XLSX files did not yield org-level RDY rows suitable for trend analysis.</p>',
      '<p>See <code>site/public-data/metadata/fft_manual_download_needed.md</code> for URLs tried and recommended manual steps.</p>',
      '</section>'
    )
  } else {
    trend_not_available_section(c(
      "Setting-level or trust-level FFT XLSX with RDY org code",
      "At least two comparable publication months",
      "Patient experience lead confirmation of response rates and suppression"
    ))
  }

  supporting_html <- paste0(
    collapsible_details("Assurance source tables and DSPT history", key_figures),
    collapsible_details("Additional source commentary", theme_commentary_section(commentary_cards)),
    wrap_trend_collapsible(dspt_trend_section, "DSPT assessment history (descriptive)"),
    if (nzchar(fft_trend_section)) wrap_trend_collapsible(fft_trend_section, "FFT trend detail") else ""
  )

  verify_extra <- paste0(
    '<details class="nhs-verify-details"><summary>Assurance index (demo extract — full columns)</summary>',
    html_table(assurance),
    '</details>',
    '<details class="nhs-verify-details"><summary>CQC regulatory context note</summary>',
    cqc_html,
    '</details>'
  )

  config <- list(
    question = paste(
      "Which public assurance artefacts contain RDY rows, what is each useful for,",
      "what are the gaps (FFT org-level, CQC non-statistical context),",
      "and how should a Business & Performance Partner use this as a conversation starter?"
    ),
    dataset_line = "KO41a, ERIC, DSPT, FFT and CQC public sources (assurance index)",
    prompt_excerpt = paste(
      "Index public assurance sources for RDY: KO41a, ERIC, DSPT, FFT, CQC.",
      "Appropriate-use language only — no operational IG or complaints performance conclusions.",
      sep = "\n"
    ),
    scope = list(
      can = c(
        "Confirm which public assurance sources contain RDY rows and what each is useful for.",
        "Document gaps such as missing org-level FFT rows or non-statistical CQC context."
      ),
      cannot = c(
        "Combine assurance sources into a performance score or league table.",
        "Draw operational IG, complaints or patient experience conclusions without local owner validation."
      )
    ),
    headline = c(
      if (!is.na(ko41a_new)) paste0("KO41a 2024-25: ", ko41a_new, " new written complaints, ", ko41a_upheld, " upheld — complaints team should confirm themes and local position.") else "KO41a confirms RDY participation in 2024-25 written complaints return.",
      if (!is.na(eric_sites)) paste0("ERIC 2024/25: ", eric_sites, " sites with exemplar estate metrics extracted — estates owner should confirm amendments.") else "ERIC confirms RDY trust row in 2024/25 estates return.",
      paste0("DSPT latest public assessment: ", dspt_latest, " — but 2025-26 v8 deadline is 30 June 2026; confirm current-year submission with IG."),
      "FFT org-level rows absent — setting-specific files may hold relevant rows; patient experience lead follow-up needed.",
      "CQC overall Outstanding (main report 2019) — regulatory context only, not a performance score."
    ),
    why_useful = c(
      "The agent mapped which public assurance sources contain RDY rows and what each can safely support.",
      "It extracted traceable values where possible, flagged currentness risks, and named who to speak to next."
    ),
    bottom_line = paste(
      "This is an assurance source map, not a performance report.",
      "Public data confirms RDY participation in key statutory returns and shows DSPT Standards met on the latest published assessment,",
      "but FFT needs setting-level follow-up and all sources need owner confirmation before any meeting use."
    ),
    grouped_findings = list(
      list(
        title = "Statutory participation confirmed",
        items = list(
          list(
            title = "KO41a — Written complaints (annual)",
            body = "RDY org-level row present in 2024-25 extract. Presence confirms participation — volume and themes need the complaints team.",
            owner = "Complaints team to confirm 2024-25 figures and whether public row matches internal reporting."
          ),
          list(
            title = "ERIC — Estates returns (annual)",
            body = "RDY trust row in 2024/25 ERIC extract. Supports facilities benchmarking — not a simple good/bad score.",
            owner = "Estates/facilities owner to confirm amendments file and board reporting year."
          )
        )
      ),
      list(
        title = "IG and patient experience",
        items = list(
          list(
            title = "DSPT — Data Security and Protection Toolkit",
            body = paste0("Latest public assessment: ", dspt_latest, ". ", dspt_history_note),
            owner = "IG/calendar owner to confirm current-year submission status."
          ),
          list(
            title = "FFT — Friends and Family Test (org-level gap)",
            body = "No org-level RDY rows in downloaded FFT summary XLSX — workflow gap for analysts, not evidence of poor experience.",
            owner = "Patient experience lead to confirm whether setting-level XLSX download fills the gap."
          )
        )
      ),
      list(
        title = "Regulatory context",
        items = list(
          list(
            title = "CQC — Regulatory context",
            body = "Care Quality Commission provider page captured as context note — inspection and regulatory background only.",
            owner = "Quality/governance lead to confirm latest inspection status and internal action plans."
          )
        )
      )
    ),
    data_used_html = paste0(
      '<ul class="nhs-list-compact">',
      '<li><code>demo_assurance_profile.csv</code></li>',
      '<li>KO41a, ERIC, DSPT processed extracts</li>',
      '<li>CQC context note (regulatory background only)</li></ul>'
    ),
    period = "KO41a 2024-25; ERIC 2024/25; DSPT public history; FFT/CQC as noted.",
    trend_available = if (dspt_n >= 2) "Descriptive DSPT history only — not numeric performance trend" else "Annual snapshots only — KO41a/ERIC/FFT cannot support six-month numeric trends",
    agent_summary = c(
      "Public data confirms RDY participation in KO41a complaints and ERIC estates returns.",
      paste0("Latest DSPT public assessment: ", dspt_latest, " — annual assurance label, not operational IG detail."),
      "FFT org-level rows were absent in the downloaded summary file — patient experience lead follow-up needed.",
      "CQC provides regulatory context only — not a performance score or league table."
    ),
    human_checks = standard_human_checks(list(
      list(
        q = "Who is the named owner for each return this cycle?",
        expl = "KO41a, ERIC, DSPT and FFT each have accountable teams — confirm before citing in assurance packs."
      )
    )),
    verify_intro = verify_intro_short(
      '<li><a href="../public-data/processed/demo_assurance_profile.csv">demo_assurance_profile.csv</a></li>',
      "Assurance index figures trace to demo and processed extracts — no derived ranks or peer medians."
    )
  )

  verify_body <- paste0(
    traceability_verify_body(
      "See linked demo CSV and filter notes.",
      "demo_assurance_profile.csv",
      c("ko41a_annual", "eric_annual", "dspt_rdy")
    ),
    verify_extra
  )

  body <- agent_brief_sections(config, assurance_kfe_html, verify_body, supporting_html)

  write_public_report("public-assurance-profile.html",
    "Worked example: AI-assisted public statutory assurance source map",
    "KO41a, ERIC, DSPT, FFT and CQC — assurance navigation brief (public data only)", body)
}

# --- F. Urgent care / diagnostics check --------------------------------------

build_urgent_diagnostics <- function() {
  dm01_demo <- load_demo("demo_dm01_diagnostics.csv")
  dm01 <- load_rdy_glob("^rdy_dm01_monthly.*\\.csv$")
  ae <- load_rdy_glob("^rdy_ae_monthly.*\\.csv$")
  kh03 <- load_demo("demo_kh03_beds.csv")
  if (is.null(kh03)) kh03 <- load_trend_file("latest_kh03_beds_rdy.csv")

  ae_trend <- load_trend_file("trend_ae_rdy.csv")
  dm01_trend <- load_trend_file("trend_dm01_rdy.csv")
  kh03_trend_df <- load_trend_file("trend_kh03_beds_rdy.csv")

  ae_ed_zero <- FALSE
  ae_other_adm <- NA
  if (!is.null(ae)) {
    ed_cols <- c("A&E attendances Type 1", "A&E attendances Type 2")
    for (col in ed_cols) {
      if (col %in% names(ae)) {
        v <- to_num(ae[[col]][1])
        if (!is.na(v) && v == 0) ae_ed_zero <- TRUE
      }
    }
    if ("Other emergency admissions" %in% names(ae)) {
      ae_other_adm <- to_num(ae[["Other emergency admissions"]][1])
    }
  }

  dm01_summary <- NULL
  dm01_top_test <- "n/a"
  trend_dm01_activity <- list(available = FALSE, n_periods = 0L)
  if (!is.null(dm01_trend) && nrow(dm01_trend) > 0) {
    aud <- dm01_trend[grepl("Audiology", dm01_trend$measure_name, ignore.case = TRUE), , drop = FALSE]
    if (nrow(aud) > 0) {
      dm01_top_test <- aud$measure_name[1]
      trend_dm01_activity <- extract_stacked_trend(aud, measure_name = aud$measure_name[1], label = "DM01 — Audiology total activity")
    } else {
      top_test <- dm01_trend$measure_name[which.max(to_num(dm01_trend$metric_value))]
      dm01_top_test <- top_test
      trend_dm01_activity <- extract_stacked_trend(dm01_trend, measure_name = top_test, label = paste0("DM01 — ", top_test, " total activity"))
    }
  }

  trend_ae_other <- extract_stacked_trend(
    ae_trend, measure_id = "OTHER_EM_ADM",
    label = "A&E — other emergency admissions (source validation)"
  )
  trend_ae_type12 <- extract_stacked_trend(
    ae_trend, measure_id = "AE_TYPE1",
    label = "A&E — Type 1 attendances (expected zero at RDY)"
  )

  if (!is.null(dm01) && "Diagnostic Tests" %in% names(dm01)) {
    dm01$Total_WL <- to_num(dm01$`Total WL`)
    dm01$Total_Activity <- to_num(dm01$`Total Activity`)
    dm01_summary <- dm01[, c("Diagnostic Tests", "Total WL", "Total Activity", "13+ Weeks")]
    dm01_summary <- dm01_summary[order(-dm01$Total_Activity), ]
    if (nrow(dm01_summary) > 0) {
      non_total <- dm01_summary[!grepl("^TOTAL$", trimws(dm01_summary$`Diagnostic Tests`), ignore.case = TRUE), , drop = FALSE]
      if (nrow(non_total) > 0) dm01_top_test <- non_total$`Diagnostic Tests`[1]
    }
  }

  ae_file_note <- basename(list.files(processed_dir, pattern = "^rdy_ae_monthly.*\\.csv$")[1])
  if (length(ae_file_note) == 0 || is.na(ae_file_note)) ae_file_note <- "rdy_ae_monthly_*.csv"
  dm01_mar_activity <- NA
  if (!is.null(dm01) && "Diagnostic Tests" %in% names(dm01)) {
    row <- dm01[trimws(dm01$`Diagnostic Tests`) == dm01_top_test, , drop = FALSE]
    if (nrow(row) > 0) dm01_mar_activity <- to_num(row$`Total Activity`[1])
  }

  kh03_trend <- extract_stacked_trend(
    kh03_trend_df,
    measure_id = "Mental Illness",
    label = "KH03 — Mental illness overnight beds (recent snapshots)"
  )

  ae_latest_period <- if (isTRUE(trend_ae_other$available)) trend_ae_other$latest_period else "latest A&E extract"
  dm01_latest <- latest_from_trend(dm01_trend, measure_name = dm01_top_test)
  if (!is.null(dm01_latest) && !is.na(dm01_latest$value)) dm01_mar_activity <- dm01_latest$value
  dm01_latest_period <- if (!is.null(dm01_latest)) dm01_latest$period else if (isTRUE(trend_dm01_activity$available)) trend_dm01_activity$latest_period else "latest DM01 extract"

  dm01_window_spec <- setNames(
    list(list(
      measure_name = dm01_top_test,
      label = paste0("DM01 — ", dm01_top_test, " total activity"),
      note = "Provisional DM01 monthly diagnostics; audiology may dominate activity counts."
    )),
    "dm01_top"
  )
  dm01_window_stats <- if (!is.null(dm01_trend) && dm01_top_test != "n/a") {
    build_window_stats(dm01_trend, dm01_window_spec, 6L)
  } else {
    list()
  }
  ae_window_spec <- list(
    other_adm = list(
      measure_id = "OTHER_EM_ADM",
      label = "A&E — other emergency admissions (source validation)",
      note = "Source validation only — small counts need service-owner confirmation."
    )
  )
  ae_window_stats <- if (!is.null(ae_trend)) build_window_stats(ae_trend, ae_window_spec, 6L) else list()
  dm01_trend_window <- if (length(dm01_window_stats) > 0 && nzchar(dm01_window_stats[[1]]$window_start %||% "")) {
    paste0(dm01_window_stats[[1]]$window_start, " \u2013 ", dm01_window_stats[[1]]$window_end)
  } else if (isTRUE(trend_dm01_activity$available)) {
    paste0(trend_dm01_activity$previous_period, " to ", trend_dm01_activity$latest_period)
  } else {
    dm01_latest_period
  }
  ae_trend_window <- if (length(ae_window_stats) > 0 && nzchar(ae_window_stats[[1]]$window_start %||% "")) {
    paste0(ae_window_stats[[1]]$window_start, " \u2013 ", ae_window_stats[[1]]$window_end)
  } else if (isTRUE(trend_ae_other$available)) {
    paste0(trend_ae_other$n_periods, " months in trend_ae_rdy.csv")
  } else {
    ae_latest_period
  }
  ae_n_periods <- if (!is.null(ae_trend)) length(unique(na.omit(trimws(ae_trend$reporting_period_start)))) else 0L
  dm01_n_periods <- if (!is.null(dm01_trend)) length(unique(na.omit(trimws(dm01_trend$reporting_period_start)))) else 0L

  dm01_wl_total <- NA
  dm01_wl_13 <- NA
  dm01_aud_wl <- NA
  if (!is.null(dm01_summary) && nrow(dm01_summary) > 0) {
    total_row <- dm01_summary[grepl("^TOTAL$", trimws(dm01_summary$`Diagnostic Tests`), ignore.case = TRUE), , drop = FALSE]
    if (nrow(total_row) > 0) {
      dm01_wl_total <- to_num(total_row$`Total WL`[1])
      dm01_wl_13 <- to_num(total_row$`13+ Weeks`[1])
    }
    aud_row <- dm01_summary[grepl("Audiology", dm01_summary$`Diagnostic Tests`, ignore.case = TRUE), , drop = FALSE]
    if (nrow(aud_row) > 0) dm01_aud_wl <- to_num(aud_row$`Total WL`[1])
  }

  kh03_stale_note <- if (isTRUE(kh03_trend$available)) {
    paste0(
      "KH03 supporting extract: ", kh03_trend$n_periods,
      " quarterly snapshots from ", kh03_trend$series$period[1], " to ", kh03_trend$latest_period,
      " (may lag monthly A&E/DM01 dates)."
    )
  } else {
    "KH03 quarterly trend not available from current extract."
  }

  urgent_has_ae_dm01_trend <- isTRUE(trend_ae_other$available) || isTRUE(trend_dm01_activity$available) ||
    any(vapply(dm01_window_stats, function(s) isTRUE(s$available), logical(1)))

  source_check <- data.frame(
    Source = c("A&E monthly provider", "DM01 diagnostics", "KH03 overnight beds"),
    RDY_present = c(!is.null(ae), !is.null(dm01), !is.null(kh03)),
    Notes = c(
      if (!is.null(ae)) paste0("Other emergency admissions: ", if (is.na(ae_other_adm)) "?" else ae_other_adm, "; 0 Type 1/2 A&E attendances") else "Extract not found",
      if (!is.null(dm01)) paste0(nrow(dm01), " diagnostic test rows for RDY (", dm01_latest_period, ")") else "Extract not found",
      if (!is.null(kh03)) paste0(nrow(kh03), " bed rows — latest snapshot from historic pipeline") else "Extract not found"
    ),
    stringsAsFactors = FALSE
  )
  source_check$RDY_present <- ifelse(source_check$RDY_present, "Yes", "No")

  urgent_kfe_specs <- list(
    list(
      figure = "A&E — RDY row and Type 1/2 attendances",
      what = "Monthly A&E provider statistics confirming RDY appears in the public file; Type 1 and Type 2 attendance columns.",
      latest = paste0("RDY present; Type 1/2 A&E attendances = 0 (", ae_latest_period, " extract)"),
      comparator_type = "validation_only",
      comparator_detail = "Source validation — RDY does not operate a Type 1/2 emergency department.",
      trend = trend_ae_type12,
      polarity = "validation_only",
      trend_label = "Source validation only",
      judgement = "Zero Type 1/2 ED attendances — no ED performance conclusion for RDY; treat as source-presence check only.",
      validation_status = "Source validation only",
      human_check = short_human_check("Urgent/emergency care lead to confirm service model and what is coded in A&E returns.")
    ),
    list(
      figure = "A&E — Other emergency admissions",
      what = "Count of other emergency admissions recorded in the monthly A&E provider file for RDY.",
      latest = if (!is.na(ae_other_adm)) paste0(ae_other_adm, " (", ae_latest_period, ")") else "See A&E extract",
      comparator_detail = if (isTRUE(trend_ae_other$available)) {
        paste0(
          "Previous period: ", format(trend_ae_other$previous_value, big.mark = ","),
          " (", trend_ae_other$previous_period, "). Historic stack: trend_ae_rdy.csv (", ae_n_periods, " periods)."
        )
      } else if (!is.null(ae_trend)) {
        paste0("Historic stack in trend_ae_rdy.csv (", ae_n_periods, " periods) — source validation only.")
      } else {
        "Single month only."
      },
      trend = trend_ae_other,
      polarity = "validation_only",
      trend_label = "Source validation only",
      judgement = "Small counts should be treated as coding/service-model-specific until confirmed — not ED activity.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Confirm what 'other emergency admissions' represents for RDY in the national return.")
    ),
    list(
      figure = paste0("DM01 — ", dm01_top_test, " (activity and waiting list)"),
      what = "Monthly diagnostic waiting list and activity by test type for RDY as provider.",
      latest = if (!is.na(dm01_mar_activity)) {
        paste0("Activity ", format(dm01_mar_activity, big.mark = ","), " (", dm01_latest_period, ")")
      } else {
        paste0("See DM01 extract — top test: ", dm01_top_test, " (", dm01_latest_period, ")")
      },
      comparator_detail = if (!is.na(dm01_aud_wl) && !is.na(dm01_wl_total)) {
        paste0("Audiology WL ", format(dm01_aud_wl, big.mark = ","), " of ", format(dm01_wl_total, big.mark = ","),
               " total; ", if (!is.na(dm01_wl_13)) paste0(dm01_wl_13, " waits 13+ weeks.") else "")
      } else if (isTRUE(trend_dm01_activity$available)) {
        paste0("Previous activity: ", format(trend_dm01_activity$previous_value, big.mark = ","),
               " (", trend_dm01_activity$previous_period, ").")
      } else {
        "See DM01 summary table."
      },
      trend = trend_dm01_activity,
      polarity = "unknown",
      trend_label = if (isTRUE(trend_dm01_activity$available)) {
        if (trend_dm01_activity$absolute_change > 0) "Rising" else if (trend_dm01_activity$absolute_change < 0) "Falling" else "Stable"
      } else {
        "Not available"
      },
      judgement = "DM01 for RDY is mainly an audiology/community diagnostics picture — audiology accounts for most waiting-list volume in the extract.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Diagnostics service owner to confirm DM01 mapping to local community diagnostic pathways.")
    ),
    list(
      figure = "KH03 — Mental illness overnight beds",
      what = "Quarterly overnight bed stock for Mental Illness sector — bed-stock source, not diagnostics.",
      latest = if (isTRUE(kh03_trend$available)) {
        paste0(format(kh03_trend$latest_value, big.mark = ","), " beds (", kh03_trend$latest_period, " — ", kh03_trend$measure_label, ")")
      } else {
        "See KH03 extract — verify latest quarter on NHS England site"
      },
      comparator_detail = if (isTRUE(kh03_trend$available)) {
        paste0(
          "Previous snapshot: ", format(kh03_trend$previous_value, big.mark = ","),
          " (", kh03_trend$previous_period, "). ", kh03_stale_note
        )
      } else {
        kh03_stale_note
      },
      trend = kh03_trend,
      polarity = "unknown",
      trend_label = if (isTRUE(kh03_trend$available)) "Stable" else "Not available",
      judgement = "KH03 is a quarterly bed-stock source — label metric name (available vs occupied) before interpreting values.",
      validation_status = "Local owner confirmation needed",
      human_check = short_human_check("Bed management/estates lead to confirm latest KH03 quarter, metric definition and alignment with internal bed state.")
    )
  )

  urgent_kfe_html <- key_figures_explained_section(
    urgent_kfe_specs,
    paste(
      "These sources are grouped because they were checked together, not because they form a single performance pathway.",
      "Main summary uses latest period per source; supporting extracts may contain longer histories."
    ),
    show_trend = TRUE,
    show_validation = TRUE,
    period_caption = paste0(
      "A&E headline: ", ae_latest_period, " (extract: ", ae_n_periods, " periods); ",
      "DM01 headline: ", dm01_latest_period, " (extract: ", dm01_n_periods, " periods); ",
      if (isTRUE(kh03_trend$available)) paste0("KH03: ", kh03_trend$n_periods, " quarterly snapshots") else "KH03: see extract"
    )
  )

  kh03_mi <- NULL
  if (!is.null(kh03) && "Sector" %in% names(kh03)) {
    mi <- kh03[kh03$Sector == "Mental Illness", , drop = FALSE]
    if (nrow(mi) > 0) {
      mi$Beds <- to_num(if ("Number_Of_Beds" %in% names(mi)) mi$Number_Of_Beds else mi$metric_value)
      snap_col <- if ("Effective_Snapshot_Date" %in% names(mi)) "Effective_Snapshot_Date" else "reporting_period_start"
      kh03_mi <- mi[, intersect(c(snap_col, "Number_Of_Beds", "metric_value"), names(mi)), drop = FALSE]
    }
  } else if (!is.null(kh03) && "measure_id" %in% names(kh03)) {
    mi <- kh03[kh03$measure_id == "Mental Illness", , drop = FALSE]
    if (nrow(mi) > 0) {
      kh03_mi <- mi[, intersect(c("reporting_period_start", "measure_id", "measure_name", "metric_value"), names(mi)), drop = FALSE]
    }
  }

  ae_summary_html <- if (!is.null(ae)) {
    cols <- intersect(c("Period", "Org Code", "Org name", "A&E attendances Type 1", "Other emergency admissions"), names(ae))
    html_table(ae[, cols, drop = FALSE])
  } else {
    "<p><em>No A&E RDY extract.</em></p>"
  }

  ae_detail_html <- if (!is.null(ae)) html_table(ae) else "<p><em>No A&E RDY extract.</em></p>"

  kpis <- list(
    list(value = if (!is.null(ae)) "Yes" else "No", label = "RDY in A&E file"),
    list(value = if (!is.null(dm01)) nrow(dm01) else 0, label = "DM01 RDY rows"),
    list(value = if (!is.null(kh03)) nrow(kh03) else 0, label = "KH03 RDY rows"),
    list(value = dm01_latest_period, label = "DM01 latest period")
  )

  key_figures <- paste0(
    kpi_row(kpis),
    period_caption_html(paste0("A&E: ", ae_latest_period, "; DM01: ", dm01_latest_period)),
    '<h3>Source presence check</h3>', html_table(source_check),
    '<h3>A&amp;E RDY row — interpretive columns only (', esc(ae_latest_period), ')</h3>', ae_summary_html,
    if (!is.null(dm01_summary)) {
      paste0('<h3>DM01 diagnostics summary — ', esc(dm01_latest_period), '</h3>', html_table(dm01_summary, 15))
    } else {
      ""
    },
    if (!is.null(dm01_summary)) {
      bar_chart(dm01_summary$`Diagnostic Tests`, to_num(dm01_summary$`Total Activity`),
                paste0("DM01 total activity by test (RDY, ", dm01_latest_period, ")"),
                period_caption = dm01_latest_period)
    } else {
      ""
    }
  )

  commentary_cards <- c(
    build_commentary_card(
      "A&E monthly — RDY presence and zero ED attendances",
      "Source validation only", "definition",
      list(
        "Plain-English meaning" = "Monthly A&E provider statistics — attendance and emergency admission columns.",
        "Latest value / position" = paste0(
          "RDY row present; Type 1/2 A&E attendances = 0",
          if (!is.na(ae_other_adm)) paste0("; other emergency admissions = ", ae_other_adm) else ""
        ),
        "Comparator / trend" = if (!is.null(ae_trend)) {
          n_ae <- length(unique(na.omit(trimws(ae_trend$reporting_period_start))))
          paste0(
            "Historic A&E stack (", n_ae, " months) — ",
            "Type 1/2 attendances remain zero; source validation only."
          )
        } else {
          "Single month A&E extract — run script 05 for historic stack."
        },
        "Agent flag" = "Source validation only",
        "Cautious interpretation" = paste0(
          "Zero ED attendances are consistent with RDY not operating a Type 1/2 emergency department — ",
          "this row confirms source presence and coding, not urgent care performance. ",
          if (!is.na(ae_other_adm) && ae_other_adm <= 5) {
            paste0("Small other emergency admission count (", ae_other_adm, ") needs service-owner confirmation of what is included.")
          } else ""
        ),
        "Human check required" = "Urgent/emergency care lead to confirm service model and what 'other emergency admissions' represents for RDY."
      )
    ),
    build_commentary_card(
      paste0("DM01 — Diagnostic tests (", dm01_latest_period, ")"),
      "Review locally", "review",
      list(
        "Plain-English meaning" = "Monthly diagnostic waiting list and activity by test type for RDY as provider.",
        "Latest value / position" = if (!is.null(dm01)) paste0(nrow(dm01), " test rows; highest activity: ", dm01_top_test) else "Extract not found",
        "Comparator / trend" = if (isTRUE(trend_dm01_activity$available)) {
          paste0(
            "Historic DM01 stack: latest vs previous month for ", dm01_top_test, " — ",
            format(trend_dm01_activity$latest_value, big.mark = ","), " vs ",
            format(trend_dm01_activity$previous_value, big.mark = ","), "."
          )
        } else if (!is.null(dm01_trend)) {
          "Historic DM01 file present but fewer than two comparable months for top test."
        } else {
          "Single month in current extract — run script 05 for DM01 historic stack."
        },
        "Agent flag" = "Review locally",
        "Cautious interpretation" = "Audiology and community diagnostics may dominate activity — do not infer national waiting position without local validation.",
        "Human check required" = "Diagnostics service owner to confirm DM01 mapping to local community diagnostic pathways."
      )
    ),
    build_commentary_card(
      "KH03 — Overnight mental illness beds (snapshot dates)",
      if (isTRUE(kh03_trend$available)) "Watch / clarify" else "Trend not available",
      if (isTRUE(kh03_trend$available)) "watch" else "definition",
      list(
        "Plain-English meaning" = "Quarterly overnight bed stock by sector — mental illness rows relevant to RDY as mental health provider.",
        "Latest value / position" = if (isTRUE(kh03_trend$available)) {
          paste0(format(kh03_trend$latest_value, big.mark = ","), " beds (", kh03_trend$latest_period, " snapshot)")
        } else {
          "Mixed snapshot dates in extract — verify latest quarter on NHS England site"
        },
        "Comparator / trend" = if (isTRUE(kh03_trend$available)) {
          paste0(
            "Historical snapshots span ", kh03_trend$n_periods, " dates — latest vs previous: ",
            format(kh03_trend$latest_value, big.mark = ","), " vs ",
            format(kh03_trend$previous_value, big.mark = ","),
            ". Irregular snapshot intervals — descriptive only."
          )
        } else {
          "Insufficient comparable snapshots for trend in current slice."
        },
        "Agent flag" = if (isTRUE(kh03_trend$available)) "Watch / clarify" else "Trend not available",
        "Cautious interpretation" = kh03_stale_note,
        "Human check required" = "Bed management/ estates lead to confirm latest KH03 quarter and alignment with internal bed state."
      )
    )
  )

  kh03_trend_html <- if (isTRUE(kh03_trend$available)) {
    trend_section(
      list(kh03_trend),
      "trend_kh03_beds_rdy.csv — Mental Illness sector, recent snapshots only",
      c(
        "Snapshot dates are irregular (quarterly, not monthly) — descriptive only.",
        "Latest snapshot may lag NHS England publication — verify on source site."
      )
    )
  } else {
    trend_not_available_section(c(
      "Latest KH03 quarterly snapshot aligned to board reporting date",
      "Consistent sector filter (Mental Illness) across consecutive quarters"
    ))
  }

  ae_dm01_trends <- list()
  if (isTRUE(trend_ae_other$available)) ae_dm01_trends <- c(ae_dm01_trends, list(trend_ae_other))
  if (isTRUE(trend_dm01_activity$available)) ae_dm01_trends <- c(ae_dm01_trends, list(trend_dm01_activity))

  ae_dm01_trend <- if (length(ae_dm01_trends) > 0) {
    trend_section(
      ae_dm01_trends,
      "trend_ae_rdy.csv / trend_dm01_rdy.csv (historic stack from script 05)",
      c(
        "A&E trends are source validation only — zero Type 1/2 ED attendances expected at RDY.",
        "Descriptive period-on-period change only — not operational cause."
      )
    )
  } else {
    trend_not_available_section(c(
      "Run site/public-data/05_download_historic_public_data.R for trend_ae_rdy.csv and trend_dm01_rdy.csv",
      "Service-owner confirmation that RDY service model excludes Type 1/2 ED activity"
    ))
  }

  urgent_window_stats <- c(
    if (length(ae_window_stats) > 0) ae_window_stats else list(),
    if (length(dm01_window_stats) > 0) dm01_window_stats else list()
  )
  urgent_window_html <- if (length(urgent_window_stats) > 0 &&
      any(vapply(urgent_window_stats, function(s) isTRUE(s$available), logical(1)))) {
    window_stats_section(
      urgent_window_stats,
      paste0(
        "Six-month descriptive trends from trend_ae_rdy.csv (", ae_trend_window,
        ") and trend_dm01_rdy.csv (", dm01_trend_window, ")."
      ),
      "Six-month",
      c(
        "A&E trends are source validation only — zero Type 1/2 ED attendances expected at RDY.",
        "DM01 provisional monthly data — validate with diagnostics service owner."
      )
    )
  } else {
    ""
  }

  supporting_html <- paste0(
    collapsible_details("Source presence tables and charts", key_figures),
    collapsible_details("Additional source commentary", theme_commentary_section(commentary_cards)),
    if (nzchar(urgent_window_html)) wrap_trend_collapsible(urgent_window_html, "A&E and DM01 six-month window summary") else "",
    wrap_trend_collapsible(ae_dm01_trend, "A&E trend detail"),
    wrap_trend_collapsible(kh03_trend_html, "KH03 bed trend detail")
  )

  verify_extra <- paste0(
    '<details class="nhs-verify-details"><summary>A&amp;E RDY row — full public data columns</summary>',
    ae_detail_html,
    '</details>',
    if (!is.null(kh03_mi)) paste0(
      '<details class="nhs-verify-details"><summary>KH03 mental illness beds (snapshots in extract)</summary>',
      html_table(kh03_mi),
      '</details>'
    ) else ""
  )

  config <- list(
    question = paste(
      "Check whether RDY appears in public A&E, DM01 and KH03 files;",
      "explain what each source can safely support for a trust without an emergency department;",
      "and where trend analysis is or is not available."
    ),
    dataset_line = "A&E, DM01 and KH03 public provider files (RDY)",
    prompt_excerpt = paste(
      "Confirm RDY row presence per source. Report zero ED attendances explicitly.",
      "No ED performance claims. No causal language.",
      sep = "\n"
    ),
    scope = list(
      can = c(
        "Confirm RDY row presence in public A&E, DM01 and KH03 files.",
        "Show descriptive trends where stacked historic files support them.",
        "Explain what each source can safely support for a trust without a Type 1/2 emergency department."
      ),
      cannot = c(
        "Prove urgent care or diagnostic performance standing from source validation alone.",
        "Make ED performance claims — RDY shows zero Type 1/2 A&E attendances by service model."
      )
    ),
    headline = c(
      "These sources are grouped because they were checked together, not because they form a single performance pathway.",
      paste0(
        "RDY appears in public A&E, DM01 and KH03 files — A&E confirms zero Type 1/2 ED attendances (source validation only, not ED performance)."
      ),
      if (!is.na(dm01_aud_wl) && !is.na(dm01_wl_total)) {
        paste0(
          "DM01 (", dm01_latest_period, "): audiology accounts for most waiting-list volume (",
          format(dm01_aud_wl, big.mark = ","), " of ", format(dm01_wl_total, big.mark = ","),
          if (!is.na(dm01_wl_13)) paste0("; ", dm01_wl_13, " waits over 13 weeks in extract.") else ")."
        )
      } else if (!is.null(dm01)) {
        paste0("DM01: ", nrow(dm01), " test rows for ", dm01_latest_period, " — ", dm01_top_test, " has highest activity.")
      } else {
        "DM01 extract not summarised."
      },
      if (isTRUE(kh03_trend$available)) kh03_stale_note else "KH03: confirm latest quarter before capacity discussions.",
      paste0(
        "Trend extracts: A&E ", ae_n_periods, " periods; DM01 ", dm01_n_periods,
        " periods — headline uses latest period per source."
      )
    ),
    why_useful = c(
      "The agent checked RDY presence across heterogeneous urgent-care, diagnostics and bed-stock sources.",
      "It explained which sources are meaningful for a mental health/community trust and where local owners must confirm definitions."
    ),
    bottom_line = paste(
      "This is a source applicability check, not a unified performance report.",
      "A&E rows validate RDY's service model rather than ED performance.",
      "DM01 mainly describes audiology/community diagnostics waiting lists for RDY.",
      "KH03 provides quarterly mental health bed-stock context.",
      "Local owners should confirm all figures before operational use."
    ),
    grouped_findings = list(
      list(
        title = "A&E source validation",
        items = list(
          list(
            title = "A&E — RDY row and zero Type 1/2 attendances",
            body = paste0(
              "RDY row present in ", ae_latest_period, " A&E extract. Type 1/2 attendances = 0 — expected for RDY service model.",
              if (!is.na(ae_other_adm)) paste0(" Other emergency admissions: ", ae_other_adm, ".") else ""
            ),
            owner = "Urgent/emergency care lead to confirm service model and A&E return coding."
          )
        )
      ),
      list(
        title = "DM01 diagnostics",
        items = list(
          list(
            title = paste0("DM01 — ", dm01_top_test),
            body = paste0(
              if (!is.null(dm01)) paste0(nrow(dm01), " diagnostic test rows for ", dm01_latest_period, ". ") else "",
              if (!is.na(dm01_mar_activity)) paste0("Top test activity: ", format(dm01_mar_activity, big.mark = ","), ". ") else "",
              "Audiology/community diagnostics may dominate — local validation required."
            ),
            owner = "Diagnostics service owner to confirm DM01 mapping to local pathways."
          )
        )
      ),
      list(
        title = "KH03 bed snapshots",
        items = list(
          list(
            title = "KH03 — Mental illness overnight beds",
            body = if (isTRUE(kh03_trend$available)) {
              paste0(
                "Latest snapshot: ", format(kh03_trend$latest_value, big.mark = ","), " beds (",
                kh03_trend$latest_period, "). Quarterly stack only — verify latest quarter on NHS England site."
              )
            } else {
              "KH03 extract present — confirm latest quarter before capacity discussions."
            },
            owner = "Bed management/estates lead to confirm alignment with internal bed state."
          )
        )
      )
    ),
    data_used_html = paste0(
      '<ul class="nhs-list-compact">',
      '<li>A&amp;E monthly provider CSV (<code>', esc(ae_file_note), '</code>)</li>',
      '<li>DM01 (<code>demo_dm01_diagnostics.csv</code> + full RDY extract)</li>',
      '<li>KH03 (<code>demo_kh03_beds.csv</code> + trend file where used)</li>',
      if (!is.null(ae_trend)) '<li><code>trend_ae_rdy.csv</code></li>' else "",
      if (!is.null(dm01_trend)) '<li><code>trend_dm01_rdy.csv</code></li>' else "",
      '</ul>'
    ),
    period = paste0("A&E ", ae_trend_window, "; DM01 ", dm01_trend_window, "; KH03 quarterly snapshots (", if (isTRUE(kh03_trend$available)) paste0(kh03_trend$previous_period, " to ", kh03_trend$latest_period) else "see trend file", ")."),
    trend_available = if (urgent_has_ae_dm01_trend || isTRUE(kh03_trend$available)) {
      paste0("Yes — six-month A&E/DM01 stacks where available; KH03 quarterly snapshots (", kh03_stale_note, ")")
    } else {
      "Limited — source validation only for some sources"
    },
    agent_summary = c(
      paste0(
        "RDY appears in public A&E, DM01 and KH03 files — interpretation needs service-owner confirmation.",
        if (ae_ed_zero) " A&E shows zero Type 1/2 ED attendances (expected for RDY service model)." else ""
      ),
      if (!is.null(dm01)) paste0("DM01: ", nrow(dm01), " test rows for ", dm01_latest_period, " — ", dm01_top_test, " has highest activity; six-month window ", dm01_trend_window, ".") else "DM01 extract not summarised.",
      if (isTRUE(kh03_trend$available)) paste0("KH03: ", kh03_stale_note) else "KH03: confirm latest quarter before capacity discussions.",
      "This brief validates source presence — it does not prove urgent care or diagnostic performance standing."
    ),
    human_checks = standard_human_checks(list(
      list(
        q = "Which urgent care metrics apply locally without an ED?",
        expl = "Many national urgent care indicators assume Type 1/2 ED activity — confirm which apply to RDY."
      )
    )),
    verify_intro = verify_intro_short(
      paste0(
        '<li><a href="../public-data/processed/demo_dm01_diagnostics.csv">demo_dm01_diagnostics.csv</a></li>',
        '<li><a href="../public-data/processed/demo_kh03_beds.csv">demo_kh03_beds.csv</a></li>'
      ),
      "Figures trace to processed RDY extracts and trend_*.csv files — filter to Organisation_Code or Org Code = RDY."
    )
  )

  verify_body <- paste0(
    traceability_verify_body(
      "See linked demo CSV and filter notes.",
      c("demo_dm01_diagnostics.csv", "demo_kh03_beds.csv", ae_file_note,
        if (!is.null(ae_trend)) "trend_ae_rdy.csv" else NULL,
        if (!is.null(dm01_trend)) "trend_dm01_rdy.csv" else NULL,
        if (!is.null(kh03_trend_df)) "trend_kh03_beds_rdy.csv" else NULL),
      c("ae_monthly", "dm01_monthly", "kh03_quarterly")
    ),
    verify_extra
  )

  body <- agent_brief_sections(config, urgent_kfe_html, verify_body, supporting_html)

  write_public_report("public-urgent-diagnostics-check.html",
    "Worked example: AI-assisted urgent care, diagnostics and beds source check",
    "Urgent care, diagnostics and beds — public source applicability check (RDY)", body)
}

# --- Run all reports ---------------------------------------------------------

build_performance_overview()
build_mh_profile()
build_csds_profile()
build_talking_therapies()
build_assurance_profile()
build_urgent_diagnostics()

cat("Public reports written to:", normalizePath(reports_dir), "\n")

validation_script <- file.path(script_dir, "04_validate_public_reports.R")
if (file.exists(validation_script)) {
  source(validation_script, local = TRUE)
  if (exists("validate_public_reports", mode = "function")) {
    validate_public_reports(reports_dir = reports_dir, processed_dir = processed_dir)
  }
}
