Signalise

module

Data

The data engine. It aggregates raw telemetry into per-minute statistics, encodes those into the vectors the world model consumes, curates them, and publishes every dataset — training, validation, testing and runtime — through the same code. Size varies; the path does not.

The boundary

Data owns the canonical encoding surface: no module may define a competing encoding, and downstream modules load datasets rather than constructing producers. It sits below the model and control layers and imports none of them — enforced by tests/data/test_boundaries.py and tests/data/test_no_competing_encoding.py.

Decisions on record (18)

Public surface

MinuteMeanWindowaggregate.py · 5 public methods

as_table · clear · covers · extend · note_range

SignaliseDatasetdatasets.py · 3 public methods

feature_layout · manifest · split_indices

SequenceDatasetdatasets.py · 3 public methods

feature_layout · split_indices · window

FlowsheetCompilerflowsheet.py · 4 public methods

build_geojson · build_geojson_from_surface · from_environment · from_rows

FlowsheetSurfaceFileContractio.py · 2 public methods

read · write

TelemetryFileContractio.py · 2 public methods

read · write

RuntimeBundlemain.py · 3 public methods

actions · observations · rewards

DataModulemain.py · 21 public methods

begin_cycle · build_dataset_artifact · build_recent_steps · build_runtime_bundle · build_steps · canonical_step_interval_seconds · compile_flowsheet · export_flowsheet_surface · export_telemetry · feature_layout · from_environment · get_dataset · get_steps · import_flowsheet_surface · import_telemetry · layout_fingerprint_or_empty · list_datasets · maintain_datasets · maintenance_interval_seconds · node_operations · observe

WindowAnchorobservation.py · 1 public methods

measured

MembershipRowoperation_membership.py · 3 public methods

disagrees · unattributed · writable

MembershipReportoperation_membership.py · 4 public methods

disagreements · unattributed · verified · writable

DatasetQualityquality.py · 6 public methods

as_dict · concerns · excited · is_uniform · out_of_span · usable

StepStorestep_store.py · 2 public methods

build_steps · get_steps

Commands

signalise data build-dataset signalise data build-steps signalise data datasets signalise data feature-layout signalise data flowsheet signalise data inspect signalise data operations signalise data publish-datasets signalise data runtime-window signalise data steps signalise data telemetry

Enforced invariants (335)

The plant is reporting; one actuator's tag is not. That step is not complete.

test_a_dropped_action_tag_is_not_reported_as_a_complete_step

0.0 is a fabricated extreme in engineering units, not a neutral 'no reading'.

test_a_dropped_action_falls_back_to_its_nominal_not_to_zero

A closed valve reads 0.0. That is a measurement, not an absence.

test_a_genuine_zero_reading_is_kept_and_not_treated_as_missing

returns one step per minute

test_returns_one_step_per_minute

step ids are unique

test_step_ids_are_unique

step start iso strings

test_step_start_iso_strings

step end one minute after start

test_step_end_one_minute_after_start

plant id in every step

test_plant_id_in_every_step

flowsheet fingerprint consistent

test_flowsheet_fingerprint_consistent

encode_chunk v2.0.0 must not include an observation_layout key.

test_no_observation_layout_key

observation values is float list

test_observation_values_is_float_list

action values is float list

test_action_values_is_float_list

reward values is float list

test_reward_values_is_float_list

Spec 17: a constrained CV's dispersion is an ORDINARY observation dimension.

test_observation_values_is_base_dims_then_one_dispersion_dim_per_constrained_cv

`cv_ids` must be a subset of the observation vector — `std_index` indexes the PREDICTED

test_a_constrained_cv_is_an_observation_node_carrying_a_limit

missing count is int

test_missing_count_is_int

A minute with no telemetry is missing every node it needed, not just its observations.

test_missing_count_all_missing_when_no_telemetry

missing count zero when all present

test_missing_count_zero_when_all_present

Every node the step is built from counts — observations, actions AND rewards.

test_missing_count_partial

normalise ts trims to minute

test_normalise_ts_trims_to_minute

normalise ts distinct minutes

test_normalise_ts_distinct_minutes

Bulk (build) mode and runtime (serve) mode encode the same surface → identical fingerprint.

test_build_and_serve_produce_identical_fingerprint

Two independent Encoders over the same surface pin the same id orders (no ordering nondeterminism).

test_fingerprint_is_stable_across_encoder_instances

