Skip to content

Modernization: Phase 0 audit + Phase 1 correctness & dependency hygiene (0.4.0) - #34

Draft
gavinrozzi wants to merge 25 commits into
masterfrom
modernization-2026
Draft

Modernization: Phase 0 audit + Phase 1 correctness & dependency hygiene (0.4.0)#34
gavinrozzi wants to merge 25 commits into
masterfrom
modernization-2026

Conversation

@gavinrozzi

@gavinrozzi gavinrozzi commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Modernization: Phase 0 audit + Phase 1 correctness & dependency hygiene

This draft PR is the first tranche of the phased modernization effort. It contains the
Phase 0 discovery audit (AUDIT.md) and the Phase 1 "quick wins" — correctness fixes and
dependency hygiene — targeting a 0.4.0 release. The reproducible data pipeline (Phase 2)
and performance/stability work (Phase 3) will follow on this branch or successors.

Phase 0 — audit (see AUDIT.md for the full document)

Phase 1 — what's in this PR

Correctness (#20, #27)

Dependency hygiene (#21, #24, #28)

  • raster removed (internal vectorized haversine replaces pointDistance).
  • tidycensus removed (Census FIPS table vendored as internal data, data-raw/fips_codes.R).
  • tidyr removed; jsonlite/httr/curl/RSQLite/DBI demoted to Suggests gated by
    rlang::check_installed().
  • Imports is now dplyr, rlang, stringr, utils — zero GDAL/GEOS/PROJ/arrow/retired
    packages at load. Package load: ~2.7 s / 54 namespaces → ~0.15 s / 25 namespaces.
  • Distances are now haversine rather than WGS84 geodesic (≤ ~0.5% difference, in NEWS).

Performance (partial #33)

  • search_radius(): vectorized distance pass (was a ~42k-iteration loop); typical query
    now ~10 ms. Bounding-box prefilter + benchmark script planned for Phase 3.

Docs (#13, #14, #19, #29, #32)

  • New FAQ vignette (longitude sign convention, ZIP-vs-ZCTA/missing ZIPs, jurisdiction
    caveats, lazy-data install error).
  • zip_code_db provenance-and-limitations section; zip_to_cd vintage disclosure.

Infrastructure

  • r-lib/actions v2 check matrix (ubuntu release/devel/oldrel-1, macOS, Windows) +
    Codecov coverage workflow; citEntry()bibentry(); version 0.4.0; NEWS.md with
    every issue number.

Staged, not posted

Verification

  • devtools::test(): 77 pass / 0 fail / 0 warnings (baseline was 51 pass + 28
    deprecation warnings).
  • R CMD check --as-cran: no errors/warnings/notes attributable to the package on the
    local run (details in AUDIT.md §1 and the CI matrix on this PR).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg


Phase 2 — reproducible data pipeline & refreshed data (added)

  • data-raw/ pipeline: pinned + SHA256-checksummed sources (Census 2020 relationship
    files, 2024 Gazetteer, ACS 5-yr vintage 2023 via CENSUS_API_KEY, GeoNames CC-BY,
    MIT uszipcode snapshots), carry-forward + refresh build, ~20-check validation
    gate
    (all passing), licensing decisions in data-raw/README.md.
  • Data refreshed: zip_code_db 41,877 → 42,725 (every 2020 ZCTA incl. 97003 Zipcode 97003 and a dozen or so other zipcodes in Oregon are missing from zip_code_db #25;
    91230 No data found for ZIP code 91230 #26 via curated supplement; 787 Military ZIPs; 0 removed); Gazetteer
    coordinates + ACS 2023 attributes; zcta_crosswalk → 2020 vintage;
    zip_to_cd119th Congress (Outdated congressional districts in "data/zip_to_cd.rda" #29, crediting PR Update zip code/congressional district relationships (fixes #29) #30).
  • zip_data_version() accessor; download_comprehensive_data() (checksum-verified,
    R_user_dir cache, never auto-downloads; Depends → R ≥ 4.0). The data-2026.08
    release is uploaded as a draft — publish it to activate the function.
  • refresh-data workflow: manual dispatch + quarterly, opens a draft PR with the gate's
    diff summary; needs the CENSUS_API_KEY repo secret; one-line switch to the
    self-hosted runner.
  • Fixed the pre-existing pkgdown workflow failure (R code run without shell: Rscript).

Phase 3 — performance & stability (added)

  • search_radius(): bounding-box prefilter + 3-column materialization. bench/:
    ~700–770 ms / 4.3 GB per query (0.3.5 loop) → ~2.5 ms / 3.5 MB (~280×), result
    sets verified identical.
  • Input validation with informative errors (search_radius, zip_distance).
  • download_zip_data() deprecated and defanged to a no-op — its body could never
    refresh installed data and, when run from a source checkout, silently overwrote
    pipeline-built files (observed live during testing). Flagging for explicit maintainer
    sign-off since it changes runtime behavior of an exported function.
  • Return-schema snapshot tests for every exported function; get_cd() dead-code/join
    fix. Suite: 100 pass / 0 fail / 0 warnings.

Phase 4

Final R CMD check --as-cran: Status: OK (zero notes).

gavinrozzi and others added 25 commits August 24, 2026 09:38
Records the discovery-phase findings for the modernization effort:
baseline R CMD check and test results, function-by-function dependency
autopsy, upstream (uszipcode-project) reproducibility verdict, the
verified identity transformation contract between the 0.2.6 simple_db
snapshot and the shipped zip_code_db, missing-ZIP forensics for
issues #19/#25/#26, and the recommended Phase 2 pipeline decision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
The ordering bug reported in #20 was fixed in 0.3.4 but never gained a
regression test. Locks in both reprexes from the issue thread (the
README example and the repeated-pair case), NA behavior for
coordinate-less ZIPs, and the units/lonlat arguments. Also wraps the
pre-existing bare normalize_zip assertions in proper test_that() blocks
for testthat edition 3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
…#27)

reverse_zipcode() previously returned rows in database order, collapsed
duplicate inputs to a single row, and could therefore not be used inside
dplyr::mutate() (the size-mismatch error reported in #27). Both
functions now match inputs with match(), guaranteeing one output row per
input element, in input order, with NA rows (and a warning) for ZIP
codes not present in zip_code_db. geocode_zip() previously also silently
dropped unmatched ZIPs; it still errors when no input matches at all.

Tests written first to reproduce the exact reprex from the issue thread.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
, #33)

Removes the raster dependency - and with it the retired sp lineage and
the GDAL/GEOS/PROJ system-library chain loaded at attach time - by
replacing the two raster::pointDistance() call sites with an internal
vectorized haversine function (mean Earth radius 6,371,008.8 m).

- zip_distance() now looks up coordinates via match() (no join), keeping
  strict input order; lonlat = FALSE retains its previous meaning
  (planar distance in degree units) for backward compatibility.
- search_radius() computes all distances in one vectorized call instead
  of a per-row loop (~42k iterations), dropping runtime from seconds to
  ~10 ms; its intended NA-coordinate filter, previously a no-op due to
  argument shadowing, now actually runs.
- Distances are now spherical (haversine) rather than WGS84 geodesic;
  results can differ from previous releases by up to ~0.5%.
- Fixes tidyselect .data-in-select() deprecation warnings in the touched
  functions and a duplicated backticked %>% roxygen import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
tidycensus (and its sf -> GDAL dependency chain, the source of the
libarrow/GDAL load-time warnings) was imported wholesale solely to read
the static tidycensus::fips_codes reference table in search_fips() and
get_cd(). The 3,256-row Census FIPS table (public domain) is now
bundled as internal package data in R/sysdata.rda, regenerable via
data-raw/fips_codes.R with provenance documented.

With raster and tidycensus both removed, library(zipcodeR) no longer
touches GDAL, GEOS, PROJ, arrow, or any retired r-spatial package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
normalize_zip()'s single tidyr::extract() call is replaced with base
regmatches()/regexec(). jsonlite, httr, curl, RSQLite and DBI are used
only by download_zip_data() and move from Imports to Suggests, gated by
rlang::check_installed(). utils is now properly declared in Imports.

Imports is now: rlang, stringr, dplyr, utils.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
…#14, #19, #32)

- New FAQ vignette covering the western-hemisphere longitude sign
  convention (#14), missing/coordinate-less ZIP codes and the
  ZIP-vs-ZCTA distinction (#19, #26), jurisdiction accuracy caveats
  (#32), and the lazy-data installation error (#13).
- zip_code_db docs gain a provenance-and-limitations section (#32) and
  fix the copy-pasted zipcode_type description; zip_to_cd docs disclose
  the pre-2020 redistricting vintage (#29).
- geocode_zip docs note the longitude sign convention (#14).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
R-CMD-check now runs the standard v2 template: --as-cran checks across
ubuntu-latest (release, devel, oldrel-1), macos-latest and
windows-latest (release), replacing the retired ubuntu-20.04 images,
v1 actions, and hand-rolled dependency caching. Adds a test-coverage
workflow uploading to Codecov.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
…ry()

Every issue addressed in this release cycle is referenced in NEWS.md.
Adds AUDIT.md, CLAUDE.md and issue_comments/ to .Rbuildignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
Drafts for issues #13, #14, #20, #21, #24, #27, #28, #32, #33 — to be
reviewed and posted manually, never posted automatically. Each header
notes the suggested action (post/close).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
The pkgdown site permanently redirects from gavinrozzi.github.io to
zipcoder.39n.io; R CMD check --as-cran flags the 301s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
Six staged scripts (acquire -> build x3 -> validate -> finalize) rebuild
every bundled dataset from pinned, SHA256-checksummed primary sources:
Census 2020 relationship files, the 2024 Gazetteer, ACS 5-year estimates
(vintage pinned, key via CENSUS_API_KEY), GeoNames place names (CC BY),
and the MIT-licensed uszipcode snapshots as base + validation reference.

The build strategy is carry-forward + refresh: the shipped database is
the base (no ZIP is ever silently dropped), with upstream-1.0.1 rows,
missing 2020 ZCTAs, and curated USPS-only ZIPs appended, and ZCTA-backed
attributes refreshed from Census sources. A ~20-check validation gate
hard-fails the pipeline before anything ships and emits a diff summary
for release PRs. Licensing decisions (incl. the USPS-derived naming
columns and the future HUD-USPS stage) documented in data-raw/README.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
Data releases are now versioned separately from code releases: metadata
(release tag, build date, source vintages, comprehensive-asset checksum)
is embedded in R/sysdata.rda by the pipeline and exposed via the new
zip_data_version() accessor.

download_comprehensive_data() fetches the ~450 MB comprehensive SQLite
database (full ACS profiles per ZIP) from the versioned GitHub data
release on demand - never automatically - verifies its SHA256, and
caches it in tools::R_user_dir("zipcodeR", "data") with a documented
offline path. Depends bumped to R >= 4.0 for R_user_dir.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
Validation gate: all checks passed (see data-raw/refresh_summary.md).

- zip_code_db: 41,877 -> 42,725 rows; 0 removed. Every 2020 Census ZCTA
  now present (fixes #25 97003 and the territory ZCTAs behind #19), 787
  Military ZIPs added (new zipcode_type value), curated USPS-only ZIPs
  incl. 91230 (#26). Coordinates/areas refreshed from the Census 2024
  Gazetteer, demographics from ACS 5-year vintage 2023.
- zcta_crosswalk: rebuilt on the 2020 ZCTA/tract vintage (168,212 rows).
- zip_to_cd: rebuilt on the 119th-Congress relationship file (#29),
  following the method of PR #30 by @awallender.

Tests updated where they pinned exact 2-decimal legacy coordinates, and
extended with schema-contract and regression-ZIP checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
refresh-data.yaml runs the data-raw pipeline (validation gate included)
on manual dispatch and quarterly, then opens a draft PR carrying the
pipeline's diff summary - a human always merges. Single lightweight job
with source caching; a commented runs-on line points it at the
self-hosted runner to save hosted minutes.

pkgdown.yaml had a pre-existing failure: an R snippet run without
'shell: Rscript {0}', which bash rejects. The step was redundant with
setup-r-dependencies and is removed (preferably added to extra-packages).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
…ema snapshots (#33)

- search_radius() gains a cheap lat/lng window prefilter (5% inflated,
  pole-safe) before the exact haversine pass and now materializes only
  the three needed columns. bench/search_radius_bench.R records the
  numbers: pre-0.4.0 loop ~700-770ms / 4.3GB allocated per query;
  0.4.0 boxed ~2.5ms / 3.5MB - roughly a 280x speedup - with result
  sets verified identical to the exhaustive pass.
- Input validation with informative errors: search_radius coordinate/
  radius checks, zip_distance units via match.arg and a length-
  compatibility check.
- download_zip_data() is deprecated AND defanged to a no-op: its body
  could never refresh installed data, and when executed from a source
  checkout it overwrote pipeline-built files (observed during testing).
- get_cd() drops its dead many-to-many join (fixes a dplyr warning) and
  resolves the state abbreviation via match().
- New test-04-schemas.R freezes every exported function's return schema
  as a testthat snapshot (forward-compat contract), plus validation and
  deprecation tests. Suite: 100 pass / 0 fail / 0 warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
Maps every 0.4.0 item to its issue, sizes the follow-ups (HUD crosswalk
stage, FIPS enrichment for #7, comprehensive-DB functions, timezone
refresh), and codifies the data-refresh, deprecation, and dependency
policies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
preferably is no longer on CRAN, so any::preferably cannot resolve;
the site template installs from amirmasoudabdol/preferably instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
…d data versioning

Correctness (search_radius prefilter):
- longitude differences are wrapped onto [-180,180], so circles crossing
  the antimeridian keep their candidates (Adak/Aleutians/Guam)
- the longitude window is sized at the highest-|lat| edge of the search
  circle instead of the query point, so large radii no longer drop
  higher-latitude candidates; near-pole/all-longitude circles skip the
  prefilter. New tests assert boxed == exhaustive on the exact cases the
  review found failing, plus the antimeridian reprex.

Data (zip_to_cd rebuild):
- Census ZZ not-in-any-district pseudo-rows excluded (31 rows dropped;
  gate now enforces 4-digit CD codes)
- coverage restored for USPS-only ZIPs the pre-2020 HUD crosswalk had:
  district(s) derived from the ZIP's USPS city (6,410 ZIPs) or from the
  state where it has a single district (63); military ZIPs stay unmapped
  by design and get_cd() now warns on no-match instead of returning an
  empty result silently. Residual vs the old release: 1,266 USPS-only
  ZIPs with no principled derivation (documented). Gate gains a
  no-coverage-shrinkage check.

Data versioning / download hardening:
- comprehensive-asset tag+sha256 moved to an explicit pinned registry in
  sources.R (no longer derived from DATA_VERSION), gate-checked
- data-raw/fips_codes.R re-saves every sysdata object instead of a
  hard-coded pair (no more zip_data_meta clobbering)
- download_comprehensive_data(): Windows-safe checked rename; sha256
  fallback passes -a 256 to any shasum variant (incl. shasum.exe),
  validates 64-hex output, and probes tools:: with inherits = FALSE

Compatibility & data quality:
- zip_distance() restores data.frame-style recycling for divisible
  lengths (undocumented break found by review)
- state-modal timezone imputation for new ZIPs is now counted in the
  refresh summary; FAQ/CLAUDE.md/data docs updated to the CD119-derived
  zip_to_cd (54,817 rows)

Suite: 111 pass / 0 fail / 0 warnings. R CMD check --as-cran: OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
User-facing:
- zip_distance(lonlat = FALSE) had a unit bug in every prior release
  (Euclidean degrees put through the meters-to-miles conversion,
  returning 0.00 for essentially every pair). The mode is now deprecated
  with a warning and returns a planar equirectangular approximation in
  the requested units until removal.
- get_cd() distinguishes ZIPs absent from zip_code_db (typos, numeric
  input that lost its leading zero) from known ZIPs with no district
  mapping, instead of misdiagnosing both as military/USPS-only; input is
  coerced to character first.
- download_comprehensive_data() move-into-place simplified: rename, else
  overwrite-copy; the old cache is never deleted ahead of a successful
  replacement and a failed tmp cleanup is no longer treated as failure.
- Dead NA-scrub line removed from the search_radius prefilter.

Pipeline robustness (all rebuilt outputs verified byte-identical):
- 02: half-specified coordinates (lat xor lng NA) are normalized to NA
  pairs, and the implausible-coordinate guard requires both coords
  non-NA, removing an any(NA) crash path; imputed_tz guards simplified.
- 04: one shared eligibility predicate (!zipcode_type %in% 'Military' -
  fixing the NA-semantics drift between derivation and the unmapped
  count), one covered-ZIP intermediate for both lookups, named counts
  computed once for message/stats/summary, and tier disjointness
  asserted via stopifnot instead of a papering distinct().
- 05: refresh summary reports city-derived and single-district-state
  counts separately (the committed summary had mislabeled the merged
  count) and drops hardcoded 2010/pre-2020 baseline labels that would
  misdescribe future refreshes.
- 06 + fips_codes.R: both sysdata writers use the load-env/re-save-all
  pattern with all.names = TRUE, so no internal object can be silently
  dropped by either script.
- CLAUDE.md: lookup-function count corrected to 12.

Suite: 114 pass / 0 fail / 0 warnings. R CMD check --as-cran: OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
Gate integrity (the big one):
- 05_validate.R now takes its baseline from git (PIPELINE_BASELINE_REF,
  default HEAD) instead of the working-tree data/ files that
  06_finalize.R overwrites - a rerun previously compared the candidate
  against itself, which is how the committed refresh summary showed
  '0 added' and how the CD-coverage change slipped past its own check.
  Rerun against master, the gate now sees the real diff: +848 ZIPs and
  exactly 1,266 lost CD mappings, matched against a new documented
  one-time acceptance constant (ACCEPTED_CD_COVERAGE_LOSS in sources.R,
  to be reset to 0 post-release per ROADMAP). The committed
  refresh_summary.md is regenerated against master and is now honest
  (848 added by type, 32,907 coordinate refreshes, 33,637 ACS
  refreshes, true previous row counts, no hardcoded vintage labels).
- NEWS quantifies the 1,266 unmapped USPS-only ZIPs and the rationale
  (stale pre-2020 districts deliberately not carried; HUD stage will
  restore them), and corrects the dependency paragraph (httr removed
  entirely; Suggests serve download_comprehensive_data() and the
  pipeline, not the no-op download_zip_data()).

User-facing hardening:
- get_cd() rejects vector input with an informative error instead of
  silently recycling against zip_to_cd (or crashing in the warning
  branch).
- search_fips(): the audit-listed nchar(county_fips < 3) misplaced
  parenthesis is fixed; >3-digit codes and unknown counties now error
  informatively instead of failing silently.
- zip_distance(lonlat = FALSE) wraps the longitude difference onto
  [-180, 180], fixing ~5.5x errors on trans-dateline pairs.
- download_comprehensive_data(): SHA256 capability verified BEFORE the
  ~450 MB download (no more download-then-discard loops), the download
  runs under a temporarily raised timeout (default 60s is ~13x too
  short), and a failed download explains that the pinned data release
  may not be published yet. tools declared in Imports.

Suite: 121 pass / 0 fail / 0 warnings. R CMD check --as-cran: OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
Data semantics & API:
- Document the at-large district-code flip: DC/PR/territories now carry
  the Census delegate code 98 (voting at-large states keep 00);
  search_cd() accepts "00" and "98" as aliases so pre-2020-style
  calls keep working, and NEWS + ?zip_to_cd explain the convention.
- get_cd() labels every district with its own state: state_fips is now
  parallel to district, fixing silently wrong labels for the 258 ZIPs
  whose districts span a state line (02861 reported RI-01 as "MA").
- reverse_zipcode(NA) no longer crashes - a single NA yields an NA row
  with a warning, matching vector behavior.
- normalize_zip(100000) boundary fixed (>= instead of >); numeric and
  character branches now agree.
- search_fips(): unknown state FIPS errors informatively (previously a
  silent empty result while unknown county errored) and the state-only
  branch returns a tibble like every other search function.

Pipeline & automation:
- DATA_VERSION derives from the build date (override via
  PIPELINE_DATA_VERSION), so scheduled refreshes stamp new versions
  instead of reusing 2026.08 forever.
- The CD coverage-loss acceptance is keyed to the exact 1,266-ZIP list
  (data-raw/accepted_cd_coverage_loss.txt); losing any OTHER ZIP fails
  the gate, so the acceptance cannot mask future regressions. ROADMAP
  says to empty the file post-release.
- GeoNames US.zip is declared a floating source: its near-daily
  in-place regeneration no longer aborts scheduled refreshes; the
  downloaded hash is recorded for provenance instead of enforced.

Docs:
- README re-knit against the refreshed data (distances, 2020-vintage
  tracts); the staged issue-20 comment quotes current outputs and
  explains the coordinate-refresh delta.

Not fixed (reviewer claim refuted): DESCRIPTION's
Config/roxygen2/version 8.1.0 and the NAMESPACE format are exactly what
the installed roxygen2 8.1.0 emits via devtools::document().

Suite: 136 pass / 0 fail / 0 warnings. R CMD check --as-cran: OK.
Validation gate passes under both HEAD and master baselines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEHekDvK7kMM6rVT1vDsxg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant