# PTSDdiag 0.5.0

This release adds two things. The first is the **redundancy analysis** the
preprint's headline claim rests on: the best symptom combination is rarely
alone, and reporting only the winner overstates how much the data single it out.
The second makes the package self-sufficient for **multi-site validation** — a
site that cannot share its data can now receive a set of symptom combinations
derived elsewhere, evaluate them locally, and return a releasable summary,
without any analysis code living outside the package and without reaching into
unexported internals.

## New features

### Redundancy analysis

* `compute_plateau()` returns every combination whose balanced accuracy falls
  within `delta` percentage points of the best in the same search, as a count
  and as a proportion of that rule form's own candidate space. The denominators
  matter: a six-item hierarchical search evaluates 13,685 combinations against
  the non-hierarchical search's 38,760, so the same plateau count means
  different things. `delta` defaults to 1 percentage point and accepts a vector,
  so a primary and secondary plateau come from one call.
* `bootstrap_stability()` re-runs the entire exhaustive search on bootstrap
  resamples of the respondents and reports, for every combination, how often it
  stayed within `delta` of that replicate's best (`pi`) and how often it was the
  outright winner (`p_argmax`, with ties shared equally). Taking the reference
  point within each replicate rather than from the original sample keeps every
  comparison on the scale of its own resample. Note that `pi` for the
  combination that won in the original sample is optimistic, since replicates
  share most of their observations with the sample that selected it; the
  documentation says so. Per-replicate plateau sizes are returned as well, for a
  percentile interval across replicates.
* `symptom_selection()` reports how often each item appears across the plateau
  against the chance baseline for that rule form, computed by enumeration rather
  than assumed. Raw selection counts are zero-sum and cannot be read alone. The
  baseline is flat at `n_symptoms / 20` without a cluster constraint, but under
  the hierarchical rule it ranges from 25.2% for each cognition-and-mood item to
  55.8% for each avoidance item, so a flat comparison would make two-item
  clusters look preferred when they are merely unavoidable. A stability weighted
  variant is available when a `bootstrap_stability()` result is supplied.
* `plot_symptom_selection()` draws those frequencies with the chance baseline
  marked item by item, or as a ratio to chance.
* `compare_rule_forms()` runs the above across a named list of searches and
  stacks the results into long data frames with a `rule_form` column, ready for
  a manuscript table.

### Multi-site transport and release

* `evaluate_sets()` scores a supplied list of symptom combinations against the
  full DSM-5-TR PCL-5 diagnosis. Where `score_all_combinations()` enumerates and
  scores a whole candidate space, this scores exactly the combinations it is
  given, which is what a validation site needs when the search happened
  elsewhere. It uses the same collapsed-pattern matrix path as
  `bootstrap_stability()`, so cost scales with the number of distinct response
  patterns rather than with sample size, and it reproduces
  `score_all_combinations()` exactly.
* `transport_plateau()` turns those per-combination results into something a
  site may release: a five-number summary of balanced accuracy, sensitivity and
  specificity **across** each transported plateau, plus two-by-two counts for a
  short, pre-specified list of named combinations. The
  combination-to-value linkage is discarded deliberately — per-combination
  counts across a whole candidate space are enough constraints to recover the
  response-pattern distribution, and in a small sample a rare pattern is
  effectively an individual record. The function has no argument that emits
  per-combination detail for a plateau, so it cannot be released by accident.
* `write_transport()` and `read_transport()` serialise a return bundle. Every
  filename is prefixed with the sample identifier and every table carries
  `sample_id` and `payload_id` columns, so bundles from different sites stay
  distinguishable as loose files in one directory. `dir` is required and must
  already exist, matching `write_combinations()`: the package never picks a
  location for you and never creates directories, so a mistyped path fails
  instead of scattering files. `session_info` is off by default.
* `diagnostic_metrics()` computes every metric a two-by-two table supports, with
  intervals, recomputed from the counts. The interval functions behind it are
  exported too: `wilson_ci()`, `exact_ci()` (Clopper-Pearson, for small
  denominators), `ba_ci()`, `lr_ci()` (Simel) and `kappa_ci()`. `ba_ci()`
  combines the component intervals by Newcombe square-and-add (see Bug fixes),
  and every interval here is documented as **marginal**: none is a valid basis
  for comparing two combinations, which are paired on the same individuals.