A different flowsheet must yield a different fingerprint (skew is detectable, not masked).

test_fingerprint_changes_when_flowsheet_changes

One write per node per backfill chunk. This is what churned the identity mid-run.

test_the_satellites_resume_cursor_is_not_the_flowsheet

a finished backfill is not the flowsheet

test_a_finished_backfill_is_not_the_flowsheet

A per-run historian URL and a controller mode say where data comes from, not how it encodes.

test_the_edges_coordinates_and_mode_are_not_the_flowsheet

touching a row is not changing it

test_touching_a_row_is_not_changing_it

a node becoming an action changes the flowsheet

test_a_node_becoming_an_action_changes_the_flowsheet

Amends ADR 0063, by ADR 0064: geometry is presentation, and the encoder cannot see it.

test_moving_equipment_does_not_change_the_ENCODING

The map's own freshness is a different question, and it keeps its own key — otherwise the

test_moving_equipment_DOES_change_the_drawing

Membership is a stored column since ADR 0062 and decides which operation a signal reaches.

test_reattributing_an_entity_changes_the_flowsheet

remapping a signal to another entity changes the flowsheet

test_remapping_a_signal_to_another_entity_changes_the_flowsheet

adding a node changes the flowsheet

test_adding_a_node_changes_the_flowsheet

the surface keeps every field the encoder reads

test_the_surface_keeps_every_field_the_encoder_reads

config hash is deterministic

test_config_hash_is_deterministic

config hash length

test_config_hash_length

config hash default is stable

test_config_hash_default_is_stable

encoder config defaults

test_encoder_config_defaults

`EncoderConfig` held exactly one thing: the colour ramp's clip (ADR 0064 deleted it).

test_the_config_carries_nothing_the_encoder_still_needs

nominal centered anchors

test_nominal_centered_anchors

clamps out of range

test_clamps_out_of_range

vector uses per index bounds and fallbacks

test_vector_uses_per_index_bounds_and_fallbacks

denormalize ∘ normalize == identity (within the bounds). Planner works in normalized space;

test_denormalize_is_inverse

The planner's setpoint recovery must be the shared function — no competing inverse.

test_planner_dispatch_uses_canonical_denormalize

The autopilot's runtime normalizer must be the same function — no competing encoding.

test_autopilot_runtime_delegates_to_canonical

material internal when both endpoints in one op

test_material_internal_when_both_endpoints_in_one_op

material transfer src to dst

test_material_transfer_src_to_dst

material plant input when src outside

test_material_plant_input_when_src_outside

material plant output when dst outside

test_material_plant_output_when_dst_outside

disjoint operations do not overlap

test_disjoint_operations_do_not_overlap

overlapping operations detected

test_overlapping_operations_detected

nested operations detected as overlap

test_nested_operations_detected_as_overlap

every reward is operation attributed

test_every_reward_is_operation_attributed

action features exist and are attributed

test_action_features_exist_and_are_attributed

operation of uses entity not node

test_operation_of_uses_entity_not_node

The decode block carries the STORED membership (ADR 0062) — the column, not the geometry.

test_operation_membership_block_reports_what_the_rows_say

The point of the column: absent membership is absent, not quietly filled in from geometry.

test_an_entity_with_no_stored_operation_attributes_to_nothing

D2: geometry proposes. Cleared columns change the record and leave the proposal untouched.

test_the_geometry_is_still_available_as_a_proposal

The property the column exists for: a polygon is a drawing, not a claim about the process.

test_moving_the_geometry_does_not_move_the_membership

The other half of D2: the proposal is the geometry's opinion, so it must track the drawing.

test_the_proposal_does_move_with_the_geometry

re identified rows produce the same ordered signals

test_re_identified_rows_produce_the_same_ordered_signals

The whole point: a clone or a restore must not invalidate a trained model.

test_the_fingerprint_survives_re_identification

The entity index is the primary sort key, and it used to come from raw DB row order.

test_entity_order_does_not_depend_on_row_order

node row order does not move a dimension

test_node_row_order_does_not_move_a_dimension

A tie on node name is broken by the controller, not by row identity.

test_mirrored_tag_names_stay_distinguishable

mirrored tag names order deterministically under row shuffle

test_mirrored_tag_names_order_deterministically_under_row_shuffle

Keyed on node name ALONE these two distinct surfaces hash identically — the controller half

test_the_fingerprint_separates_surfaces_that_share_a_node_name

Same tag names, different DCS attribution → a genuinely different surface.

test_swapping_the_controllers_changes_the_fingerprint

Same node name AND same controller: the key cannot identify the surface, so refuse rather

test_a_genuinely_ambiguous_surface_is_refused

This used to raise: a degree-scale polygon rastered to ~5e10 voxel cells at 0.5 m, so the

test_an_oversized_entity_is_no_longer_a_problem_to_guard

The guard must not fire on real plant geometry — an 11 m vessel is fine.

test_a_realistic_entity_still_encodes

the geometry does not propose an operation for the plant

test_the_geometry_does_not_propose_an_operation_for_the_plant

the plant keeps its signals

test_the_plant_keeps_its_signals

plant scope is read off the class not the name

test_plant_scope_is_read_off_the_class_not_the_name

the class comparison ignores case and padding

test_the_class_comparison_ignores_case_and_padding

`class DataEncoder` is defined exactly once, and that one definition is in signalise.data.encoder.

test_exactly_one_encoder_implementation_and_it_is_the_shared_library

The Data job (bulk mode) and the autopilot kernel (runtime mode) both import signalise.data.encoder —

test_both_a_job_and_the_kernel_import_the_shared_encoder

a healthy dataset has nothing to say

test_a_healthy_dataset_has_nothing_to_say

Not a warning. A constant action column carries no information about that action, so nothing

test_a_lever_that_never_moved_makes_the_dataset_unusable

a constant observation is named

test_a_constant_observation_is_named

The stockpile case: it varied — instrument noise guarantees that — and still said nothing,

test_a_channel_parked_against_a_limit_is_flagged_but_not_fatal

These land in a dataset's stored quality record, so an operator reading it later sees the

test_the_measures_survive_the_round_trip_into_a_record

an empty dataset is still unusable and says nothing false

test_an_empty_dataset_is_still_unusable_and_says_nothing_false

A transmitter cannot report past its own span — anything beyond it is a fault, not a value.

test_a_reading_outside_its_instruments_span_is_reported_as_a_fault

A PV tracking an SP overshoots. That is control, not a fault, and must not be flagged.

test_a_controller_overshooting_its_setpoint_range_is_not_an_instrument_fault

The declared graph is the plant's circuit.

test_a_material_wired_between_two_units_is_accepted

An edge to nowhere would leave a unit unreachable and silently unsimulated.

test_a_material_naming_equipment_that_is_not_there_is_refused

The pair plus the equipment they share is the loop.

test_a_setpoint_closing_on_a_measurement_is_accepted

A loop closes on a pv; anything else is a mis-declared loop, not a loop.

test_a_setpoint_closing_on_a_node_that_is_not_a_measurement_is_refused

A loop that commands one unit and measures another is a description error.

test_a_setpoint_closing_on_another_units_measurement_is_refused

Presence is not required yet — Telfer's circuit is not described. Consistency is.

test_a_flowsheet_that_declares_neither_is_still_accepted

ADR 0097 D2 — aggregation is derived from the quantity, so the quantity must say.

test_every_unit_of_measure_says_whether_it_is_extensive

a resolvable source is accepted

test_a_resolvable_source_is_accepted

a source that names something absent is refused

test_a_source_that_names_something_absent_is_refused

An operator fixing a 191-node flowsheet needs to know which node and which reference.

test_the_refusal_names_the_node_and_what_it_could_not_find

a continuously reported reward gets a dispersion channel

test_a_continuously_reported_reward_gets_a_dispersion_channel

A 12-minute analyser cannot vary inside a 1-minute step, so its spread is always zero.

test_a_reward_reported_slower_than_a_step_gets_none

Faster than the step still varies within it; at or slower than the step, it cannot.

test_the_boundary_is_the_step_interval

A DCS block recomputes every scan, but it cannot report news its inputs have not delivered.

test_a_computed_tag_inherits_the_cadence_of_its_slowest_input

The loader reconstructs the model's input from the artifact alone — no database, no encoder.

test_a_published_artifact_loads_into_the_model_input

Splits are chronological ranges over one contiguous encode — disjoint, and covering the set.

test_the_splits_partition_the_set_without_overlap

The correctness reason for per-split artifacts, not merely a tidiness one.

test_a_per_split_artifact_cannot_leak_across_the_boundary

Published has to mean LOADABLE. The first live publish put arrays and a manifest in the bucket

test_a_split_artifact_is_self_contained

A window is a sample; a sample containing rows from two splits belongs to neither.

test_a_window_belongs_to_one_split_only

The load-bearing property: no dropped step is counted twice, and none disappears.

test_the_shares_sum_to_everything_that_was_dropped

a dropped step lands in the split whose range contains it

test_a_dropped_step_lands_in_the_split_whose_range_contains_it

Steps outside the surviving span still belong to a split — the first or the last.

test_the_edges_are_absorbed_not_lost

a clean window reports zero

test_a_clean_window_reports_zero

time weighted mean and dispersion

test_time_weighted_mean_and_dispersion

causal rolling zscore

test_causal_rolling_zscore

deferred identity resolution and orphan skip

test_deferred_identity_resolution_and_orphan_skip

inactive node excluded

test_inactive_node_excluded

gap minutes produce no row

test_gap_minutes_produce_no_row

window bounds half open

test_window_bounds_half_open

empty raw returns empty

test_empty_raw_returns_empty

incremental and full derivation produce identical rows

test_incremental_and_full_derivation_produce_identical_rows

`build_recent_steps` resumes from the newest built step, so consecutive builds overlap by the

test_overlapping_builds_still_use_the_window

Coverage keys on the RANGE aggregated, not on calendar minutes — an empty minute is not a

test_a_skipped_range_makes_the_next_build_re_derive

a cold window matches the full derivation

test_a_cold_window_matches_the_full_derivation

Bounded by TIME from the newest minute — trimming by count would evict minutes still inside

test_the_window_is_bounded_by_its_capacity

a null saturation hashes as if the column were not there

test_a_null_saturation_hashes_as_if_the_column_were_not_there

It changes what the planner optimises, so it is a different plant description.

test_declaring_a_saturation_point_does_move_the_fingerprint

two different knees are two different flowsheets

test_two_different_knees_are_two_different_flowsheets

data encoding surface imports no model or control module

test_data_encoding_surface_imports_no_model_or_control_module

The derivative must not be consulted about the source it is derived from.

test_bounds_never_ask_the_database

A label nothing reads, on an answer nothing should trust.

test_there_is_no_database_source_label

`unavailable` and `no_telemetry` must stay separate — only one of them is a fault.

test_an_unreadable_raw_store_is_distinct_from_a_cold_plant

The trainer's 90-day request against 28 days of telemetry.

test_a_window_wider_than_coverage_is_clamped_to_it

Clamping must never NARROW a legitimate request.

test_a_window_inside_coverage_is_left_alone

only the leading span is trimmed when the end is inside

test_only_the_leading_span_is_trimmed_when_the_end_is_inside

A plant with no partitions yet must not be silently clamped to nothing.

test_no_raw_coverage_leaves_the_request_untouched

Ordering is the point: the clamp must precede the chunk loop, not correct it afterwards.

test_build_steps_reads_the_bounds_before_walking_chunks

a node dynamics map is rekeyed onto the clones nodes

test_a_node_dynamics_map_is_rekeyed_onto_the_clones_nodes

The ids are already the target's, so there is nothing to remap. Rewriting them would be the

test_a_same_plant_carry_is_left_alone

A node the clone genuinely lacks must stay recognisable as unmatched, not be rewritten into a

test_an_id_with_no_counterpart_on_the_target_is_not_invented

Spec 17's dispersion channels are keyed `<node id>:std` and are what the excursion gate

test_a_derived_channel_key_carries_its_node_id_across

import flowsheet returns plant id

test_import_flowsheet_returns_plant_id

import flowsheet table counts

test_import_flowsheet_table_counts

compile flowsheet after import

test_compile_flowsheet_after_import

loaded telemetry covers window

test_loaded_telemetry_covers_window

import telemetry plant id

test_import_telemetry_plant_id

All 10080 steps are either written or skipped (idempotent total).

test_build_steps_all_steps_covered

build steps no missing

test_build_steps_no_missing

Re-running build_steps on already-current steps skips all of them.

test_build_steps_idempotent

get steps returns 10080

test_get_steps_returns_10080

step has required fields

test_step_has_required_fields

step plant ids correct

test_step_plant_ids_correct

encoder config hash consistent

test_encoder_config_hash_consistent

flowsheet fingerprint consistent

test_flowsheet_fingerprint_consistent

episode ids contain plant id

test_episode_ids_contain_plant_id

