Skip to contents

Manages the full skeleton pipeline lifecycle: portable batch directories, batch splitting, raw registry loading, the declarative code registry, and the orchestrated per-batch processing (framework -> trim -> codes -> randvars) that produces one Skeleton file per batch with incremental invalidation.

Portable Directory Resolution

Directories are stored as candidate path vectors and resolved lazily via CandidatePath active bindings. The first existing directory wins and is cached. If the cached path becomes invalid (e.g. after moving to a different machine), the binding automatically re-resolves from the candidate list.

Per-batch pipeline

$process_skeletons() runs four phases per batch, with per-phase incremental invalidation so editing one step only re-runs what it affects:

Phase 1 – framework

A single user function registered via $register_framework(fn), signature (batch_data, config), returns a fresh base data.table (time grid + structural censoring). Full rebuild on body(fn) / formals(fn) hash change.

Phase 1b – trim

At most one user function registered via $register_trim(fn), signature (skeleton, batch_data, config), returning a data.table. It is the one declared place in the pipeline that MAY delete skeleton rows. It runs immediately after the framework and before the code registry, so every code entry sees the final row set. A change to the registered trim rebuilds the base, because a deletion cannot be rewound. It runs only on a rebuild, so it always reads a fresh base.

Phase 2 – codes

The declarative code registry, built via $register_codes() (primary) and $register_derived_codes() (derived). Per-entry fingerprint diff: entries no longer present are dropped, new or modified entries are freshly applied. Derived entry fingerprints fold in their upstream primary fingerprints so upstream behavior edits cascade correctly.

Phase 3 – randvars

An ordered named list of user functions registered via $register_randvars(name, fn), each signature (skeleton, batch_data, config). Divergence-point rewind-and-replay invalidation: the first step whose name or hash differs from the stored sequence triggers a drop of its columns and replay of it plus everything downstream of it. Add/remove/edit/reorder all handled uniformly.

Phase 2 runs BEFORE phase 3, so a phase-3 step MAY read a phase-2 column. $randvars_hashes() folds the code registry fingerprints into every step's hash, so a registry edit replays every step against the new columns. See the Skeleton class for the on-disk provenance format.

The order is recorded on each Skeleton as phase_order. A skeleton written under the old order carries NULL there, and $process_skeletons() rebuilds it once.

Only phase 1b may delete rows. A phase-3 step that filters rows breaks the rewind-and-replay contract, because rewind drops columns and cannot restore rows. Move any such filter into $register_trim().

Code Registry