* `five_number()` reduces a vector to the six numbers a site may release.
* `icd11_items()` exposes the six PCL-5 items `create_icd11_diagnosis()` scores,
  so a table describing the operationalisation is generated from the same
  definition that is applied and cannot drift from it.
* `chance_baseline()` and `n_candidates()` expose the size of a candidate space
  and the proportion of it containing each item. Under a cluster constraint the
  baseline is a step function of the cluster rather than a constant, because a
  two-item cluster contributes far more of its combinations than a seven-item
  one — which is what makes a raw selection frequency misleading on its own.
* `set_id_to_items()`, `format_set_items()`, `format_set_labels()` and
  `pcl5_item_labels()` convert between combination ids and item numbers or
  labels. `as_set_matrix()` accepts a combination specification in any of the
  three shapes the package produces.

## Bug fixes

* `ba_ci()` no longer returns a zero-width interval when sensitivity or
  specificity is at a boundary. The analytic variance
  `Var(BA) = 0.25 (Var(sens) + Var(spec))` is degenerate there, because
  `p(1-p)/n` is zero, so a table with six positives all classified correctly
  reported `1.00 (1.00, 1.00)` — no uncertainty at all from 183 observations,
  while its own component intervals were correctly wide. The default is now
  Newcombe square-and-add of the component intervals, which is not degenerate
  at a boundary: the same table gives `1.00 (0.80, 1.00)` under the default
  `method = "wilson"`, and `1.00 (0.77, 1.00)` under `method = "exact"`. The
  analytic form stays available as `method = "wald"` and warns when a component
  sits at 0 or 1.
* `diagnostic_metrics(ci = )` now reaches balanced accuracy. It previously
  applied the requested method to the proportions but always used the analytic
  form for their mean, so `ci = "exact"` returned Clopper-Pearson bounds on
  sensitivity and specificity next to a Wald interval on balanced accuracy.
  `ba_method` overrides.
* **The hierarchical rule could accept a set spanning one cluster too few.**
  When `clusters` did not cover all 20 items, `.diagnose_combination()` counted
  items belonging to no cluster as though they formed a cluster of their own:
  the lookup table was sized to the highest clustered item, so an endorsed item
  above that index read as `NA` and one inside the range but outside every
  cluster read as `0`, and `unique()` then counted either as a distinct cluster.
  With `clusters = list(B = 1:5, C = 6:7, D = 8:14)`, a respondent endorsing
  items 1, 6 and 15 was diagnosed under a 3-of-3 hierarchical rule despite
  spanning only two of the three clusters. Items outside every cluster now
  contribute nothing towards spanning. **Results are unchanged for the standard
  DSM-5-TR PCL-5 clusters**, which cover all 20 items, so no analysis using the
  defaults is affected. Affects `optimize_combinations_clusters()`,
  `score_all_combinations()` and `apply_symptom_combinations()` when custom,
  non-exhaustive clusters were supplied.

## Improvements

* `symptom_frequency()` gains `Baseline` and `Enrichment` columns and a
  `clusters` argument, for the same reason: selection frequencies over a fixed
  number of combinations are only interpretable against chance. Existing columns
  are unchanged.
* `plot_symptom_frequency()` gains `type = "enrichment"`, filling each tile with
  the ratio of observed to chance selection on a scale diverging around 1. The
  default remains `type = "relative"`.

## Notes

* The bootstrap collapses respondents to unique binarized response patterns and
  expresses each replicate as two matrix products, which is exact rather than an
  approximation because the reference diagnosis and every decision rule depend
  on the data only through the binarized responses. A full 2,000-replicate run
  over all 38,760 six-item combinations takes seconds.
* Under the hierarchical rule, items belonging to no cluster count towards the
  endorsement threshold but not towards spanning the clusters. This follows from
  the bug fix above and holds identically in the per-row rule and the matrix
  path, which are checked against each other on every run.

# PTSDdiag 0.4.1

CRAN check-time reduction; no functional changes. The 0.4.0 submission was
auto-rejected by CRAN's incoming pretests solely for exceeding the 10-minute
check-time limit on r-devel-windows.

* Search-heavy tests (exhaustive 6-symptom optimizations, holdout- and
  cross-validation runs) are now skipped on CRAN via `skip_on_cran()`; the
  complete suite continues to run on GitHub Actions for every push. CRAN
  still exercises the optimizer, the summary tables, the plot, and the whole
  0.4.0 evaluation API on compact search spaces and hand-built definitions.