10 080 steps (7 days) at 360 steps/episode = 28 distinct episode IDs.

test_steps_span_multiple_episodes

build dataset returns id

test_build_dataset_returns_id

build dataset manifest fields

test_build_dataset_manifest_fields

build dataset split counts sum to steps

test_build_dataset_split_counts_sum_to_steps

build dataset split fractions

test_build_dataset_split_fractions

build dataset feature layout

test_build_dataset_feature_layout

build dataset fingerprint set

test_build_dataset_fingerprint_set

Second call returns the same dataset_id (fingerprint collision → same row).

test_build_dataset_idempotent

materialise returns artifact dir

test_materialise_returns_artifact_dir

materialise cache files present

test_materialise_cache_files_present

materialise array shapes

test_materialise_array_shapes

Observation values are signed intensities in [-1, 1].

test_materialise_obs_values_signed_intensity

materialise step meta length

test_materialise_step_meta_length

materialise signalise dataset loads

test_materialise_signalise_dataset_loads

materialise split indices cover all steps

test_materialise_split_indices_cover_all_steps

materialise dataloader batch shape

test_materialise_dataloader_batch_shape

train loader length

test_train_loader_length

val loader length

test_val_loader_length

test loader length

test_test_loader_length

all loader length

test_all_loader_length

train loader shuffle

test_train_loader_shuffle

val loader not shuffled

test_val_loader_not_shuffled

batch keys

test_batch_keys

batch shapes

test_batch_shapes

invalid split raises

test_invalid_split_raises

loads from cache

test_loads_from_cache

getitem keys

test_getitem_keys

getitem tensor shapes

test_getitem_tensor_shapes

getitem step meta

test_getitem_step_meta

getitem out of range

test_getitem_out_of_range

split indices train

test_split_indices_train

split indices val

test_split_indices_val

split indices validation alias

test_split_indices_validation_alias

split indices test

test_split_indices_test

split indices all

test_split_indices_all

split indices invalid

test_split_indices_invalid

feature layout node

test_feature_layout_node

manifest node

test_manifest_node

A missing artifact is an error, not a trigger to build one (ADR 0069 D3).

test_raises_when_the_artifact_is_not_there

is cached false when missing

test_is_cached_false_when_missing

the dispersion channels reach the array

test_the_dispersion_channels_reach_the_array

Steps encoded before the dispersion channels existed. Coercing them is how a 67-wide dataset

test_a_step_that_does_not_match_the_layout_is_refused_not_truncated

No CV carries a limit -> no dispersion block -> the vector is exactly the observation nodes.

test_a_plant_with_no_cvs_is_unaffected

incomplete steps are excluded

test_incomplete_steps_are_excluded

A dataset quietly losing rows is its own defect.

test_the_exclusion_count_is_returned_not_swallowed

the exclusion is logged

test_the_exclusion_is_logged

a fully complete window excludes nothing and stays quiet

test_a_fully_complete_window_excludes_nothing_and_stays_quiet

`_build_dataset` sizes the splits, `_materialise_dataset` writes the rows. A filter on one and

test_both_dataset_paths_use_the_shared_selector

an all incomplete window fails loudly rather than building nothing

test_an_all_incomplete_window_fails_loudly_rather_than_building_nothing

One day imported over a month of raw: refuse, and name what it would have cost.

test_refuses_to_purge_history_it_is_not_replacing

A clean-room arm means it — and now has to say so.

test_the_declared_exemption_lets_a_benchmark_arm_through

The throwaway benchmark plant: nothing to protect, so nothing to refuse.

test_a_fresh_plant_is_not_blocked

every constrained cv gets a contiguous dispersion dimension

test_every_constrained_cv_gets_a_contiguous_dispersion_dimension

The whole point of issues/0026: `_constrained_cvs` must find a real index, not None.

test_std_index_reaches_the_gate

Regression that was live for exactly one commit while writing this.

test_the_dispersion_block_moves_the_fingerprint

a flowsheet encodes to a step

test_a_flowsheet_encodes_to_a_step

The specific disagreement that was possible: the geojson publishes the order every consumer

test_the_published_layout_is_the_order_the_encoder_used

the signal layout and the encoder agree

test_the_signal_layout_and_the_encoder_agree

ADR 0064 deleted it. A lattice nobody reads is worth catching if it returns, because the last

test_the_geojson_no_longer_publishes_a_lattice

every entity id is remapped and scoped to the target