Primary entries are registered via $register_codes(), which declares codes, the function to apply them (e.g. add_diagnoses, add_cods), which rawbatch groups to use, and optional prefixing/combining. Derived entries are registered via $register_derived_codes() and OR together already-existing skeleton columns from upstream primary entries – useful when the combined column needs to draw from registrations that use DIFFERENT fns (something combine_as can't express because it re-runs the same fn on rbound data).

See also

Skeleton for the per-batch on-disk format and provenance fields; CandidatePath for the multi-host directory resolution mechanism; add_diagnoses, add_cods, add_rx for common fn choices in $register_codes().

Other skeleton_pipeline: Skeleton

Public fields

group_names

Character vector. Names of rawbatch groups.

batch_size

Integer. Number of IDs per batch.

seed

Integer. Shuffle seed for reproducibility.

id_col

Character. Person ID column name.

n_ids

Integer. Total number of IDs across all batches.

n_batches

Integer. Number of batches.

batch_id_list

List of ID vectors, one per batch.

batch_composition_digest

Character or NULL. The digest::digest() of batch_id_list, and of matched_set_roster when it is set. $set_ids(), $set_batches() and $set_matched_sets() set it. A study saved by swereg 27.2.6 or earlier has none.

matched_set_roster

List of data.tables, one per batch, or NULL. Each holds the roster rows of that batch, in roster row order. $set_matched_sets() sets it. $set_ids() and $set_batches() set it to NULL.

groups_saved

Character vector of rawbatch groups saved to disk.

code_registry

List of code registration entries, appended to by $register_codes() and $register_derived_codes(). Primary entries (from $register_codes()) are plain lists with: codes, fn, fn_args, groups, combine_as, label. Derived entries (from $register_derived_codes()) are tagged with kind = "derived" and hold codes, from, as, label instead – no fn, no groups, no raw data access. The dispatcher .apply_code_entry_impl() branches on the entry's kind field, defaulting to "primary" when absent.

created_at

POSIXct. Timestamp when this study was created.

data_rawbatch_cp

CandidatePath for the rawbatch directory.

data_skeleton_cp

CandidatePath for the skeleton directory.

data_meta_cp

CandidatePath for the metadata directory (holds registrystudy.qs2). Defaults to the rawbatch directory for backward compatibility.

data_raw_cp

CandidatePath for the raw-registry directory, or NULL if not configured.

data_summaries_cp

CandidatePath for the audit-track summaries directory (git-tracked TSV per full run), or NULL if the feature is not configured. When NULL, $compute_summary() still writes the local summary.qs2 and status.txt but skips the TSV.

framework_fn

Function of signature (batch_data, config) returning a fresh base skeleton data.table (phase 1). Set via $register_framework(). $process_skeletons() re-runs this function per batch when its body/formals hash changes.

trim_fn

Function of signature (skeleton, batch_data, config) returning a data.table (phase 1b), or NULL. Set via $register_trim(). It is the one place in the pipeline that may delete skeleton rows.

randvars_fns

Named ordered list of phase-3 functions, each with signature (skeleton, batch_data, config). Populated via $register_randvars(name, fn). Registration order is execution order. $process_skeletons() uses Skeleton$sync_randvars()'s divergence-point rewind-and-replay to apply changes incrementally.

skeleton_manifest

List, or NULL. The commit record for the skeleton dataset: proof that $process_skeletons() ran to completion and produced a complete, uniformly-built set of batches, plus an identity for exactly which batches those were and what built them. Written to registrystudy.qs2 by $process_skeletons(), atomically, via $save_meta().

NULL means "no trustworthy skeleton dataset". $process_skeletons() clears it before doing any work and only re-commits it at the end if the result validates, so a killed or partially-failed run leaves NULL rather than a stale record vouching for skeletons it no longer describes. Downstream stages that need to know what they are reading (s1) must load this from disk rather than from an embedded study copy, which is frozen at the time the plan was saved.

Fields: committed_at, swereg_version, n_batches, batches (the exact sorted batch IDs – a count alone cannot tell batches 1..N from 2..N+1), identity_hash (shared by every batch) and identity (a digest over the ordered per-batch (batch, identity_hash, saved_at) triples, so it moves when any batch is rebuilt even if the code did not change).

population_by_specs

List of character vectors. Each element declares one by aggregation that will be pre-computed during $process_skeletons() and stored in each batch's meta sidecar, so that $population(by = ...) is a fast meta-only walk. Read back with $population(by = <one of the declared specs>).

Active bindings

data_rawbatch_dir

Character (read-only). Resolved rawbatch directory for the current host. Lazily resolved from self$data_rawbatch_cp.

data_skeleton_dir

Character (read-only). Resolved skeleton directory for the current host.

data_meta_dir

Character (read-only). Resolved metadata directory for the current host (where registrystudy.qs2 lives).

data_raw_dir

Character or NULL (read-only). Resolved raw-registry directory, or NULL if not configured.

data_summaries_dir

Character or NULL (read-only). Resolved audit-track summaries directory for the current host, or NULL if not configured.

skeleton_files

Character vector (read-only). Skeleton output file paths detected on disk. Scans skeleton_dir on each access.

expected_skeleton_file_count

Integer (read-only). Expected number of skeleton files (one per batch).

meta_file

Character. Path to the on-disk metadata file (registrystudy.qs2) inside data_meta_dir.

summary

List or NULL (read-only). The summary.qs2 payload written by $process_skeletons() (per-column counts, registry-wide totals, build metadata). NULL with a one-line message if the file is missing.

Methods


RegistryStudy$save_rawbatch()

Save rawbatch files for one group.

Usage

RegistryStudy$save_rawbatch(
  group,
  data,
  n_workers = default_n_workers("rawbatch")
)

Arguments

group

Character. Group name (must be in group_names).

data

data.table or named list of data.tables.

n_workers

Integer. Number of parallel writers (default 1L, serial). When > 1L, slices are written concurrently via mirai daemons; the 'mirai' package must be installed. Each splittable data.table gets BID added in-place and is keyed on it (setkey(dt, BID)), so per-batch slices are O(log n) keyed lookups instead of O(n) %in% scans. RAM stays ~1x the input (no split materialisation).

An ID MAY be in more than one batch (see $set_batches()). Then each batch gets the rows of the IDs it holds, in input order, by one %in% scan per batch. That path adds no BID column and does not change the caller's tables.

After $set_matched_sets(), each batch gets every row of a person once for each skeleton_id of that person in the batch. The ID column of each copy holds that skeleton_id. The copies of one row are adjacent, and the rows keep their input order. The ID column takes the type of skeleton_id. The other columns do not change. Before it writes the group, the method writes each missing "roster" file. A roster where every skeleton_id equals the person ID takes the paths above.


RegistryStudy$load_rawbatch()

Load rawbatch files for a single batch.

Usage

RegistryStudy$load_rawbatch(batch_number)

Arguments

batch_number

Integer. 1-indexed batch number.

Returns

Named list of data.tables.


RegistryStudy$load_skeleton()

Load a skeleton file for batch_number as a Skeleton R6 object. Returns NULL if the file is missing (caller rebuilds from scratch). Errors if the file on disk is not a Skeleton R6 object (e.g. corrupted or from an incompatible version of swereg).

Usage

RegistryStudy$load_skeleton(batch_number)

Arguments

batch_number

Integer batch index.

Returns

A Skeleton, or NULL if the file is missing.


RegistryStudy$save_skeleton()

Save a Skeleton to this study's skeleton directory, plus a small meta_%05d.qs2 sidecar capturing provenance hashes and the per-batch code-check accumulator snapshot. Subsequent $process_skeletons() runs read the meta first and skip loading the heavy skeleton entirely when every hash still matches.

Skeleton is written first, then meta. A crash between the two leaves a stale meta on disk; the next run reads it, finds the hashes don't match the current pipeline, falls through to the slow path, and rewrites both.

This is the one site that computes the per-column code-entry counts. sk$refresh_code_entry_counts() runs before either file is written, so the skeleton file and the meta both carry counts that describe the data being written.

Usage

RegistryStudy$save_skeleton(sk, framework_removals = NULL)

Arguments

sk

A Skeleton to persist.

framework_removals

Optional data.table reporting what each framework step removed. .process_one_batch() passes the framework_removals attribute of the framework function's return on a rebuild. On a slow path that runs no framework, it passes the report the previous meta held. NULL when the caller holds no report. The table goes into the meta sidecar, which is where $compute_summary() reads it.

Returns

The full path the skeleton file was written to, invisibly.


RegistryStudy$delete_rawbatches()

Delete all rawbatch files from disk.

Usage

RegistryStudy$delete_rawbatches()


RegistryStudy$delete_skeletons()

Delete all skeleton output files (and their meta sidecars, plus any cached population_*.qs2 and summary.qs2 artefacts) from disk.

Usage

RegistryStudy$delete_skeletons()


RegistryStudy$delete_meta_file()

Delete the metadata file from disk.

Usage

RegistryStudy$delete_meta_file()


RegistryStudy$save_meta()

Save this study object as metadata. Captures the destination path first, then clears host-specific CandidatePath caches before writing, so the on-disk file never carries a resolved path from the saving host.

Usage

RegistryStudy$save_meta()


RegistryStudy$describe_codes()

Print human-readable description of all registered codes.

Usage

RegistryStudy$describe_codes()


RegistryStudy$summary_table()

Return a data.table summarizing all registered codes.

Usage

RegistryStudy$summary_table()

Returns

data.table with columns: name, codes, label, generated_columns.


RegistryStudy$population()

Read a pre-computed population table for one of the by specs declared at construction time via population_by_specs.

Population tables are computed automatically at the end of $process_skeletons() from the per-batch aggregations stored in each meta sidecar, then written as population_<spec>.qs2 in the skeleton directory. This getter just reads that file.

Usage

RegistryStudy$population(by)

Arguments

by

Character vector of column names. Must match (in any order) one of the entries in self$population_by_specs.

Returns

The population data.table with columns: isoyear, the by columns, and n (unique-person count). Errors if the spec was not declared or the file does not exist yet.


RegistryStudy$print()

Print method for RegistryStudy.

Usage

RegistryStudy$print(...)

Arguments

...

Ignored.


RegistryStudy$new()

Create a new RegistryStudy object.

Usage

RegistryStudy$new(
  data_rawbatch_dir,
  group_names = c("lmed", "inpatient", "outpatient", "cancer", "dors", "other"),
  data_skeleton_dir = data_rawbatch_dir,
  data_meta_dir = data_rawbatch_dir,
  data_raw_dir = NULL,
  data_summaries_dir = NULL,
  batch_size = 1000L,
  seed = 4L,
  id_col = "lopnr",
  population_by_specs = list()
)

Arguments

data_rawbatch_dir

Character vector of candidate paths for rawbatch files. The first existing path is used; a single non-existing path is created automatically.

group_names

Character vector of rawbatch group names.

data_skeleton_dir

Character vector of candidate paths for skeleton output. Defaults to same candidates as data_rawbatch_dir.

data_meta_dir

Character vector of candidate paths for the metadata directory holding registrystudy.qs2. Defaults to same candidates as data_rawbatch_dir (backward compatible). Pass an explicit value – e.g. the parent of rawbatch – to keep the singleton control file out of the per-batch data directory.

data_raw_dir

Character vector of candidate paths for raw registry files (optional). NULL if raw data paths are managed externally.

data_summaries_dir

Optional character vector of candidate paths for the audit-track summaries directory (typically inside the project git repo, e.g. dev/summaries/). When NULL (default), $compute_summary() still writes summary.qs2 and status.txt to the skeleton directory but skips the git-tracked TSV.

batch_size

Integer. Number of IDs per batch. Default: 1000L.

seed

Integer. Shuffle seed.

id_col

Character. Person ID column name.

population_by_specs

Optional list of character vectors. Each element declares one by aggregation pre-computed during $process_skeletons() and stored in each batch's meta sidecar for fast $population(by) access. Example: list(c("rd_age_continuous"), c("rd_age_continuous", "ri_is_amab")). Default: empty list.


RegistryStudy$check_version()

Check if this object's schema version matches the current class version. Errors if the object was saved with an older schema.

Usage

RegistryStudy$check_version()

Returns

invisible(TRUE) if versions match. Errors otherwise with an actionable migration message.


RegistryStudy$register_framework()

Register the framework function (phase 1). Called once per batch at the start of $process_skeletons(), with signature function(batch_data, config), returns a fresh data.table containing the base time grid + censoring. Everything downstream builds on this output. A change to the function body or formals triggers a full rebuild of every batch on the next $process_skeletons() run.

Usage

RegistryStudy$register_framework(fn)

Arguments

fn

A function of signature (batch_data, config) returning a data.table.

Returns

invisible(self).


RegistryStudy$register_trim()

Register the trim function (phase 1b). A study may register at most one. It runs immediately after the framework and before the code registry, so every code entry sees the row set the trim leaves behind.

$register_trim() stops on a second call, naming the trim already registered. $register_framework() overwrites in the same situation. The two differ deliberately. A study has one framework by construction. A second $register_trim() call is a script that means to delete rows in two places.

This is the one declared place in the pipeline that MAY delete skeleton rows. Phase 3 MUST NOT: its rewind drops columns and cannot restore rows.

fn runs exactly once per rebuild, on a fresh base. It never sees data it already trimmed, so it MAY delete a fixed count or a fraction. An edit to a randvars step or to a code entry rebuilds nothing and therefore re-runs no trim.

A change to body(fn) or formals(fn) rebuilds the base skeleton of every batch. So does adding a trim to a study that had none, and so does removing one.

Usage

RegistryStudy$register_trim(fn)

Arguments

fn

A function of signature (skeleton, batch_data, config) returning a data.table. $process_skeletons() rebinds the skeleton data to what it returns, and stops when it returns anything else.

Returns

invisible(self).


RegistryStudy$register_randvars()

Register one phase-3 "random variables" step. Phase 3 is an ordered sequence of user-supplied functions; each call to $register_randvars() appends one step to the end of the sequence. Registration order is execution order at $process_skeletons() time.

Signature of fn: function(skeleton, batch_data, config). It mutates skeleton in place and must ONLY ADD columns (never modifying or deleting existing ones – the drop-and-replay tracking depends on this invariant).

Phase 3 runs after phase 2, so fn MAY read a code registry column.

An edit to fn's body, under the same name, changes the step's hash. It replays this step and every step after it. A change to the framework function, or to any code registry entry, replays the whole sequence: $randvars_hashes() folds both into every step's hash.

fn MUST NOT change the row count. Skeleton$sync_randvars() compares the row count before and after every replayed step. It stops the run when the count moves, and it names the step. Register a row filter with $register_trim() instead. The trim runs on a fresh base, before the code registry, so every later phase sees the rows it leaves.

Nothing checks the add-only contract. Rewind drops the columns a step recorded. It cannot restore a column the step overwrote.

Usage

RegistryStudy$register_randvars(name, fn)

Arguments

name

Character scalar. The user-facing step name. Used as the key in Skeleton$randvars_state and in the divergence-point comparison.

fn

A function of signature (skeleton, batch_data, config).

Returns

invisible(self).


RegistryStudy$code_registry_fingerprints()

Return the xxhash64 fingerprint of every entry in self$code_registry, in registry order.

Primary entries: fingerprint depends on (codes, groups, fn_args, combine_as) and on the hash of the entry's fn. Two primary entries produce the same fingerprint, and are therefore treated as "the same entry" across runs, only when all five agree. An edit to a registered code function's body re-applies that entry. label is NOT included: it is presentation, and editing it replays nothing.

Derived entries: fingerprint depends on (codes, from, as) PLUS the fingerprints of every upstream primary entry whose output prefix is referenced in from. This cascades invalidation when an upstream primary's fn_args / groups / codes change, without requiring the user to touch the derived entry. Computed in a two-pass walk: primary fingerprints first, then derived fingerprints using the already-computed upstream fingerprints.

Used by Skeleton$sync_with_registry() for incremental per-entry add/drop.

Usage

RegistryStudy$code_registry_fingerprints()

Returns

Character vector of fingerprints.


RegistryStudy$randvars_hashes()

Return one hash per registered phase-3 step, named by step name and in registration order.

Each step's hash folds in four inputs. They are the step function's own body and formals, the framework function's hash, the trim function's identity, and the code registry fingerprint set. Every step takes the same framework, trim and code registry components. So a change to any of the three diverges at step 1, and Skeleton$sync_randvars() replays the whole sequence.

The framework component is NA_character_ when no framework function is registered. The trim component is "__swereg_no_trim__" when no trim function is registered.

Two inputs are NOT covered. A change to either one replays nothing:

  • Whatever a registered function calls or reads. Each hash covers that function's own body and formals. It does not follow a call into a helper, and it does not read a variable the function captured from its environment.

  • The rawbatch data. Nothing hashes raw content, so new raw data alone replays nothing.

$pipeline_identity() and $process_skeletons() both call this. $process_skeletons() passes the result to Skeleton$sync_randvars(), which stores each step's hash in Skeleton$randvars_state and compares it on the next run.

Usage

RegistryStudy$randvars_hashes()

Returns

Named character vector of xxhash64 digests, parallel to self$randvars_fns. character(0) when no step is registered.


RegistryStudy$pipeline_identity()

This study's current pipeline identity: the five components that decide what a freshly-built skeleton looks like, each normalized so that two representations of the same pipeline compare equal.

The components are framework, trim, phase_order, randvars and codes. codes is a SORTED SET of code-entry fingerprints, because Skeleton$sync_with_registry() applies entries by set difference: a re-applied entry moves to the end of the stored registry while naming an unchanged pipeline. The other four stay ordered, because for those the order is semantic.

identical(sk$pipeline_identity(), study$pipeline_identity()) is necessary for a synced skeleton. It is not sufficient. Unequal components mean the skeleton is definitely stale. Equal ones mean only that nothing changed among those five inputs.

Two inputs sit outside the identity: the rawbatch data, and whatever a registered function calls or reads from its environment. A change to either one leaves the identity equal over a stale skeleton. $randvars_hashes() says why.

.PHASE_ORDER is a package constant, so it never discriminates between two studies. It discriminates on the SKELETON side, where an old skeleton reads NULL.

Usage

RegistryStudy$pipeline_identity()

Returns

A named list with elements framework, trim, phase_order, randvars and codes.


RegistryStudy$adopt_runtime_state_from()

Copy runtime state (IDs, batch list, saved groups) from another RegistryStudy into this one, WITHOUT touching config fields (group_names, code_registry, directory candidates, framework/randvars registration, schema version, etc.).

Use case: in run_generic_create_datasets_v2.R, the generator script constructs a fresh study every run with the current in-memory config, then on re-runs calls $adopt_runtime_state_from(qs2_read(self$meta_file)) to pick up batch ids and saved-group state without silently adopting a stale code registry or group name list.

Usage

RegistryStudy$adopt_runtime_state_from(other)

Arguments

other

Another RegistryStudy to copy runtime state from.

Returns

invisible(self).


RegistryStudy$register_codes()

Register code definitions for the code registry.

Each call declares codes, the function to apply them, which batch data groups to use, and optional prefixing/combining. Appends to self$code_registry.

Usage

RegistryStudy$register_codes(
  codes,
  fn,
  groups,
  fn_args = list(),
  combine_as = NULL,
  label = NULL
)

Arguments

codes

Named list of code vectors (e.g. ICD-10, ATC, operation codes).

fn

Function to call (e.g. add_diagnoses, add_rx).

groups

Named list mapping prefixes to group names. Unnamed elements get no prefix. Each element is a character vector of group names to rbindlist before calling fn.

fn_args

Named list of extra arguments to pass to fn (e.g. list(source = "atc")).

combine_as

Character or NULL. If non-NULL, also run fn on all groups combined, using this as the prefix.

label

Character. Human-readable label for describe_codes() output. Defaults to deparse(substitute(fn)). Presentation only: it is NOT part of the entry's fingerprint, so editing it replays nothing.

The call STOPS on either of two conflicts. It stops when groups names a group that is not in the study's group_names, because $load_rawbatch() reads one file per name there. It stops when the entry would generate a column an already-registered entry generates, because $drop_code_entry() removes exactly the columns an entry declares.


RegistryStudy$register_derived_codes()

Register a derived code entry: one that doesn't read rawbatch data, but instead ORs together already-existing skeleton columns from earlier primary entries.

For each name <nm> in codes, a new column <as>_<nm> is written as Reduce("|", list(get("<from[1]>_<nm>"), ...)). The codes list pattern values are ignored at apply time but DO participate in the fingerprint, so editing the code list triggers replay. The fingerprint also folds in the fingerprints of every upstream primary entry whose output prefix appears in from, so upstream behavior edits (e.g. cod_type on an add_cods primary) cascade into derived replay automatically.

The derived entry runs in registration order during phase-2 sync, so any primary registrations whose output columns it references MUST be registered BEFORE this call.

Usage

RegistryStudy$register_derived_codes(codes, from, as)

Arguments

codes

Named list. Keys name the output columns' suffixes; the pattern values are ignored at apply time.

from

Character vector of source prefixes (e.g. c("os", "dorsu", "dorsm")).

as

Character scalar: the output column prefix.

The call STOPS when the entry would generate a column an already-registered entry generates. Every entry owns its output columns alone.


RegistryStudy$apply_codes_to_skeleton()

Apply all registered codes to a skeleton data.table. Thin loop over self$code_registry that delegates per-entry work to the file-level .apply_code_entry_impl() helper. Kept for backwards-compatible "apply everything at once" callers; the incremental code-registry sync inside the Skeleton R6 class calls .apply_code_entry_impl() directly on one entry at a time.

The method reserves the column slots the whole registry needs, then applies each entry. It writes the result back to skeleton where the caller passed a variable. Where the caller passed an expression there is no binding to write to, so use the return value.

Usage

RegistryStudy$apply_codes_to_skeleton(skeleton, batch_data)

Arguments

skeleton

data.table. The person-week skeleton to write to.

batch_data

Named list of data.tables from load_rawbatch().

Returns

The skeleton, invisibly. It is the same object the caller passed while that object had a free column slot for every new column, and a new object otherwise.


RegistryStudy$set_ids()

Set IDs and split into batches.

Usage

RegistryStudy$set_ids(ids)

Arguments

ids

Vector of person IDs.


RegistryStudy$set_batches()

Set the batch composition directly, one ID vector per batch, in the given order. Use it to keep a matched set in one batch, which $set_ids() cannot do because it shuffles the IDs.

An ID MAY be in more than one batch. $save_rawbatch() then writes every row of that ID into each batch that holds it. The population and summary counts sum over the batches, so they count a person in two batches twice.

The method stops when rawbatch or skeleton files are on disk and the new composition differs from the recorded one. A study with no recorded composition, for example one saved by swereg 27.2.6, records the new one and continues.

Usage

RegistryStudy$set_batches(batches)

Arguments

batches

A non-empty list of non-empty vectors of person IDs. An ID MUST NOT repeat within one batch. The batches get the names "1" to "n", as $set_ids() gives them. The method does not shuffle the IDs.

Returns

invisible(self).


RegistryStudy$set_matched_sets()

Set the batch composition from a roster of matched sets. Each set stays whole in one batch. Each membership gets its own skeleton ID and its own copy of the person's rawbatch rows.

The method packs the sets into batches in order of first appearance of set_id, sets_per_batch sets per batch. The batches hold the skeleton_id values of their roster rows, as $set_batches() would set them. The guard of $set_batches() applies, and the roster is part of the recorded composition. So a changed roster stops while rawbatch or skeleton files are on disk.

$save_rawbatch() then writes each rawbatch row of a person once for each skeleton_id that person has in the batch. The copy carries that skeleton_id in the ID column, and its other columns do not change. $save_rawbatch() also writes the roster rows of each batch as the rawbatch group "roster", with the person ID and the skeleton_id unchanged. Every phase function reads them as batch_data[["roster"]].

A roster where every skeleton_id equals the person ID gives the same rawbatch files as $set_batches() on the same IDs, plus the "roster" files.

Usage

RegistryStudy$set_matched_sets(roster, sets_per_batch)

Arguments

roster

A data.table with one row per membership and the columns id_col (the person ID in the rawbatch data), set_id, role (character) and skeleton_id. One skeleton_id MUST map to one person ID. A skeleton_id MUST NOT repeat within one batch, and MAY repeat across batches. Other columns go into the "roster" files unchanged.

sets_per_batch

A positive whole number. The number of sets in each batch. The last batch MAY hold fewer.

Returns

invisible(self).


RegistryStudy$load_skeleton_meta()

Read the meta_%05d.qs2 sidecar for one batch. Returns NULL if missing or unreadable (treated as cache miss by the fast path in .process_one_batch()).

Usage

RegistryStudy$load_skeleton_meta(batch_number)

Arguments

batch_number

Integer batch index.

Returns

A list (the meta payload) or NULL.


RegistryStudy$skeleton_meta_path()

Filesystem path of a meta sidecar.

Usage

RegistryStudy$skeleton_meta_path(batch_number)

Arguments

batch_number

Integer batch index.

Returns

Character. The full path.


RegistryStudy$skeleton_pipeline_hashes()

Summary of per-batch pipeline hashes across all currently-persisted skeleton files in self$data_skeleton_dir. Use this to spot batches out of sync with each other or with self$pipeline_identity().

Files that are not valid Skeleton R6 objects (e.g. unreadable or corrupted) surface as rows with NA identity_hash, NA framework_fn_hash, NA trim_fn_hash and NA phase_order.

Usage

RegistryStudy$skeleton_pipeline_hashes(files = NULL)

Arguments

files

Optional character vector of skeleton file paths. When NULL (default) every skeleton_NNNNN.qs2 in self$data_skeleton_dir is read. Pass the subset a consumer is about to read to scope the answer to those files.

Returns

A data.table with columns: batch, identity_hash, identity, framework_fn_hash, trim_fn_hash, phase_order, n_randvars, n_code_entries, saved_at. identity is a list column holding each batch's normalized pipeline identity, so a caller can name the component that differs rather than quote a digest. phase_order is the stored character vector collapsed with " -> ", so one batch is one row.


RegistryStudy$assert_skeletons_consistent()

Assert that every persisted skeleton file has the same pipeline identity AND that it matches this study's current pipeline identity. Errors loudly with an actionable message if not, naming the component that differs.

Runs as the pre-flight check inside tteplan_from_spec_and_registrystudy(), so partial-rebuild stragglers or config drift never silently flow into a TTE plan.

Usage

RegistryStudy$assert_skeletons_consistent(files = NULL)

Arguments

files

Optional character vector of skeleton file paths to check. NULL (default) checks every skeleton in the store. A consumer reading a subset SHOULD pass that subset, so the answer describes what it will read.

Returns

The single identity hash on success, invisibly.


RegistryStudy$process_skeletons()

Orchestrate the skeleton pipeline per batch.

Reads self$framework_fn (phase 1), self$trim_fn (phase 1b), self$code_registry (phase 2), and self$randvars_fns (phase 3) from the study. Applies them via the incremental logic on Skeleton. Exact per-batch work:

  1. Load existing skeleton via self$load_skeleton(i). Rebuild the base from scratch when the file is missing. Rebuild it too when framework_fn_hash, trim_fn_hash or phase_order does not match the study's current identity. A rebuild calls self$framework_fn(batch_data, self), wraps the result in a fresh Skeleton, and resets phases 2 and 3. (Phase 1.) 1b. On a rebuild only, call self$trim_fn(sk$data, batch_data, self) and rebind sk$data to what it returns. Skipped when no trim is registered. (Phase 1b.)

  2. Call sk$sync_with_registry() with self$code_registry_fingerprints(). Entries present on disk but not in the current registry are dropped (via .entry_columns() on the stored descriptor); entries present in the current registry but not on disk are applied fresh. (Phase 2.)

  3. Call sk$sync_randvars() with the current ordered self$randvars_fns and their body/formals hashes. Divergence- point rewind-and-replay semantics drop and re-run the affected phase-3 steps only. A step MAY read a phase-2 column, because phase 2 already ran. (Phase 3.)

  4. Save via self$save_skeleton(sk).

batch_data is loaded lazily – exactly once per batch, by whichever phase needs it first. If no phase needs it (everything already in sync), the rawbatch read is skipped entirely and the per-batch work is just load then save.

Usage

RegistryStudy$process_skeletons(
  batches = NULL,
  n_workers = default_n_workers("skeleton"),
  ...
)

Arguments

batches

Integer vector of batch indices to process, or NULL (default) for all batches in self$batch_id_list.

n_workers

Integer. Number of parallel workers (1 = sequential). When > 1, each batch runs in a fresh worker subprocess via the generic batch runner.

...

Additional arguments (unused; reserved for future use).

Returns

invisible(self).


RegistryStudy$clone()

The objects of this class are cloneable with this method.

Usage

RegistryStudy$clone(deep = FALSE)

Arguments

deep

Whether to make a deep clone.

Examples

if (FALSE) { # \dontrun{
study <- RegistryStudy$new(
  data_rawbatch_dir = c("/linux/.../rawbatch/", "C:/win/.../rawbatch/"),
  data_skeleton_dir = c("/linux/.../skeleton/", "C:/win/.../skeleton/"),
  data_raw_dir      = c("/linux/.../raw/",      "C:/win/.../raw/"),
  group_names = c("lmed", "inpatient", "outpatient", "cancer", "dors")
)

# Phase 1: framework (structural time grid + censoring)
study$register_framework(my_framework_fn)

# Phase 1b: trim (the only place rows may be deleted)
study$register_trim(my_trim_fn)

# Phase 2: codes. Primary entries first, derived entries after.
study$register_codes(
  codes      = list(e11 = c("E11"), vte = c("I26", "I80")),
  fn         = swereg::add_diagnoses,
  groups     = list(ov = "outpatient", sv = "inpatient"),
  combine_as = "os"
)
study$register_codes(
  codes   = list(e11 = c("E11"), vte = c("I26", "I80")),
  fn      = swereg::add_cods,
  fn_args = list(cod_type = "underlying"),
  groups  = list(dorsu = "dors")
)
study$register_codes(
  codes   = list(e11 = c("E11"), vte = c("I26", "I80")),
  fn      = swereg::add_cods,
  fn_args = list(cod_type = "multiple"),
  groups  = list(dorsm = "dors")
)
# Build osd_e11 = os_e11 | dorsu_e11 | dorsm_e11 (same codes list
# shared by reference so an edit in one place cascades to all four)
study$register_derived_codes(
  codes = list(e11 = c("E11"), vte = c("I26", "I80")),
  from  = c("os", "dorsu", "dorsm"),
  as    = "osd"
)

# Phase 3: randvars (ordered user steps; order = execution order).
# They run after the code registry, so they may read its columns.
study$register_randvars("demographics", my_demographics_fn)
study$register_randvars("exposure",     my_exposure_fn)

study$set_ids(ids)
study$save_rawbatch("lmed", lmed_data)
study$describe_codes()
study$process_skeletons(n_workers = 4L)

# Per-batch provenance and cross-batch consistency check
sk <- study$load_skeleton(1L)
identical(sk$pipeline_identity(), study$pipeline_identity())  # FALSE => stale
study$assert_skeletons_consistent()          # errors on mixed state
} # }