* Vignettes compute on 120-row subsets (previously 250), the
  cross-validation demo uses 2 folds, and the external-validation example
  reuses the holdout-derived combinations instead of running a third full
  search. All narratives, code paths, and interpretive statements are
  unchanged and were re-verified against the re-rendered output.

# PTSDdiag 0.4.0

This release focuses on the multi-site validation workflow: everything a
collaborator site previously had to hand-code around the package is now built
in. It also corrects the ICD-11 operationalization to the field-standard
six-item mapping (see Corrections — ICD-11 results change). Apart from that
correction, no existing call changes behavior.

## Corrections

* **ICD-11 re-experiencing now uses PCL-5 items 2-3 (was 1-3).**
  `create_icd11_diagnosis()` — and everything built on it: the `"icd11"`
  fixed criterion in `compare_optimizations()`, `include_icd11 = TRUE` in
  `evaluate_definitions()`, and the ICD-11 row of the symptom-frequency
  heatmap — now operationalizes ICD-11 PTSD with the narrow six-item mapping
  (items 2, 3, 6, 7, 17, 18; at least one symptom per cluster). ICD-11
  requires re-experiencing with a here-and-now quality, which nightmares
  (item 2) and flashbacks (item 3) capture but intrusive memories (item 1)
  as worded in the PCL-5 do not; this is the mapping used across the
  published PCL-5-to-ICD-11 literature (Kuester et al. 2017; Schellong et
  al. 2019; Heeke et al. 2020; Pettrich et al. 2025). **All ICD-11 benchmark
  results change** relative to earlier versions; because the six-item rule
  is strictly more conservative, ICD-11 diagnoses can only become less
  frequent. The broad seven-item variant remains available as a custom fixed
  criterion — see the recipe in `?create_icd11_diagnosis`.

## New features

* `as_definitions()` converts combinations imported from JSON into the
  definitions list `evaluate_definitions()` expects, with optional top-`n`
  truncation and automatic rule labels such as `"4/6 Hierarchical"`.
  `read_combinations()` output is now classed `ptsdiag_spec` and can be passed
  to `evaluate_definitions()` directly (single spec or a list of specs — the
  conversion happens automatically). `write_combinations()` gains an optional
  `label` argument stored in the file, so the derivation site controls how
  each rule is labelled downstream.
* `evaluate_definitions()` gains `reference`: validate definitions against an
  external reference standard (e.g. a clinician CAPS diagnosis) supplied as a
  logical vector, a 0/1 column, or a column name. Rows with a missing
  reference are excluded with a message, and a `"Full 20-item PCL-5"` ceiling
  row is added by default (`include_full_pcl5`) so the cost of the reduced
  symptom set can be separated from the intrinsic PCL-5-vs-reference
  disagreement.
* `evaluate_definitions()` gains `tidy` (and `as_percent`): return a plain
  analysis table with `Approach` / `Rank` / `Combination`, the 2x2 counts,
  and numeric metrics — the same layout as `summarize_top_combinations()`,
  so derivation and validation results can be combined with `rbind()`. No
  more parsing rule labels out of the formatted display table.
* `score_all_combinations()` scores **every** candidate combination
  (optionally cluster-constrained) against the DSM-5-TR diagnosis and returns
  the complete ranked table — the exhaustive companion to
  `optimize_combinations()`, for interchangeability ("plateau") analyses.
  Chunked, with optional parallel scoring via `future.apply` like
  `cross_validation()`.
* `check_pcl5_data()`: exported pre-flight check that reports every data
  problem in one pass (column count, numeric type, integer 0-4 range, missing
  values) instead of one error at a time, plus an informational note on
  all-zero rows. Aimed at collaborator sites preparing data for
  `rename_ptsd_columns()`.
* New `inst/CITATION`: `citation("PTSDdiag")` now points to the paper and the
  package.

## Bug fixes

* Hierarchical definitions that carry a custom cluster structure (stored in a
  combinations JSON file) are now evaluated with exactly those clusters;
  previously `evaluate_definitions()` silently substituted the default PCL-5
  B/C/D/E structure.

# PTSDdiag 0.3.5

## New features

* New `score_by = "balanced_accuracy"` optimization criterion: maximises
  balanced accuracy, the mean of sensitivity and specificity (equivalently,
  minimises FN/P + FP/N). Available in `optimize_combinations()`,
  `optimize_combinations_clusters()`, `compare_optimizations()`,
  `holdout_validation()`, and `cross_validation()`, and accepted as metadata
  by `write_combinations()`.
* A `Balanced Accuracy` column ((sensitivity + specificity) / 2) is now
  reported by `summarize_ptsd_changes()` (as `balanced_accuracy`),
  `create_readable_summary()` (so `res$summary`, the holdout/cross-validation
  summaries, and `evaluate_definitions()` show it),
  `summarize_top_combinations()`, `compare_diagnostic_systems()`, and the
  cross-validation `combinations_summary` (as `Balanced_Accuracy`). This
  makes the reported metric match the quantity the new default optimizes.

## Behavior changes

* All optimization and validation functions now default to
  `score_by = "balanced_accuracy"` (was `"accuracy"`). In imbalanced samples
  (the bundled clinical data is about 94% PTSD-positive) plain accuracy is
  dominated by the majority class; balanced accuracy weighs performance in
  the diagnosed and non-diagnosed groups equally. Pass
  `score_by = "accuracy"` to reproduce results from earlier versions.
* `score_by = "balanced_accuracy"` requires both diagnosed and non-diagnosed
  cases under the reference criterion; data where every case falls in one
  class now stops with an informative error suggesting `"accuracy"` or
  `"sensitivity"`.
* `create_readable_summary()` now requires a `balanced_accuracy` column in
  its input. Data frames produced by `summarize_ptsd_changes()` carry it
  automatically; hand-built inputs need the additional column.

## Documentation

* All vignettes demonstrate `score_by = "balanced_accuracy"` as the standard
  outcome, and the Getting started vignette explains the choice among the
  three criteria.
* Vignettes now run on a uniform 250-row subset of the bundled data, so
  building the package (and the CI checks) is several times faster. The
  Getting started vignette previously optimized on all 5,000 rows.
* Removed leftover editorial text from the Getting started vignette, and the
  PCL-5 total score is now computed on a descriptive copy so the optimizer's
  "total column detected" warning no longer appears in the rendered
  vignette. Fixed typos across the comparison, validation, and multi-site
  vignettes.
* README article links point to the current vignettes (the previous links
  referenced articles removed in 0.3.1).
* Examples use the same uniform 250-row subset with compact search spaces so
  that CRAN's `--run-donttest` checks stay fast.

## Internal

* Structural tests now run on compact search spaces and reuse expensive
  comparison objects, cutting the test-suite runtime from about five minutes
  to under a minute without losing coverage; the canonical 6-of-4 searches
  are still exercised once per optimizer.

# PTSDdiag 0.3.3

## Bug fixes

* `create_icd11_diagnosis()` now operationalises the ICD-11 "sense of current
  threat" cluster with PCL-5 items 17 (hypervigilance) and 18 (exaggerated
  startle), the symptoms the documentation always intended. Earlier versions
  used items 16 (risk-taking behaviour) and 17, so the ICD-11 benchmark
  diagnosis, the ICD-11 symptom set reported by `compare_optimizations()` /
  `symptom_frequency()`, and any comparison against ICD-11 are affected.
  Re-run analyses that benchmarked against ICD-11.

# PTSDdiag 0.3.2

## New features

* `extract_definitions()` and `evaluate_definitions()` are now exported.
  `extract_definitions()` pulls the top-n combinations of each optimized
  scenario out of a `compare_optimizations()` result (reading each rule from
  the object) as a portable, shareable object; `evaluate_definitions()`
  applies such a set of mixed-rule definitions (plus ICD-11) to any sample
  and returns a performance table. Together they support multi-site
  workflows where only symptom-index definitions, not patient data, are
  shared.
* An `Accuracy` column ((TP + TN) / N) is now reported by
  `create_readable_summary()` (so `res$summary`, the holdout/cross-validation
  summaries, and the multi-site tables show it), `summarize_top_combinations()`,
  and `compare_diagnostic_systems()`. This makes the reported metric match the
  quantity `score_by = "accuracy"` optimizes.

## Behavior changes

* `holdout_validation()` and `cross_validation()` now default to
  `score_by = "accuracy"` (was `"sensitivity"`), matching
  `optimize_combinations()` and `compare_optimizations()`.

## Data

* `simulated_ptsd_genpop` now also ships paired clinician-administered CAPS-5
  ratings (`C1`–`C20`) for the same participants, simulated to correlate with
  the PCL-5 items at a total-score r of about 0.8. The dataset now has 43
  columns (demographics + 20 PCL-5 + 20 CAPS-5); the PCL-5 items and
  demographics are unchanged.

## Documentation

* The CAPS-5 workflow vignette now uses the bundled paired data instead of
  random CAPS-5 ratings, so the instrument agreement it shows is realistic.

# PTSDdiag 0.3.1

## Breaking changes

* The `score_by` argument values have been renamed for clarity. Pre-0.3.1
  values are no longer accepted; passing them errors with a migration hint.

  | Old value (≤ 0.3.0) | New value (0.3.1) | Meaning |
  | --- | --- | --- |
  | `"false_cases"` | `"accuracy"` | minimise FP + FN (= maximise accuracy) |
  | `"newly_nondiagnosed"` | `"sensitivity"` | minimise FN (= maximise sensitivity) |

  Defaults updated accordingly: `optimize_combinations()`,
  `optimize_combinations_clusters()`, and `compare_optimizations()` default
  to `"accuracy"`; `holdout_validation()` and `cross_validation()` default
  to `"sensitivity"`.

## Bundled data

* `simulated_ptsd` and `simulated_ptsd_genpop` now ship with three
  demographic columns (`patient_id`, `age`, `sex`) so that the
  demographic carry-through workflow (`id_col`) can be demonstrated
  end-to-end on the bundled data. The numerical PCL-5 profile of both
  datasets is unchanged.

## Documentation

* The error message from `rename_ptsd_columns()` /
  `rename_caps5_columns()` when the number of non-ID columns is not
  exactly 20 now spells out the strict positional behaviour and points
  users at `id_col` for unrelated covariates.
* Vignette restructure: the previous six vignettes have been
  consolidated into four with a shared template (clinical purpose +
  explicit input assumptions + epidemiological vocabulary +
  interpretation of every results table):
  * **Getting started** — single-cohort workflow + demographic
    join-back (replaces `quickstart` and folds in `id_column_workflow`).
  * **Comparing diagnostic criteria** — multi-scenario optimisation +
    ICD-11 + symptom-frequency heatmap (replaces `internal_analysis`
    and `multi_scenario_analysis`).
  * **Validating abbreviated symptom definitions** — internal and
    external validation (replaces validation sections of
    `internal_analysis` and `external_validation_pcl5`).
  * **Validating a shared definition across sites** — a multi-site
    workflow that derives a definition at one site and validates it at
    another by exchanging only a JSON symptom-index file, with no
    patient-level data shared.
  * **CAPS-5 workflow** — using CAPS-5 as the reference instrument
    (replaces `external_validation_caps5`).

# PTSDdiag 0.3.0

## New features

* `compare_optimizations()` runs multiple symptom-optimization scenarios on
  the same dataset in a single call and returns a `ptsdiag_comparison` S3
  object. Default scenarios reproduce the three approaches compared in the
  PTSDdiag preprint (4/6 hierarchical, 4/6 non-hierarchical, 3/6
  non-hierarchical). Users can supply their own named list of scenarios with
  arbitrary `n_symptoms` / `n_required` / `hierarchical` combinations.
* Fixed (non-optimised) criteria such as ICD-11 can be added to the same
  comparison either via the `include_icd11 = TRUE` convenience flag or via
  `scenarios` entries of `type = "fixed"`, including user-supplied logical
  diagnosis vectors. They appear as additional rows in the comparison table
  and as rows of 0/1 cells in the heatmap.
* `summarize_top_combinations()` produces a tidy manuscript-ready table
  (Approach / Rank / Combination / TP / FN / FP / TN / Sensitivity /
  Specificity / PPV / NPV) from a `ptsdiag_comparison`. Optional
  `as_percent = TRUE` returns percentages for direct manuscript use.