test_every_entity_id_is_remapped_and_scoped_to_the_target

references follow their targets

test_references_follow_their_targets

The walk replaces any string that IS a mapped id, so a reference this function has never

test_ids_nested_anywhere_are_rewritten_too

the mapping is deterministic so reimport is idempotent

test_the_mapping_is_deterministic_so_reimport_is_idempotent

a different target gets different ids

test_a_different_target_gets_different_ids

source plant users do not come along

test_source_plant_users_do_not_come_along

counts match per entity type

test_counts_match_per_entity_type

name sets match per entity type

test_name_sets_match_per_entity_type

signals compile to nodes

test_signals_compile_to_nodes

signal fks and semantics resolve

test_signal_fks_and_semantics_resolve

ids are minted uuids not source ids

test_ids_are_minted_uuids_not_source_ids

geometry roundtrips via cell

test_geometry_roundtrips_via_cell

blank template has null geometry

test_blank_template_has_null_geometry

validation collects pk and fk errors

test_validation_collects_pk_and_fk_errors

get steps pages past the 1000 cap

test_get_steps_pages_past_the_1000_cap

get steps exact multiple of cap

test_get_steps_exact_multiple_of_cap

get steps respects limit

test_get_steps_respects_limit

get steps empty

test_get_steps_empty

flowsheet read plain json

test_flowsheet_read_plain_json

Regression: .gz flowsheets must read without NameError (gzip import).

test_flowsheet_read_gzip_transparent

A document with a singular `plant` object is normalised into `plants`.

test_flowsheet_plant_singular_fallback

Export writes the single customer format: controller_name,node_id,value,source_timestamp.

test_telemetry_write_is_controller_node_keyed_csv

no module outside data computes the action encoding

test_no_module_outside_data_computes_the_action_encoding

The pair is kept together so it cannot drift; this is what 'cannot drift' has to mean.

test_the_canonical_map_and_its_inverse_round_trip

A refused window must not silently answer 'no layout change' — that disables the gates.

test_the_fingerprint_does_not_depend_on_the_step_window

an unreadable flowsheet is the only unknown

test_an_unreadable_flowsheet_is_the_only_unknown

180 steps is an invariant, not a default — a settings value must not undercut it.

test_the_window_is_floored_at_the_runtime_minimum

a larger window is honoured

test_a_larger_window_is_honoured

the window spans the requested minutes

test_the_window_spans_the_requested_minutes

The signature IS the contract. This took a `RuntimeSnapshot` and reached into it for another

test_it_takes_the_values_it_needs_not_the_kernel_snapshot

a dense window has no lag

test_a_dense_window_has_no_lag

Continuity, not wallclock recency — so one limit works for live AND compressed replay.

test_a_gappy_window_reports_the_gap_as_seconds

A satellite stopped six hours ago yields a window that slid back with it: still dense, lag 0.

test_wallclock_age_sees_a_dead_stream_that_continuity_cannot

wallclock age is never negative

test_wallclock_age_is_never_negative

no action targets is a mapping failure in autopilot

test_no_action_targets_is_a_mapping_failure_in_autopilot

Shadow writes no telecommand, so an unmapped action surface is not yet a defect.

test_no_action_targets_is_tolerated_in_shadow

a readable bound anchors the window to the plant

test_a_readable_bound_anchors_the_window_to_the_plant

a cold plant anchors to now and says so

test_a_cold_plant_anchors_to_now_and_says_so

The measured failure: the DB answered (None, None) while raw held data eight hours newer.

test_an_unreadable_bound_is_not_silently_treated_as_a_cold_plant

a raising bounds call is loud and unmeasured

test_a_raising_bounds_call_is_loud_and_unmeasured

an unparseable bound does not become wallclock

test_an_unparseable_bound_does_not_become_wallclock

`now` makes the age zero by construction — the most reassuring number, and meaningless.

test_freshness_is_withheld_when_the_anchor_was_assumed

freshness is reported when the anchor was measured

test_freshness_is_reported_when_the_anchor_was_measured

a fully sensed window says so

test_a_fully_sensed_window_says_so

A signal with no reading encodes to its declared NOMINAL, nominal standardises to z ~= 0, and

test_a_window_the_plant_never_measured_is_named_not_hidden

Normal plant life. The runtime has ~180 steps against the dataset's 43,000 and must act, so

test_one_sensor_dropping_out_is_partial_not_blind

an empty window is not reported as sensed

test_an_empty_window_is_not_reported_as_sensed

Downstream modules load datasets; they do not build the thing that makes them.

test_no_module_outside_data_constructs_the_producer

Training, validation, testing and runtime carry the same verdict fields, because 'the quality

test_every_split_is_rated_by_the_same_call

More than one encoder identity in a set means mixed vector orders — the ADR 0042 failure, at

test_a_mixed_encoding_identity_is_not_usable

Every value filled from nominal is a record of nothing — what the dataset builder already

test_a_fully_imputed_set_is_not_usable

an empty set is rated rather than crashing

test_an_empty_set_is_rated_rather_than_crashing

writes only what is missing

test_writes_only_what_is_missing

Geometry proposes; it does not override (D2). A disagreement is reported, not resolved.

test_a_stored_membership_is_never_overwritten

The correct answer, and the one that was previously invisible.

test_an_entity_the_geometry_places_nowhere_stays_null

verified only when every proposed membership is stored

test_verified_only_when_every_proposed_membership_is_stored

materials are written through their own rpc

test_materials_are_written_through_their_own_rpc

the backfill is idempotent

test_the_backfill_is_idempotent

rows without an id are skipped not written

test_rows_without_an_id_are_skipped_not_written

writable / disagreements / unattributed partition the rows that are not already correct.

test_report_properties_are_disjoint

the flowsheet is compiled once per cycle

test_the_flowsheet_is_compiled_once_per_cycle

a second plant gets its own surface

test_a_second_plant_gets_its_own_surface

the runtime bundle is built once per window

test_the_runtime_bundle_is_built_once_per_window

a different window is a different bundle

test_a_different_window_is_a_different_bundle

The memo must not outlive the cycle — a new module is a new cycle.

test_a_fresh_module_starts_cold

`source_timestamp` is a ROW-level predicate — pyarrow opens every file's metadata to evaluate

test_read_window_prunes_on_the_day_partition

The live drain writes one small object per poll; a day accrues thousands of ~3 KB files.

test_compaction_merges_drain_objects_and_preserves_every_row

Backfill files are already well-sized and are idempotently rewritten by replay — folding them

test_compaction_leaves_the_backfill_objects_alone

Nothing to merge must not rewrite the partition — a rewrite-for-nothing is pure risk.

test_compaction_is_a_no_op_below_two_objects

Merging in arbitrary order destroys timestamp locality, and a reader then cannot prune:

test_compaction_sorts_by_time_so_readers_can_prune

`source_timestamp` is a ROW-level predicate — pyarrow opens every file's metadata to evaluate

test_read_window_prunes_on_the_day_partition

a changed layout changes the step encoding identity

test_a_changed_layout_changes_the_step_encoding_identity

the same layout is stable

test_the_same_layout_is_stable

A caller that forgets the layout gets a DIFFERENT hash from one that supplies it — so the

test_the_layout_is_not_silently_optional_for_the_step_store

Regression on the shape of the wiring: an earlier version read the fingerprint off the

test_the_step_store_actually_receives_it

a step from the same flowsheet and encoder is current

test_a_step_from_the_same_flowsheet_and_encoder_is_current

The regression: same encoder hash, older flowsheet — it must be re-encoded, not skipped.

test_a_step_from_a_DIFFERENT_flowsheet_is_not_current

a step from a different encoder is still not current

test_a_step_from_a_different_encoder_is_still_not_current

a split window keeps only the steps that match both

test_a_split_window_keeps_only_the_steps_that_match_both

a row with no fingerprint is re encoded rather than assumed to match

test_a_row_with_no_fingerprint_is_re_encoded_rather_than_assumed_to_match

no fingerprint asked for falls back to the encoder hash alone

test_no_fingerprint_asked_for_falls_back_to_the_encoder_hash_alone

raises when steps written but zero telemetry

test_raises_when_steps_written_but_zero_telemetry

ok when no steps written

test_ok_when_no_steps_written

ok with full coverage

test_ok_with_full_coverage

warns but passes on low coverage

test_warns_but_passes_on_low_coverage

each protocol lands in its own prefix

test_each_protocol_lands_in_its_own_prefix

a window is read from both

test_a_window_is_read_from_both

ADR 0078 D2. The operation the ADR exists for: PI recalculated n1, and OPC's record of n2 is

test_a_resync_replaces_the_historian_and_leaves_opc_alone

