--- title: "Clinical outcome analysis: an end-to-end pipeline" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Clinical outcome analysis: an end-to-end pipeline} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(PhysioClinical) has_hub <- requireNamespace("PhysioAnnotationHub", quietly = TRUE) ``` `PhysioClinical` turns raw clinical instrument responses into scored, interpreted, standards-anchored outcomes. This vignette walks a single patient through the full pipeline: **score → benchmark → classify response → normative z-score → goal attainment → ICF tagging → FHIR export.** ## 1. Score an instrument A `ClinicalScore` is produced from item responses by the data-driven scoring engine. Here is a Berg Balance Scale (BBS) assessment at baseline and follow-up. ```{r score} baseline_items <- setNames(c(4, 3, 4, 3, 2, 3, 2, 2, 3, 2, 1, 2, 1, 1), sprintf("item%02d", 1:14)) followup_items <- setNames(c(4, 4, 4, 4, 3, 4, 3, 3, 4, 3, 3, 3, 2, 3), sprintf("item%02d", 1:14)) bl <- scoreInstrument("berg", baseline_items, subject_id = "P001", timestamp = "2026-01-10") fu <- scoreInstrument("berg", followup_items, subject_id = "P001", timestamp = "2026-04-10") c(baseline = bl@total, followup = fu@total) ``` If you instead start from a table of already-scored assessments, `PhysioIO::readClinicalMetadataCSV()` reads the long-format (`subject_id, visit_id, scale_name, scale_score`) layout into the same workflow. ## 2. Benchmark against published clinimetrics The bundled clinimetric store returns published MDC/MCID values with their literature provenance (never fabricated). ```{r clinimetrics} getClinimetric("BBS", "MDC", population = "elderly_baseline_45_56") ``` ## 3. Classify the response (dual MDC-vs-MCID) `classifyResponder()` applies the Beaton dual rule: a change is a *true responder* only when it exceeds both the MDC (real change beyond measurement error) and the MCID (clinically important change). We use the Fugl-Meyer upper extremity (FMA-UE) score, whose store entry carries both a MDC (6.65 points, derived from the Wagner 2008 SEM) and a MCID (4.25 points, Page 2012). Because here the MDC exceeds the MCID, a change can be clinically important yet still inside measurement error: ```{r responder} cols <- c("change", "mdc", "mcid", "classification") # a 6-point gain: past the MCID, but not past the MDC -> measurement error classifyResponder(baseline = 30, followup = 36, instrument = "FMA-UE", population = "chronic_stroke_minimal", direction = "increase")[, cols] # a 9-point gain: past both thresholds -> a true responder classifyResponder(baseline = 30, followup = 39, instrument = "FMA-UE", population = "chronic_stroke_minimal", direction = "increase")[, cols] ``` ## 4. Normative z-score `normativeZScore()` positions an observation against a governed normative reference. The reference below uses **illustrative** age/sex gait-speed strata for demonstration. ```{r normative} ref <- GovernedNormativeReference( "demo_gs", "gait", "gait_speed", provenance = list(source = "illustrative vignette data"), consent = list(status = "public"), license = list(spdx = "CC0-1.0"), governance = list(custodian = "demo", access_level = "open"), strata_vars = c("age", "sex"), model = list(type = "strata", table = data.frame( age = c(60, 60, 70, 70), sex = c("M", "F", "M", "F"), mean = c(1.30, 1.24, 1.15, 1.10), sd = c(0.20, 0.18, 0.18, 0.17)))) z <- normativeZScore(0.83, ref, covariates = list(age = 70, sex = "M")) z[c("z", "percentile", "deviation_flag")] ``` ## 5. Goal Attainment Scaling ```{r gas} goals <- list( defineGoal("Independent standing balance", importance = 3, difficulty = 2), defineGoal("Walk 10 m unaided", importance = 3, difficulty = 3)) scoreGAS(goals, attained_levels = c(1, 0)) ``` ## 6. Tag outcomes to the ICF (cross-package) With `PhysioAnnotationHub`, every instrument links to WHO ICF categories, and a condition's published Core Set is available. ```{r icf, eval = has_hub} PhysioAnnotationHub::tagICF("berg") head(PhysioAnnotationHub::getCoreSet("Stroke")[, c("icf_code", "category_title")]) ``` ## 7. Export to FHIR Finally, a `ClinicalScore` serialises to an HL7 FHIR R4 `Observation`. ```{r fhir} obs <- toFHIRObservation(fu) obs$resourceType obs$valueQuantity ``` This is the whole arc: from item responses to a standards-compliant, ICF-anchored, interoperable clinical record.