* `symptom_frequency()` returns the long-format Symptom × Approach × Count /
  RelFreq dataframe (source of the preprint's Supplementary Table S4),
  optionally appending an OVERALL pooled row.
* `plot_symptom_frequency()` draws the symptom-selection heatmap (Figure 1
  of the preprint) as a `ggplot` object, with the OVERALL row in its own
  facet for visual separation. `ggplot2` is now in Suggests.
* `id_col` carry-through (introduced in 0.2.7) propagates automatically
  through every scenario in `compare_optimizations()`.

## Documentation

* New vignette `multi-scenario-analysis` replicates the preprint's
  end-to-end derivation workflow in roughly twenty lines of code.

# PTSDdiag 0.2.7

## New features

* `rename_ptsd_columns()` and `rename_caps5_columns()` gain an `id_col`
  argument: a character vector of column name(s) to preserve through the
  workflow as participant identifiers. When supplied, the named columns are
  prepended to the renamed dataframe and propagate automatically through
  `optimize_combinations()`, `optimize_combinations_clusters()`,
  `apply_symptom_combinations()`, `holdout_validation()`, `cross_validation()`,
  `create_icd11_diagnosis()`, and `create_caps5_diagnosis()`. The result of
  each per-row function (`diagnosis_comparison`, `test_results`,
  `fold_results`, etc.) prepends these ID columns so diagnoses can be joined
  back to the original dataframe — for example, to compare demographics
  between participants who do and do not meet an optimized criterion.
* `compare_diagnostic_systems()` automatically skips carry-through ID
  columns in its `...` inputs so they are not mistaken for diagnostic
  systems.

## Behavior changes

* `binarize_data()` now mutates only the `symptom_1`..`symptom_20` columns
  rather than the whole dataframe. Any additional columns (e.g. an ID
  column) are preserved unchanged. This fixes a latent bug where a
  non-numeric carry-through column would have been coerced or errored.
* `create_ptsd_diagnosis_binarized()` now operates on the symptom subset
  only, accepting input dataframes with extra columns.
* `summarize_ptsd_changes()` silently drops non-logical columns before
  computing metrics (previously errored). This lets ID columns ride along
  in comparison dataframes without breaking downstream summarization.

## Documentation

* New vignette `id-column-workflow` demonstrating how to use `id_col` to
  merge per-row diagnoses back to a dataframe with demographics.

# PTSDdiag 0.2.6

## Improvements

* All user-facing error messages now use the `cli` package for consistent,
  rich formatting: argument names are highlighted, actual values are shown,
  and hint bullets guide users toward fixes.
* Centralized input validation via `.validate_pcl5_data()` now accepts
  `strict_cols`, `warn_total`, and `instrument` parameters, reducing code
  duplication across exported functions.
* `holdout_validation()` and `cross_validation()` now correctly restore
  the global RNG state on exit (replaced buggy `withr::local_seed()` with
  manual `on.exit` save/restore).
* Removed `withr` from Imports (no longer used).

## Documentation

* Added `@note` to `simulated_ptsd` and `simulated_ptsd_genpop` dataset
  documentation clarifying that symptoms were simulated independently
  (no within-cluster correlations).

## Tests

* New tests for RNG state restoration, edge cases (all-positive/all-negative
  baselines, binarize boundary values), non-integer `k` rejection, empty
  split guard, and `n_tied` output from optimization functions.

# PTSDdiag 0.2.5.1

## Bug fixes

* Fixed stale `README.md` that still linked to the removed `introduction.html`
  vignette instead of the new `internal_analysis.html`.
* Restored package website link in README.
* Added pkgdown site URL to DESCRIPTION `URL` field.
* Updated `JamesIves/github-pages-deploy-action` to v4.7.3 for Node.js 24
  compatibility.

# PTSDdiag 0.2.5

## Documentation

* Restructured vignettes into four focused articles: Quick Start, Full Internal
  Analysis, External Validation (PCL-5), and External Validation (CAPS-5).
* Updated pkgdown website configuration: fixed duplicate logo, added
  call-to-action buttons on the home page, added OpenGraph metadata for social
  sharing.
* Added changelog entries for versions 0.2.2--0.2.4.

# PTSDdiag 0.2.4

## New functions

* `create_caps5_diagnosis()` applies the DSM-5-TR diagnostic algorithm to
  CAPS-5 (Clinician-Administered PTSD Scale for DSM-5) item scores and returns
  the clinician-rated diagnostic status.
* `rename_caps5_columns()` standardises CAPS-5 column names to `symptom_1`
  through `symptom_20`, enabling all downstream functions to work transparently
  on CAPS-5 data.

## Improvements

* `compare_diagnostic_systems()` gains a `caps5_data` argument for including
  CAPS-5 diagnoses in the unified comparison table, and a `reference` argument
  (`"pcl5"` or `"caps5"`) for selecting which instrument defines diagnostic
  "truth". Labels are disambiguated automatically when both instruments are
  present (e.g. `"DSM-5-TR (PCL-5)"`, `"DSM-5-TR (CAPS-5)"`).

## Infrastructure

* Updated GitHub Actions workflows to Node.js 24 compatible versions.

# PTSDdiag 0.2.3

## New functions

* `create_icd11_diagnosis()` applies ICD-11 PTSD diagnostic criteria to PCL-5
  data and returns a comparison dataframe against DSM-5-TR. Output is directly
  compatible with `summarize_ptsd_changes()`.
* `compare_diagnostic_systems()` produces a single unified summary table
  comparing the diagnostic performance of DSM-5-TR, ICD-11, and any number of
  optimised symptom combinations. Output is `knitr::kable()`-ready for
  manuscript tables.

# PTSDdiag 0.2.2

## Improvements

* `create_readable_summary()` gains a `DT` parameter for optional interactive
  `DT::datatable()` widget output, consistent with the optimisation functions.
* `optimize_combinations()` and `optimize_combinations_clusters()` gain a
  `show_progress` parameter (default `TRUE`). Set to `FALSE` for batch or
  non-interactive use.

# PTSDdiag 0.2.1

## Improvements

* `optimize_combinations()`, `optimize_combinations_clusters()`,
  `holdout_validation()`, and `cross_validation()` now return plain data frames
  by default. Set `DT = TRUE` for interactive DT widgets (previous default
  behaviour).
* Combination identity (`combination_id` and `rank` columns) is now tracked
  through the full pipeline: optimization summaries, `write_combinations()` /
  `read_combinations()` JSON files, and cross-validation combination summaries.
* Progress reporting via the `cli` package: fold-level progress bars in
  `cross_validation()` and informational messages in `holdout_validation()` and
  the optimization functions.
* `cross_validation()` supports parallel fold processing via `future.apply` when
  available. Set up with `future::plan(future::multisession)` before calling.

## Deprecations

* `analyze_best_six_symptoms_four_required()` and
  `analyze_best_six_symptoms_four_required_clusters()` are soft-deprecated in

  favour of `optimize_combinations()` and `optimize_combinations_clusters()`.

## Documentation

* Updated vignettes to use the generalized API (`optimize_combinations()` and
  `optimize_combinations_clusters()`) instead of legacy wrappers.

# PTSDdiag 0.2.0

* New generalized optimization functions `optimize_combinations()` and
  `optimize_combinations_clusters()` that allow configurable number of symptoms
  per combination (`n_symptoms`), required threshold (`n_required`), number of
  top results (`n_top`), and custom cluster definitions (`clusters`).
* New `apply_symptom_combinations()` function for external validation: apply
  pre-derived combinations to a new dataset and compare against DSM-5 baseline.
* New `write_combinations()` and `read_combinations()` functions for exporting
  and importing optimized symptom combinations as human-readable JSON files,
  enabling collaboration across research groups without sharing raw data.
* Original functions `analyze_best_six_symptoms_four_required()` and
  `analyze_best_six_symptoms_four_required_clusters()` are preserved as
  backward-compatible wrappers with PCL-5 defaults.
* `holdout_validation()` and `cross_validation()` now accept `n_symptoms`,
  `n_required`, and `n_top` parameters.
* Internal refactoring: shared validation and diagnosis logic consolidated into
  reusable internal helpers.

# PTSDdiag 0.1.0

* Initial CRAN submission.
* Core analysis functions for identifying optimal 6-symptom PTSD diagnostic
  combinations using PCL-5 data.
* Hierarchical (cluster-based) and non-hierarchical analysis approaches.
* DSM-5 diagnostic criteria implementation (binarized and non-binarized).
* Diagnostic accuracy metrics: sensitivity, specificity, PPV, NPV.
* Holdout and k-fold cross-validation methods for model evaluation.
* Simulated PCL-5 dataset with 5,000 observations for demonstration.