ADR 0078 D5. A resync exists because the source produced a BETTER number; deferring to the

test_where_both_cover_a_minute_the_historian_wins

opc survives where the historian says nothing

test_opc_survives_where_the_historian_says_nothing

No `telemetry/pi` prefix exists at all — an absent source is not an error.

test_a_plant_that_never_synced_a_historian_reads_fine

A plant provisioned earlier has rows directly under `telemetry/`, with no subfolder. They are

test_telemetry_written_before_this_adr_is_still_read

The bug this nearly shipped with: reading the legacy root recurses INTO `telemetry/opc`, so

test_a_row_is_never_read_twice

the row count is exact across both protocols

test_the_row_count_is_exact_across_both_protocols

After build_steps, telemetry serves a per-node series over the built window —

test_build_steps_publishes_the_minute_tier

A single-node query returns the get_telemetry-compatible stats object the console reads:

test_the_minute_tier_single_node_contract

The rolling window stats use the SAME causal window functions as the retired get_telemetry —

test_the_minute_tier_causal_window_stats

The hand-written `__all__` and the map that resolves it cannot disagree.

test_all_agrees_with_the_export_map

Deferred resolution must not defer a failure: every advertised name really exists.

test_every_exported_name_resolves

`__getattr__` must not turn a typo into something other than AttributeError.

test_an_unknown_name_raises_attribute_error

The reason the surface is lazy at all — assert the benefit, not just the mechanism.

test_the_pure_function_does_not_drag_torch_in

It materialised on load when the cache missed — a LOADER creating the thing it loads. No

test_the_loader_takes_no_download_flag

the loader cannot reach the engine

test_the_loader_cannot_reach_the_engine

It called get_settings(scope="global", module="data"), which returns the VOCABULARY

test_from_environment_resolves_the_modules_own_settings

no module outside the engine builds a dataset

test_no_module_outside_the_engine_builds_a_dataset

`maintain_datasets` is the sequence. If it disappears, the spray comes back.

test_the_engine_exposes_one_maintenance_entry_point

A shipped flowsheet is the worked example; it must pass the gate it demonstrates.

test_a_shipped_flowsheet_declares_only_options_the_vocabulary_carries

A class exists because a plant can be described with it, not because it was listed.

test_every_simulated_equipment_class_is_reachable_from_a_flowsheet

ADR 0094 D4 — a unit carries its UCUM code, or states that it is not a physical unit.

test_units_declare_a_ucum_code_or_say_why_not

The unit must build over a bounded trailing window (step_build_window_days), NOT windowless

test_per_cycle_build_is_bounded_not_windowless

per cycle window falls back to now without bounds

test_per_cycle_window_falls_back_to_now_without_bounds

A telemetry-bounds lookup error must not abort the build — it falls back to `now`.

test_step_build_is_best_effort_on_bounds_error

step build window days default

test_step_build_window_days_default

step build window days from settings

test_step_build_window_days_from_settings

A bounded window still re-aggregated all 1,440 minutes of it every cycle to append one step.

test_build_resumes_from_the_newest_existing_step

No steps yet → there is nothing to resume from, so the full window is the correct work.

test_a_cold_plant_still_builds_the_whole_window

Resume is an optimisation — if the lookup fails the build must still run, over the full

test_resume_survives_a_get_steps_failure

A changed feature layout must rebuild the WHOLE window, not resume past the stale steps.

test_resume_does_not_skip_past_steps_encoded_under_a_different_layout

the cycle compacts the day it is about to encode

test_the_cycle_compacts_the_day_it_is_about_to_encode

compaction is bounded by its settings

test_compaction_is_bounded_by_its_settings

zero min objects stops compaction entirely

test_zero_min_objects_stops_compaction_entirely

a failed compaction does not stop the encode

test_a_failed_compaction_does_not_stop_the_encode

A backfill lands BEHIND the recent window, and the recent window never travels back to it.

test_raw_older_than_any_step_is_encoded

It walks back to where the history actually starts, not past it into empty time.

test_the_catch_up_stops_at_the_oldest_raw

Converges: when every raw sample is behind an encoded step, the extra build stops.

test_no_catch_up_once_the_backfill_is_covered

Sources

Generated from the repository. Every section is derived from an artifact the build enforces — ADR frontmatter, the architecture tests, the module's AST, its test docstrings, and the CLI's own help. Nothing here is hand-written prose, so nothing here can drift from the code without the code changing first.