--- title: "Technical reference: testing" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Technical reference: testing} %\VignetteEngine{knitr::rmarkdown} %\usepackage[utf8]{inputenc} --- This vignette inventories the growthcleanr test suite: what each test file covers, how the tests are run, and the standing coverage gaps. The summary table is the quick index; the per-file sections below give a per-test breakdown for the files that warrant one. ## Summary Counts below are from a fresh full run on the installed package: all files **green, 0 failures**. "Tests" = `test_that()` blocks; "Assertions" = passing `expect_*` checks. | File | Tests | Assertions | Scope | |------|-------|------------|-------| | `test-cleangrowth.R` | 15 | 117 | `cleangrowth()` API: adult integration (incl. `adult_scale_max_lbs` and `adult_overrides` pass-through + `adult_overrides` container/name/value validation), child/adult spanning subjects, requested optional columns on adult-only input | | `test-cran-smoke.R` | 2 | 23 | CRAN smoke test (the one file that intentionally does **not** `skip_on_cran()`): a fast, deterministic end-to-end `cleangrowth()` run for both the child and adult algorithms on tiny inline fixtures, asserting only definitional/unambiguous cases (missing/not-a-param preprocessing codes, exact same-day duplicates, a clearly-normal Include, an absurd population-limit value). Because almost every other algorithm file is file-level `skip_on_cran()`'d for runtime, this is the only end-to-end exercise of `cleangrowth()` on CRAN's platforms — it guards build/load failure, output-assembly regressions, and gross numeric divergence | | `test-data-frame-api.R` | 13 | 41 | Data-frame entry point: canonical/remapped columns, vector↔data-frame equivalence, id auto-gen, input errors, `full_detail`/`qm_detail` decoupling | | `test-visit-date-agedays.R` | 41 | 114 | Fractional `agedays` cleaned as whole days (rounded down, so -0.5 is day -1) but returned as submitted (`gc_internal_agedays` under `internal_detail`); age from `dob` + `visit_date`: route resolution and precedence; a lone date column parsed alongside an `agedays` column (parse-only, ages from `agedays`) surfacing `gc_internal_visit_date`/`gc_internal_dob` under `internal_detail`; expanded text-date parsing (year-first numeric, English spelled-month, numeric day/month via `date_order`) with one-format-per-run, four-digit-year (1900–2099), and English-locale guards; malformed/ambiguous/numeric rejection; date values with no digits treated as blank and a few impossible dates set aside (many stop); `gc_internal_dob`/`gc_internal_visit_date`; missing dates; negative ages | | `test-carry-through.R` | 17 | 76 | `carry_through`: non-role columns parked and returned appended (long input), original order/values/types, a column named like a returned growthcleanr column returned as `userinput_` with a warning (names of columns not returned in the run kept), a user `line` column carried unchanged, no-`id` path, input not mutated | | `test-semiwide-input.R` | 19 | 61 | `input_format = "semi_wide"`: keystone long/semi-wide equivalence, column remapping, empty cells vs zeros, same-day extraneous values, missing-weight/height warnings (HC-absent warning removed), date route, determinism, error catalog, the long-only length rule (length.adjust rejected; a `LENGTHCM`/`LENGTHIN` value at any age gets default handling, matching a column mapped to `HEIGHTCM`, including at 19 years and at days 729 and 730); long output carries the user's other columns (renamed `userinput_` when named like a returned growthcleanr column, as is an id column) | | `test-wide-input.R` | 24 | 76 | `input_format = "wide"`: keystone long/wide equivalence, wide-vs-semi-wide agreement, block discovery + ragged subjects, `_0` baseline block, prefix/structural remapping, imperial/length prefixes, same-day blocks as extraneous values, date route, value-with-no-age, strictness warnings (block gap, `max_blocks`, non-monotonic ages, repeated subject), missing-weight/height warnings (HC-absent warning removed), determinism, error catalog, round-trip spec, the long-only length rule (length.adjust rejected; a `LENGTHCM`/`LENGTHIN` value at >= 2 years gets default handling, matching a prefix mapped to `HEIGHTCM`); long output carries the user's other columns (renamed `userinput_` when named like a returned growthcleanr column, as is an id column) and, for wide, only subject-level columns such as `dob` | | `test-semiwide-output.R` | 11 | 53 | `output_format = "semi_wide"`: the equivalence chain (long -> long == semi -> long == melt-back of semi -> semi), echo fidelity, `widened_emit` control, imperial unit conversion reporting, the two-directional length adjustment on long-format fixtures (length.adjust in semi-wide is no longer supported -- see the long-only length rule under the input files), `carry_through` in mirror mode, scope errors, determinism | | `test-wide-output.R` | 14 | 61 | `output_format = "wide"`: the equivalence chain (long -> long == wide -> long == melt-back of wide -> wide, incl. a syngrowth slice), SDEs at scale (weight-only same-day duplicate blocks), echo fidelity + ragged NA verdicts, `widened_emit` control with per-block `gc_internal_agedays_N`, imperial reporting, `carry_through` in mirror mode, same-day blocks as extraneous values, scope/collision errors, determinism | | `test-hostile-input.R` | 46 | 128 | Adversarial input: long-format guards (zero-row / all-NA-required-column / duplicate-column / clean errors, factor `measurement` converted by its labels with a warning, `difftime` `agedays` converted to days in its own units and factor `agedays` by its labels, NA structural key → `Exclude-Missing-Info`, empty-core no-crash, near-match column hint, character coercion, hand-built `ref_tables` with a missing/misnamed/non-function element or non-list, non-scalar/non-numeric `adult_scale_max_lbs`) plus the semi-wide/wide reshape sweep (duplicate value/structural/block-column guards, and the intentional reshape-vs-long differences: unmapped column ignored, NaN cell dropped, all-empty parameter column warns); adult missing-measurement handling (same-day HT+WT pair with one side missing across all permissiveness levels, longitudinal embedded NA, and a fuzz pass over the adult regression fixture blanking one value per subject — all must not crash and report `Exclude-Missing-Info`) | | `test-hardlimit-single-obs.R` | 7 | 32 | Single-observation hard-limit exclusion per parameter (child WT/HT/HC birth + non-birth, adult WT/HT) end-to-end via `cleangrowth()`; lone in-range value survives to output exactly once | | `test-flexible-input-coding.R` | 25 | 69 | Flexible sex/param coding: sex-code recognition + verbatim echo, 1=male/2=female hard stop + override, all-1 sex warning + override, unrecognized-sex warning → `Exclude-Missing-Info`, all-adult 1/2 cleans, custom `sex_coding`, canonical param case-insensitivity (no built-in abbreviations — bare `WT`/`HT`/`HC` → Not-GC-Param unless mapped), `param_coding` override-to-imperial + validation errors, `HEADIN` + `LENGTHIN` conversion, unit-error >20% warning (incl. HC>5y excluded from the denominator) | | `test-child-regression.R` | 7 | 47 | Frozen counts, spot checks, cross-sample stability, Missing, HC, preload_refs | | `test-partial-run.R` | 9 | 21 | `gc_partial_run()`: full-run equivalence (exclude + `tbc.sd`), equivalence under checkpointing, no-change/all-changed/added/removed-subject handling, output-column-mismatch guard, `id` + `changed_subjids` validation | | `test-gc-runtrial.R` | 12 | 31 | `gc_runtrial()`: previews input setup on a data sample — format/coding detection, warning capture ("FLAGGED WHILE READING"), cleaning-result report, message muffling | | `test-child-algorithms.R` | 28 | 68 | CF rescue (deltaz/nextz rule: decline→rescue, jump→exclude, whole-string, end-of-series age rule; cf_detail, CF-E), unit-error smoke tests (Section 2 — "excluded somehow", not Evil-Twins-specific), Error Load, HC boundary, parallel (incl. an out-of-range `cores` capped to `detectCores()`), GA correction, Birth EWMA2, LENGTHCM | | `test-child-support-fns.R` | 6 | 24 | Support-fn units (T20/T21/T22/T13/T14): `get_dop`, `.child_valid` include-flag matrix, `.child_z3` boundary/order, `ewma()` cache_env, `ewma_cache_update()` neighbor rebuild | | `test-child-piv.R` | 13 | 21 | Child Step 7 PIV (T18/T19): standardized-PIV low cutoffs + 1-year split on HT (`piv.z.ht.low.young`/`.old`; WT has no low cutoffs), absolute birth backstops (WT>10.5, HT>65, HC>50 at age 0; WT>40 <2y, strict boundary) with same-magnitude non-birth controls; BMI limits on same-day WT x HT pairs (BMI<3 any age, >50 <2y, >150 >=2y; more-extreme-z value excluded; multi-pairing rule; in-range control) | | `test-child-sde-tiebreak.R` | 1 | 6 | Same-day-identical tiebreak (T2): birth keeps lowest id, non-birth keeps highest id; loser is `Exclude-Identical` | | `test-child-sde-charid.R` | 2 | 3 | Non-numeric character `id` (site-prefixed) does not break Step 13 SDE resolution: run completes with no same-day Include duplicate, and character-id vs numeric-id runs give identical exclusions (regression for the `keep_id_*` integer-coercion bug that crashed Step 19) | | `test-child-parameters.R` | 6 | 23 | Parameter acceptance + behavior (cf_rescue, ewma_window incl. hostile-value rejection, error load, imperial, LENGTHCM) | | `test-child-edge-cases.R` | 14 | 29 | Sparse/degenerate data, all-NA, same-day, negative agedays, HC>5y, single-value PIV (WT/HT/HC), determinism | | `test-child-et.R` | 3 | 12 | Child Evil Twins fixture: membership, per-pattern invariants, HC interior-step cleaned (not out-of-range) | | `test-child-birth-ewma2.R` | 3 | 11 | Birth HT/HC EWMA2 (Steps 15/16): 3-value SP with birth, isolated low birth-WT keep/exclude band | | `test-child-corroboration.R` | 3 | 16 | GA-correction corroboration gate (incl. HT/HC confirm-correction + 3.5 cap); UER3 unit-error 2-group drop | | `test-child-unit-error-range.R` | 7 | 22 | `unit_error_range` output column + `.unit_error_range_flag`/`.compute_unit_error_range` band math | | `test-exclusion-code-crosswalk.R` | 5 | 11 | Detailed→Summary crosswalk: no-NA, every Detailed mapped, Summary = canonical fold, valid factor levels, shared/child/adult folds exercised | | `test-adult-clean.R` | 64 | 224 | All 14 adult steps, 4 permissiveness levels, edge cases (via `cleanadult()` directly; incl. non-scalar `adult_permissiveness` and hostile setting overrides → clean errors); Step 13 keeps a single-value height when any same-day BMI is in range; `mean_ht` computed from included heights only and populated on excluded height rows (NA when no height is included) | | `test-adult-et.R` | 7 | 20 | Adult Evil Twins anchor rule (4-eligible-weight requirement, end/interior cede-to-EWMA), sex=NA CSD fallback | | `test-adult-cutpoint.R` | 10 | 25 | Adult HEADCM -> `Exclude-HC-Out-of-Range` and adult LENGTHCM cleaned as height; missing-column error names the missing role (sex-column note); sex-less pediatric rows suggest `adult_cutpoint = 18` only when all pediatric rows are >= 18 y (set aside with a warning when adult rows are present, a stop otherwise); `adult_cutpoint` advisory: no auto-route (all-adult sex-less data stops on sex at the default cutpoint, advisory fires first), `adult_cutpoint = 18` routes to the adult path, the advisory warning fires for all-adult data with 18-20y rows, an explicit cutpoint (18 or 20) silences it, a sub-18y row suppresses it, NA-age rows ignored, and a subject's routing is independent of the rest of the dataset | | `test-checkpoint.R` | 24 | 86 | `cleangrowth(checkpoint_dir=)` fold: single-pass-equivalence, fresh-run reset, `checkpoint_resume` (including resume with changed data: user-facing warning, input values kept on unmatched rows, summary "No result" line), checkpoint-cap error, `checkpoint_size`/`batch_size` independence, re-widen under checkpoint, carry-through of extra columns equals non-checkpoint, all-adult (explicit cutpoint 18) equals non-checkpoint, consolidated input warning byte-identical checkpoint vs non-checkpoint, deprecated-shim forwarding; standalone `cleangrowth_checkpoint_combine()` (re-combine in id order, NULL-order, missing-id warning, missing-dir / empty-dir errors, quietly/verbose/log_file); `mean_ht = TRUE` returned under checkpointing; guard that every `cleangrowth()` argument is forwarded to the checkpoints or on an explicit not-forwarded list | | `test-sd-recenter.R` | 19 | 47 | `sd.recenter` wiring for `cleangrowth()`: `"none"` disables recentering (case- and space-insensitive), `recenter_source` is not an argument, including checkpointed runs (`cleangrowth(checkpoint_dir=)`); `recenter_detail` output contract (columns, `debug`/`full_detail` parity, dedup, checkpoint forwarding); syngrowth-scale guard (the built-in table supplied explicitly is byte-identical to the default); `sd.recenter` validation (removed 2.x options and other values stop, data.frame accepted, caller's table not modified, missing column, duplicate rows, coverage of every evaluable child row with a dataset-wide count under checkpointing) | | `test-utils.R` | 16 | 524 | `recode_sex`, `longwide`, `simple_bmi` (utilities, not the cleaning algorithm); `ext_bmiz()` numeric-sex stop; `longwide()` parses sex (M/F, words, `sex_coding`, remapped column; outputs M/F; 1/2 stop and all-1 warning; warns on missing/unrecognized) and param (`param_coding`, imperial -> metric, LENGTHCM as height, HC dropped, unrecognized warned) like `cleangrowth()` | | `test-consolidated-input-warning.R` | 7 | 23 | Consolidated input warning (Option B): the three Class-2 defect warnings (zero / negative measurement, negative age) with per-parameter-group and all-rows N/%, one-warning union count, `Generated ids`-only case, unrecognized-sex leading with `sex_coding`, clean data silent | | `test-growthcleanr-app.R` | 21 | 90 | Bundled Shiny app: `growthcleanr_app()` exported with a stable signature and the app directory ships + `app.R` parses (CRAN-safe; guards the historical package/path mismatch + syntax breakage), plus the app's mapping-time guards — wide wrong-age-prefix rejected at mapping (not deferred to cleaning), sex-coding auto-detect (recognizes default pairs, skips 1/2), `value_freq`/mapped-column reactives tracking the sex/param selection, semi-wide custom-column mapping (measurement columns keep their names), an all-miss measurement mapping failing at Step 2 (not at cleaning), the hostile-input guard helpers, and long-format non-numeric-measurement + duplicate-field mappings rejected at mapping while a valid numeric mapping still succeeds. | | `test-cli.R` | 12 | 54 | Command-line driver: `.gc_cli_opts()` coercion + typing (numeric/integer/csv, dotted arg names), boolean + inverted flags, Tier-2 `param_coding`/`sex_coding`/`semiwide_cols`/`wide_cols` assembly, base-param name flags + separate `--*-units` routing to the metric vs imperial canonical key (orphan units flag unused; bad units value errors), up-front enum/number/one-sided-sex validation errors; `run_gc_cli()` round-trip equals an in-R `cleangrowth()` call, missing/empty-input errors, output-I/O guards (same-path errors and leaves the input intact; missing output directory errors), checkpointed run equals single-pass (writes `checkpoint_NNNN.rds`); `skip_on_cran()` shell-out through the shipped `inst/scripts/gcdriver.R` via `gc_cli_path()` (exit codes + result equivalence, incl. `--semiwide-col-weight`/`--weight-units`). | | `test-v2-migration-messages.R` | 17 | 73 | Messages for users coming from growthcleanr 2.x: positional 2.x call stop; `adjustcarryforward()` stop; `cf_rescue = TRUE` stop (abbreviations still match; case- and space-insensitive); `include.carryforward` message gives both 2.x mappings; `log.path` deprecation warning; percentages below 0.05% shown as `<0.1%`; unit-error-range warning wording; removed 2.x arguments stop with the replacement (and the list covers every removed 2.2.3 argument), with a 2.x `sd.recenter` string reported in the same stop (a missing value or `"none"` is not); other unknown arguments still stop, unevaluated, also via `cleangrowth_checkpoint()`; one-time vector-style note (not gated by `quietly`); "no id supplied" notice once per session (not used up by `gc_runtrial()`) while NA/duplicated-id notices repeat; `adult_cutpoint` clamp warning (days hint; once under checkpointing); case-insensitive `adult_permissiveness` and a call-free error; summary wording (included / Set aside); `longwide()` unknown `inclusion_types` warning and missing-`exclude` hint; command-line short-flag rejection, unknown-flag hint, and `--widened-emit` with long output (warns; an invalid value stops); command-line choice values ignore case and surrounding spaces | | **Total** | **550** | **2362 run** | Full testthat suite green (37 files); 0 skipped. | All 37 testthat files run standalone under `Rscript` (each carries `library(growthcleanr)` at the top). Most also carry `skip_on_cran()`. The exceptions are `test-cran-smoke.R`, which runs on CRAN by design, and `test-adult-clean.R`, `test-child-sde-charid.R`, `test-consolidated-input-warning.R`, and `test-gc-runtrial.R`. --- ## Running Tests ```bash # Single file NOT_CRAN=true Rscript -e 'testthat::test_file("tests/testthat/test-child-algorithms.R")' # All files NOT_CRAN=true Rscript -e 'testthat::test_dir("tests/testthat/")' ``` **IMPORTANT:** Tests use `library(growthcleanr)`, which loads the *installed* package. After any code changes, reinstall before testing — otherwise tests run against stale code: ```r devtools::install_local(".", force = TRUE, upgrade = "never") ``` **Development testing not distributed.** Some testing used in developing the algorithms is not part of the distributed test suite, because it depends on large or non-public fixtures and is being rebuilt. It includes a stress test on a 400-subject dataset with induced errors, code locks on preterm catch-up and preterm same-day cases, tests of the recumbent/standing offset for gestational-age-corrected children, a comparison of `ext_bmiz()` against the CDC SAS program, and a regression harness for the adult algorithm that checks every row of a 1,508-row fixture at all four permissiveness levels. --- ## Per-file detail The sections below give a per-test breakdown for the files whose individual tests are worth enumerating. Files without a section here (for example the input-format, hostile-input, flexible-coding, CLI, and recentering suites) are fully described by their row in the Summary table above — the Scope column carries their coverage rather than repeating it as a per-test table. ## test-cleangrowth.R The `cleangrowth()` entry point — dispatches to child and adult algorithms and assembles combined output. | Test | What it verifies | |------|------------------| | works as expected on adult synthetic data | Spot-check records, cutpoint comparison, frozen counts, Missing handling | | works without either adult or pediatric data | Adult-only, pediatric-only, empty data all return correct row counts | | spanning subject: all rows preserved through child/adult split | Real syngrowth subject (child + adult rows): no rows lost, no NA codes | | spanning subject: child rows get child codes, adult rows get adult codes | Code prefixes on the **Detailed** level (`exclude_detail = TRUE`): `Exclude-C-*` for child, `Exclude-A-*` for adult (the default Summary `exclude` consolidates the two, so prefixes show only at Detailed) | | spanning subject: constructed subject processed correctly across boundary | Synthetic 18y–22y subject: all rows returned, correct prefixes, normal values mostly included | | adult integration: output columns present and correctly typed | All rows returned; `bin_exclude` present; `exclude` is a factor with no NAs | | adult integration: imperial units processed correctly | HEIGHTIN/WEIGHTLBS through the full pipeline; param preserved as imperial in output; all rows returned, all included, no NA codes | | adult integration: adult_permissiveness passes through | 115 cm HT → PIV at `looser`, passes PIV at `loosest` | | adult integration: adult_scale_max_lbs passes through | 136 kg flagged with cap = 300 lbs, included without a cap | | adult integration: character/UUID ids preserved | String ids survive the round trip intact | | adult integration: spanning subject has correct output structure | Subject with rows on both sides of the cutpoint; `bin_exclude` non-NA for all rows | | optional columns are emitted for adult-only input, matching mixed output | With `full_detail`/`debug`/`recenter_detail`/`qm_detail`, adult-only and child-only output have the same columns, order, and types as mixed output; adult rows carry the same values (`cf_status = "Adult"`, child-only columns `NA`) | | adult rows in an adult-only internal batch are labeled cf_status 'Adult' | Mixed run where one internal batch has no child rows | --- ## test-cran-smoke.R The only test file that intentionally runs on CRAN (no `skip_on_cran()`), because almost every other algorithm file is skipped there for runtime. A sub-second end-to-end `cleangrowth()` sanity check on tiny inline fixtures, asserting only definitional/unambiguous cases so it cannot fail on a platform-specific trajectory judgment — it guards build/load failure, output-assembly regressions, and gross numeric divergence. | Test | What it verifies | |------|------------------| | child algorithm runs end to end and assigns the definitional codes | data.table returned, no rows dropped, default columns present, valid `exclude` factor with no NAs; `measurement == 0` → Missing-Info, a non-param → Not-GC-Param, an exact same-day duplicate resolved, and a clearly-normal value Included | | adult algorithm runs end to end and excludes a population-limit value | The adult path runs end to end and an absurd population-limit weight is excluded | --- ## test-data-frame-api.R The data-frame style entry point (the primary calling style) and the `id` auto-generation/validation behavior. | Test | What it verifies | |------|------------------| | cleangrowth(data = df) works with canonical column names | data.table input with default names | | cleangrowth(data = df) works with a plain data.frame | data.frame input coerced internally | | cleangrowth() accepts remapped column names as strings | `subjid = "patid"`, `id = "row_id"`, etc. | | data-frame and vector styles produce identical results | The two calling styles agree row-for-row | | missing id column triggers warning + auto-generates | No `id` → warning, sequential 1:N | | missing required columns error clearly | Absent required column → informative error | | non-data.frame `data` argument errors clearly | Wrong `data` type → error | | column-name args reject vectors and non-strings | Column-name args must be single strings | | vector style without id warns and auto-generates | Legacy vector path, no id → warning + auto-gen | | calling with neither `data` nor vectors errors clearly | Empty call → error | | duplicate id values warn and auto-regenerate, preserving user input | Non-unique id → warning, regen, original preserved in `id`, internal id in `gc_internal_id` | | NA id values warn and auto-regenerate, preserving user input | NA id → same handling | | valid id with display_gc_id sets gc_id == id (no warning) | Valid id → no regen, `gc_id == id` | --- ## test-child-regression.R Frozen-count and stability tests on syngrowth data. These break on intentional behavior changes — update expected values (and document the re-baseline) when that happens. | Test | What it verifies | |------|------------------| | structural invariants on 100-subject subset | Row counts, column presence, factor type, no NAs, ≥1 Include | | exclusion counts match expected on 100 subjects | Frozen counts by exclusion code (regression baseline) | | spot-check individual records | Specific record ids produce specific exclusion codes | | within-subject exclusions stable at 50 vs 100 subjects | Same subject, same code regardless of batch context | | missing measurements return Missing | NA measurements → `Exclude-Missing-Info` | | HEADCM measurements are processed | HC rows present with valid codes | | gc_preload_refs: identical results to standard run | `ref_tables` produces byte-identical output | --- ## test-partial-run.R Tests the `gc_partial_run()` incremental-run wrapper. The core guarantee is that a partial run equals a full run of the same data under the same options (gc cleans each subject independently after reference recentering). | Test | What it verifies | |------|------------------| | partial run equals a full run for every subject | `exclude` + `tbc.sd` identical to `cleangrowth(new_data)` after perturbing one subject | | partial run equals a full run under checkpointing | Same, with `checkpoint_dir` set (`checkpoint_size = 5`, `keep_checkpoints = FALSE`); scratch checkpoint files cleaned up | | no changed subjects returns the cached result | Empty `changed_subjids` reuses the cache unchanged | | all subjects changed equals a plain full run | Recompute set = every subject | | an added subject is cleaned automatically | Subject in `new_data` but not the cache is re-cleaned | | a removed subject is dropped | Subject in the cache but not `new_data` is dropped | | mismatched output options error | Cache and fresh clean with different columns → hard error (no NA-fill) | | missing id / missing changed_subjids rejected | Required-argument guards | --- ## test-child-algorithms.R Constructed-scenario tests organized by algorithm step. - **CF rescue (Step 6):** standard rescues more than none; `cf_status` populated for rescued; none mode rescues none; all mode rescues all; `cf_detail` adds `cf_status`/`cf_deltaz`/`cf_nextz`; CF-E rows (CF sharing an Include day) labeled and `cf_nextz` skips temp SDEs; cf_detail columns absent by default. - **CF rescue rule (Step 6, deltaz/nextz):** a carried value followed by a continued decline is rescued (CF-R); one followed by a jump up (true CF) is excluded (CF-NR); all CFs in one identical-value string share a single status (whole-string decision on the last CF); an end-of-series CF (no `cf_nextz`) falls back to the age rule. - **Evil Twins / OTL (Step 9):** single/two/three consecutive unit errors (×2.54) excluded as `Exclude-C-Evil-Twins`; collateral damage on clean neighbors bounded. - **Error Load (Step 21):** threshold and mincount parameters control behavior; a many-error subject triggers Unevaluable-Trajectory on remaining Includes. - **Age boundary:** HEADCM cleaned ≤5y, `Exclude-HC-Out-of-Range` >5y. - **Parallel execution:** `parallel = TRUE` matches `parallel = FALSE`. - **GA correction (Step 2b):** very low birth weight gets correction (`ctbc.sd ≠ tbc.sd`); birth z ≥ −2 does not. - **Birth EWMA2 (Steps 15/16):** extreme birth weight excluded; normal included; extreme birth height excluded. - **LENGTHCM:** results identical to HEIGHTCM for the same measurements. --- ## test-child-parameters.R Each configurable parameter is accepted and behaves as documented. | Test | What it verifies | |------|------------------| | cf_rescue='all' keeps CFs, cf_rescue='standard' excludes them | CF rescue mode behavior | | ewma_window parameter is accepted and doesn't crash | Window parameter acceptance | | ewma_window rejects non-scalar / non-finite / < 1 values | Entry validation: NA/NaN/0/negative/Inf/character/length-2/logical → clean error; fractional ≥ 1 accepted | | error load parameters affect error-load exclusions | `error.load.*` plumbing | | imperial units (HEIGHTIN, WEIGHTLBS) are converted and processed | Imperial conversion | | LENGTHCM param is accepted and processed | LENGTHCM acceptance | --- ## test-child-edge-cases.R Minimal, degenerate, and boundary-condition data. | Test | What it verifies | |------|------------------| | single subject | Runs on 1 subject | | single measurement per param | 1 HT + 1 WT → valid output | | exactly 2 measurements per param | 2 per param → valid output | | all-NA measurements | All NA → all `Exclude-Missing-Info` | | mix of NA and valid | NAs get Missing-Info, valid values get real codes | | same-day identical measurements | Detected and marked | | negative agedays | → `Exclude-Missing-Info` | | HEADCM > 5 years; HEADCM ≤ 5 years | >5y `Exclude-HC-Out-of-Range`; ≤5y cleaned | | single extreme WT / HT / HC | Each excluded as population-implausible (`Exclude-C-Hard-Limit` Detailed) | | mix of data-rich and data-sparse subjects | Both handled | | carried-forward values | Detected | | deterministic results | Same input → same output (seed-independent) | --- ## test-child-et.R Child Evil Twins on a deterministic fixture (the real ET membership coverage — `test-child-algorithms.R` Tests 5–7 only assert "excluded somehow"). | Test | What it verifies | |------|------------------| | Evil-Twins membership matches expected | Per-row `Exclude-C-Evil-Twins` membership (Detailed) | | per-pattern invariants | Pattern-level invariants on the fixture | | HC interior step is cleaned, not out-of-range | HC interior step within range is cleaned, not `Exclude-HC-Out-of-Range` | --- ## test-child-birth-ewma2.R Birth HT/HC EWMA2 changes (Steps 15/16). | Test | What it verifies | |------|------------------| | Change 4: extreme non-birth value in a 3-value HT/HC SP with a birth is caught | Birth now participates in Step 15 for exactly-3-value SPs | | Change 4: a plausible 3-value HT/HC SP with a birth is left fully Include | No over-exclusion on the plausible companion | | Change 2: isolated low uncorrected birth WT kept / excluded by band | Kept when `|z|+rise ≤ 7`, excluded when `> 7` | --- ## test-child-corroboration.R | Test | What it verifies | |------|------------------| | GA correction is gated on corroboration of prematurity | Correction applied only with a corroborating early weight or same-day low height/HC | | HT/HC confirm-correction: HC corroborates only as last resort, capped at 3.5 z | HC may confirm a correction (when low and within 3.5 z of the weight) only with no early-weight and no confirming-HT corroboration; a confirming HT overrides a bad HC; the 3.5 cap blocks an otherwise-low HC over a far-lower weight | | UER3: a clean unit-error 2-group SP drops the unit-error block | UER3 2-group selection drops the unit-error values | --- ## test-child-unit-error-range.R The default-ON informational `unit_error_range` output column and its internal helpers. | Test | What it verifies | |------|------------------| | column is present and logical by default | `unit_error_range` present, logical | | `unit_error_range = FALSE` omits the column | Opt-out path | | excluded-from-cleaning rows are FALSE | `Exclude-Missing-Info` / `Exclude-HC-Out-of-Range` rows never flagged | | `.unit_error_range_flag` flags WEIGHTKG unit-error magnitudes | WT band math | | `.unit_error_range_flag` flags HEIGHTCM unit-error magnitudes | HT band math | | `.compute_unit_error_range` returns FALSE for excluded/NA-age rows | Excluded / NA-age handling | | HEADCM is evaluated through the 5y cleaning range | An inch-valued HC is flagged at 700, 1500, and 1826 days; a plausible HC is not; no `NA` | --- ## test-exclusion-code-crosswalk.R Locks the Detailed→Summary crosswalk directly (guards the silent-NA-factor class: a new internal code with no crosswalk entry fails here). Runs a syngrowth subset + constructed rows guaranteeing shared/child/adult folds. | Test | What it verifies | |------|------------------| | every output row has non-NA Detailed and Summary codes | No silent NA factor levels | | every observed Detailed code is in the canonical crosswalk | Detailed codes all mapped | | Summary equals the canonical fold of the Detailed code, every row | Each Summary == its canonical fold | | every observed Summary code is a valid Summary factor level | Summary ∈ the 14 levels | | crosswalk coverage: shared, child, and adult folds are all exercised | All three fold families hit | --- ## test-adult-clean.R 64 unit tests calling `cleanadult()` directly (via `growthcleanr:::`), organized by step. **224 assertions.** Covers PIV (1H/1W incl. BMI-PIV), minimal/edge cases, Weight Cap (4W), same-day (9H/10W), Evil Twins (9Wa anchor rule), Extreme EWMA (9Wb), Height Distinct (10H 2D band + 3+D window + `ht_band`), Moderate EWMA (11Wb), 1D (13, incl. BMI limits + custom limits, the no-BMI height minimum being equal at loosest and looser, and a single-value height kept when any same-day BMI is in range), Error Load (14), parameter validation (incl. invalid and non-scalar `permissiveness` → clean error, not a length-mismatch crash; and hostile setting overrides — NA / non-finite / non-scalar / wrong-type `overall_ht_min` / `allow_ht_gain` / `repval_handling` etc. → clean type errors, not scalar-`if()` crashes), output structure, repeated values (2W), 2D ordered/non-ordered weight (11Wa/11Wa2), cap influence, the full-fixture regression (against `inst/testdata/adult-gc-test-ALL-PHASES.csv`, 1508 rows; the WT and HT rows carrying expected codes are compared), imperial units, mean height (computed from finally-included heights only, populated on excluded height rows, `NA` when no height is included), multi-subject isolation, the permissiveness framework (4 levels + overrides + `allow_ht_gain`/`mod_ewma_f`/`allofus15` effects), and `permissiveness_presets()` completeness. --- ## test-adult-cutpoint.R Routing at `adult_cutpoint`, the adult-cutpoint advisory, and adult-specific input handling, all end-to-end through `cleangrowth()`. **24 assertions.** | Test | What it verifies | |------|------------------| | all-adult (>=18y) data with no sex value is not auto-routed | At the default cutpoint (20), 18-20y rows stay pediatric: the advisory warning fires, then the run stops because no pediatric row has a usable value in the sex column | | `adult_cutpoint = 18` routes the same data to the adult path | Advisory silent; all 6 rows returned (adult rows are cleaned without a sex value) | | advisory fires when the default is left in place | All-adult 18-20y data with a sex value: advisory fires and the rows are cleaned by the child algorithm, not set aside | | an explicit `adult_cutpoint` silences the advisory | Both `adult_cutpoint = 20` and `adult_cutpoint = 18` | | one sub-18y measurement suppresses the advisory | Data that is not all-adult gets no advisory | | NA-age rows are ignored for the advisory | Every row with a known age is >= 18y, so the advisory still fires | | a subject's routing is independent of the rest of the dataset | An 18-20y subject gets identical per-row results cleaned alone or together with a 5-7y subject | | adult HEADCM and LENGTHCM | Adult HEADCM -> `Exclude-HC-Out-of-Range` with `unit_error_range = FALSE`; adult LENGTHCM keeps its submitted label, has `gc_internal_param = HEIGHTCM`, is included, and takes part in height cleaning (a 120 cm height is excluded against it); weights unaffected | | missing-column error names the missing role | A missing sex column gives `e.g. sex = "your_sex_column"` plus the note that the value may be blank or NA for adults; a missing measurement column names `measurement` and omits the sex note | | sex-less pediatric rows suggest `adult_cutpoint = 18` only when appropriate | With adult rows present, the pediatric rows are set aside and the warning carries the suggestion when every pediatric row is >= 18y, not when one is younger; with no adult rows, the stop carries it | --- ## test-adult-et.R Adult Evil Twins anchor rule (Adult Step 9Wa), the adult-side companion to `test-child-et.R`. | Test | What it verifies | |------|------------------| | flanked over-the-limit weight pair triggers Evil Twins | Anchored pair fires | | single interior weight spike is ceded to EWMA | Lone interior spike not ET | | single end weight spike is ceded (no anchor before it) | End spike not ET | | does not fire with fewer than 4 eligible weights | `n ≥ 4` requirement | | can fire with exactly 4 eligible weights | Boundary of the requirement | | fires for sex = NA adults (averaged CSD fallback) | sex=NA path uses M/F-averaged CSD | | adult CSD reference includes an M/F-averaged sex = NA row | Reference table has the averaged row | --- ## test-checkpoint.R Checkpointing via `cleangrowth(checkpoint_dir = ...)` (the folded interface) plus the deprecated `cleangrowth_checkpoint()` shim. | Test | What it verifies | |------|------------------| | cleangrowth(checkpoint_dir=) is identical to a single-pass run | `exclude`, `tbc.sd`, and every shared column match a plain `cleangrowth()` run | | default run starts fresh: stale checkpoints removed, result correct | `checkpoint_resume = FALSE` clears prior files; result matches a plain run | | 1/2 sex stop fires before any checkpoint is cleaned | Sorted 1/2 data stops with no checkpoint file written; a dataset that also has 0s runs even if one checkpoint holds only 1s and 2s | | checkpoint_resume = TRUE reuses prior checkpoints | Completed checkpoints skipped on resume; a fresh run overwrites | | resuming with changed data warns in user terms and the summary adds up | Rows not in the checkpoint files: warning with no internal function name, `exclude` NA, input values kept, summary "No result" line | | resuming with changed data keeps input values with character ids and extra columns | Same with character ids and a carried-through column | | explicit checkpoint_size over the checkpoint cap is increased with a warning | Explicit oversized `checkpoint_size` is increased to stay within the cap (9999 checkpoints; lowered in the test), with a warning; all rows returned | | checkpoint_size and batch_size are independent knobs | `batch_size < checkpoint_size` still equals the single-pass run | | re-widen (semi-wide) output under checkpoint equals non-checkpoint | Reshape happens once around the checkpoint loop | | carry-through (extra long columns) under checkpoint equals non-checkpoint | Extra non-growthcleanr columns are reattached, appended after the gc columns, row-for-row identical to the non-checkpoint run | | all-adult (explicit cutpoint 18) cleans identically checkpoint vs non-checkpoint | The `adult_cutpoint` is finalized once in the outer branch and forwarded to checkpoints, so an all-adult (>= 18y) dataset at `adult_cutpoint = 18` cleans as adults under both modes (no divergence/crash) | | consolidated input warning is identical checkpoint vs non-checkpoint | One dataset-wide warning (set-aside bullets + id notice), byte-identical to the non-checkpoint run — not fragmented per checkpoint with per-checkpoint denominators | | deprecated cleangrowth_checkpoint() shim warns and forwards | Emits a deprecation warning; result equals the folded interface | | cleangrowth_checkpoint_combine() re-combines a completed run in id order | Standalone re-combine of persisted checkpoint files; `id_order = data$id` restores input order, `exclude` matches the finalized run per id | | cleangrowth_checkpoint_combine() with id_order = NULL returns all rows in checkpoint order | No reorder requested; every input id present exactly once | | cleangrowth_checkpoint_combine() warns on id_order ids absent from the files | Unmatched id → plain-language warning + one NA-filled row | | cleangrowth_checkpoint_combine() errors on a missing directory | `checkpoint_dir` does not exist → clean error | | cleangrowth_checkpoint_combine() errors when no checkpoint files are present | Empty directory (no `checkpoint_NNNN.rds`) → clean error | | cleangrowth_checkpoint_combine() honors quietly / verbose / log_file | `quietly` silent; default prints combine/done; `verbose` prints per-file progress; `log_file` receives the log | | mean_ht = TRUE is returned under checkpointing, matching a single run | The `mean_ht` column reaches every checkpoint and matches a plain run | | default mean_ht: same result under checkpointing, with and without adult rows | Child-only data has no `mean_ht` column under checkpointing; with mixed data every row has it, even when some checkpoints hold only children, and it matches a single run | | temporary checkpoint files are removed when a run stops with an error | The temporary checkpoint folder is gone after a run that fails once its files are written | | a kept checkpoint folder is not removed when a run stops with an error | Files in a user-named `checkpoint_dir` remain after a failed run | | every cleangrowth() argument is forwarded to checkpoints or deliberately not | Each formal of `cleangrowth()` is either passed to the per-checkpoint call or on an explicit not-forwarded list, so a new argument cannot be silently dropped | --- ## test-utils.R Post-cleaning utilities (outside the algorithm). **524 assertions** (loop-heavy). | Test group | What it verifies | |------|------------------| | recode_sex (defaults / custom / in place) | 0/1 → 1/2 recode and custom mappings; in-place recode does not recode twice | | ext_bmiz sex coding | Stop on numeric sex (also as text or factor) unless the values are both 1 and 2 and no others; count warning for missing/unrecognized sex at ages 24–240 months; logical `sex` not read as a code; 1/2 and text codes give the same results; single-sex M or F silent | | ext_bmiz existing output columns and ages near 24 months | Warns and overwrites existing output columns without changing the input; count warning for rows at about 729–730 days | | longwide (defaults / extra cols / keep-unmatched / other codes / errors) | Long→wide reshape, `extra_cols` match logic, `keep_unmatched_data`, `inclusion_types`, duplicate-id error | | longwide sex and param parsing | Sex read like `cleangrowth()` (M/F, words, `sex_coding`, remapped column) and output as M/F, with a warning on missing/unrecognized sex; 1/2 stop and all-1 warning under the default coding, checked before rows are filtered; all-male data through `simple_bmi()` and `ext_bmiz()` silent; param read like `cleangrowth()` (`param_coding`, imperial → metric, LENGTHCM as height, HC dropped, unrecognized params warned) | | simple_bmi | `wt / (ht·0.01)²` | --- ## test-visit-date-agedays.R Fractional `agedays` is cleaned as whole days but returned as submitted, with the whole-day value in `gc_internal_agedays` (`internal_detail`). Computing `agedays` from a date of birth plus a visit date (data-frame style). Covers route resolution (`agedays` column wins when present; `age_source = "dates"` forces the date route; supplying both an `agedays` column and dates uses `agedays` silently, no warning), the both-required rule for *computing* ages from dates (a lone date column with no `agedays` column errors), the parse-only path (a lone `visit_date` or `dob` alongside an `agedays` column is parsed and surfaced as `gc_internal_visit_date`/`gc_internal_dob` under `internal_detail` without driving ages, and is gated behind that flag), and the expanded set of accepted text-date formats: order-free year-first numeric (`2013-03-04`, `2013/03/04`, `20130304`) and English spelled-month (`03mar2013`, `March 15, 2024`), plus ambiguous numeric day/month (`3/4/2013`) only when `date_order` is set. Also covers the anti-misparse guards — one format class per run (jointly across both columns), four-digit `19xx`/`20xx` years only (two-digit and out-of-range rejected), English-locale-only month names, and pattern-based classification so a content-unambiguous `25/12/2013` still needs `date_order` — rejection of unrecognized/numeric (epoch-ambiguous) columns, the `gc_internal_dob`/`gc_internal_visit_date` audit columns under `full_detail`, blank/`NA` dates yielding a missing age, and a visit date before the date of birth. --- ## test-carry-through.R `carry_through` for long input: columns growthcleanr does not consume as role columns are parked and returned appended after the growthcleanr columns, in their original order with values and types intact. Covers reserved-name collisions (the user's column returned as `userinput_`, with a warning; a user column named `line` carried unchanged), `carry_through = FALSE` restoring the drop-all behavior, `dob`/`visit_date` echoing back this way, the no-`id` path (reattachment keys on input row position, not the user's id), carried columns named like a remapped role column (e.g. a column `subjid` when `subjid = "patid"`) returned with the user's values in single and checkpointed runs alike, a role column named `line` returned intact alongside carried columns, and the guarantee that the input table is not modified. --- ## test-consolidated-input-warning.R The consolidated input warning (Option B) and the three Class-2 defect warnings it introduced. Covers: clean data with ids is silent; `measurement == 0`, negative `measurement`, and negative `agedays` each warn and are set aside (per-parameter-group N/% for the measurement value problems, all-rows N/% for age); several issues collapse into a single `warning()` whose lead line counts the **union** of distinct set-aside rows (not the sum); a missing `id` alone yields only the `Generated ids` section (no `Unusable data` lead line) with the tie-breaking explanation; and the unrecognized-sex bullet leads with the `sex_coding` fix. --- ## test-semiwide-input.R `input_format = "semi_wide"`: one row per visit, one column per parameter, reshaped to canonical long before cleaning. The keystone is equivalence -- the same measurements submitted semi-wide and long must produce identical exclusion codes, checked on a hand-built fixture and a syngrowth slice. Also covers `semiwide_cols` remapping, structural column remapping, empty cells versus a literal `0`, same-day duplicates resolving as same-day extraneous values, the missing-weight/height warnings (the head-circumference-absent warning was removed 2026-07-24), the date route, run-to-run determinism, the error catalog, and long output returning the user's id under its own name with growthcleanr's number in `gc_internal_id`, the long-only length rule (length.adjust is rejected in semi-wide input; a `LENGTHCM`/`LENGTHIN` value at any age gets default handling, matching a length column mapped to `HEIGHTCM`, including at 19 years and at days 729 and 730). --- ## test-wide-input.R `input_format = "wide"`: one row per subject, repeated observation blocks marked by a `_N` column suffix, reshaped to canonical long before cleaning. The keystone is again equivalence -- the same measurements submitted wide and long must produce identical exclusion codes -- checked on a 40-subject syngrowth slice and on a hand-built ragged fixture; a companion test asserts wide and semi-wide input agree on the same measurements, and the hand-built case also pins the minted-id emission order (subject, block, then weight -> height -> head circumference). Also covers block discovery from the data (occupied cells only, so ragged subjects need no padding), default parameter-named prefixes, `wide_cols` and structural/age-prefix remapping, imperial and length prefixes (pounds/inches clean identically to the metric equivalents while `param`/`measurement` come back verbatim), two blocks on one day resolving as same-day extraneous values, the date route (`dob` + `visit_date_N`), a value in a block with no age flagged `Exclude-Missing-Info`, the four strictness warnings (block-numbering gap, `max_blocks` exceeded, non-monotonic block ages, a subject on more than one row), the missing-parameter warnings, run-to-run and row-order determinism, the input table left unmutated, the error catalog (no block columns, prefix with no `_N` columns, unrecognized parameter, two parameters sharing a prefix, missing subjid, no age source, supplied `id`, leading-zero block collision), the round-trip spec the re-widened output layer consumes, and the long-only length rule (length.adjust rejected; a `LENGTHCM`/`LENGTHIN` value at 2 years or older gets default handling, matching a prefix mapped to `HEIGHTCM`). --- ## test-semiwide-output.R `output_format = "semi_wide"`: the submitted table is returned with the requested growthcleanr columns slotted in beside each value column. The keystone closes an equivalence chain in both directions -- `long -> long` == `semi -> long` == melt-back of `semi -> semi` -- on a hand-built fixture and on a syngrowth slice with same-day duplicates preserved as separate visit rows; the melt-back is written in the test rather than reusing package code, so an adapter bug cannot cancel itself out. Also covers echo fidelity (`identical()` per submitted column, input not mutated, verdict placed immediately right of its value column), `widened_emit` control and its switch-gated errors, imperial conversion reported through `gc_internal_meas`/`gc_internal_param`, the two-directional length offset on long-format fixtures (both directions and the 729/730-day boundary checked through `sd.orig`, conversion-before-offset, `gc_internal_meas` left at the entered metric value, adult `LENGTHCM` relabeled `HEIGHTCM` but not offset, missing ages left alone; length.adjust in semi-wide input is no longer supported -- see the long-only length rule under the input files), `carry_through` in mirror mode, the three scope errors, and determinism. --- ## test-wide-output.R `output_format = "wide"`: the submitted subject rows are returned with the requested growthcleanr columns slotted in beside each value block column (`exclude_wt_1` right of `wt_1`) -- direct long -> wide, echoing the input table rather than rebuilding it. The keystone closes the same both-directions equivalence chain as semi-wide output -- `long -> long` == `wide -> long` == melt-back of `wide -> wide` -- on a hand-built ragged fixture and on a syngrowth slice (blocks built from distinct visit days); the melt-back is written in the test. A separate weight-only test exercises **same-day extraneous values at scale** -- injected duplicate same-day weights become consecutive same-`agedays` blocks (the case the WT+HT keystone must dedupe away) and are asserted to resolve (>= 8 `Exclude-Extraneous`) and round-trip through wide output unchanged. Also covers echo fidelity (`identical()` per submitted column, input not mutated, verdict immediately right of its value column, dropping the generated columns reproduces the input), ragged blocks getting `NA` verdicts, `widened_emit` control including the wide-specific per-block `gc_internal_agedays_N` level (one column per block, not per measurement, `NA` for absent blocks) and switch-gated errors, imperial values echoed verbatim while reported in metric through `gc_internal_meas`/`gc_internal_param`, `carry_through` in mirror mode, same-day blocks resolved as extraneous values with verdicts landing on their own columns, the scope errors (long/semi-wide -> wide refused) and the pre-cleaning name collision, and determinism. --- ## test-growthcleanr-app.R The bundled Shiny app: that it ships and loads, plus the app's mapping-stage guards that reject bad column mappings before cleaning runs. The mapping-guard tests exercise the app's helper logic directly (not a live browser session). | Test | What it verifies | |------|------------------| | growthcleanr_app() is exported with a stable signature | The launcher is exported with the expected formals | | the Shiny app directory ships and app.R parses | The app directory is installed and `app.R` parses (guards the historical package/path mismatch + syntax breakage; CRAN-safe) | | wide: a wrong age prefix is rejected at mapping, not deferred to cleaning | A mis-specified wide age prefix errors at the mapping step, not downstream in cleaning | | sex-coding auto-detect recognizes default pairs and skips 1/2 | Auto-detect recognizes the default sex-code pairs and declines the ambiguous 1/2 coding | | value_freq / mapped column reactives track the sex and param selection | The value-frequency and mapped-column reactives update with the selected sex/param columns | | semi-wide: custom columns map, measurement columns keep their names | Custom semi-wide column mappings apply while the measurement columns retain their names | | semi-wide: an all-miss measurement mapping fails at Step 2, not at cleaning | A measurement column that maps to all-missing is caught at the mapping step (Step 2), not at cleaning | | hostile-input guard helpers catch the target problems and pass clean input | The app's hostile-input guard helpers flag the target problems and pass clean input through | | long: a non-numeric measurement column is rejected at mapping | A non-numeric long-format measurement column errors at mapping | | long: mapping the same column to two fields is rejected at mapping | Mapping one column to two roles errors at mapping | | long: a valid numeric mapping still succeeds (guards do not over-reject) | A valid long-format numeric mapping is accepted (guards do not over-reject) | | long: results keep the user's names, including role-like unmapped columns | A full long run returns the user's column names, and unmapped columns named like growthcleanr's stay unchanged | | semi-wide: results keep the user's names | A full semi-wide run returns the user's column names plus `exclude_` | | wide: results keep the user's names | A full wide run returns the user's column names | | keep-only trim keeps the mapped columns under the user's names | The keep-only option keeps exactly the mapped columns, under the user's names | | the exclusion summary does not count the user's own exclude_ columns | A user column named `exclude_reason` is not pooled into the summary, in all three formats | | the format can be chosen before or after the file is loaded | Mapping works with either order in Step 1 | | date samples skip blank and no-digit values and use a wide block's first usable column | The sample-date helper returns the first usable value for a column or wide block, and nothing when there is none | | exact standard measurement names pre-select the column and its unit | `WEIGHTLBS`, `HEIGHTIN`, `HEADCM` and `HEADIN` pre-select the column and the matching unit in semi-wide and wide; custom names are not guessed | | a new file, format or mapping clears results, notes and the stale mapping | Re-applying a mapping clears results and notes; a new file or format also clears the mapping | | a failed run does not leave the prior run's results available | After a run stops, no results remain from the earlier run; stating the date order lets the same file run | --- ## Coverage Gaps Summary of the standing gaps: ### Known gaps - **CF rescue rule (deltaz/nextz):** covered by constructed directional tests (decline→rescue, jump→exclude, whole-string, end-of-series age rule) plus regression counts; the boundary values (sum = 1, `cf_deltaz` = 1.5/0.5/1, the 13y age split) are not each pinned by a dedicated test. - **Child Step 17 HC velocity:** no test asserts an HC `Exclude-C-Abs-Diff`; the whole HC velocity path is covered only via frozen counts. - **Child Steps 15/16 middle/first/last EWMA2 variants:** only the birth-WT and 3-value-SP paths are constructed; middle/first/last value rules are covered indirectly via regression counts. - **Child Step 19 (pairs/singles):** frozen count only (currently 0 in the regression subset); pair-split and single-branch boundaries untested. - **Child Step 13 EWMA-based SDE resolution / robust SP-median drop:** only whole-day identicals are constructed. - **Adult:** error load with the −5 exponent, and very-low/high UW scaling edge cases, are not covered.