Module and Component Code Coherence#
Status: completed on 2026-09-27.
Purpose#
This document turns the current software design contracts into an ordered coherence programme for existing code. Its aim is not to make every module look identical. It is to ensure that every component states the same facts in its runtime behavior, dependency contract, public metadata, tests, and generated documentation.
The normative authoring rules remain in the module author guide and the contribution checklist. This note records deviations found in the current module set and the recommended order for resolving them.
Audit scope and baseline#
The current audit surface covers all 36 public ProcessStep classes exported through
modacor.modules, their module-layer helpers, their generated reference pages,
and focused module/runtime dependency tests. The deliberately non-discoverable
deprecated XSGeometry step was not treated as part of the public surface.
The following checks were made:
package placement and dependency direction;
ProcessStepDescriberconfiguration and public metadata;exact
source_refs,processing_reads, andprocessing_writesbehavior;authoritative in-place mutation and
produced_outputsbookkeeping;Pint conversion and
BaseDataunit/uncertainty propagation;semantic validation and use of
attrswhere a class owns validation; andagreement between implementation, tests, generated reference pages, and prose documentation.
At the audit baseline, the focused command
.venv-dev/bin/python -m pytest -q tests/modules tests/server/test_execution.py
passed all 280 tests. The findings below are therefore primarily uncovered contract gaps, not a list of failing tests. Each fix must add a regression test that fails before the fix; a passing legacy suite is not sufficient evidence that the contract is complete.
Implementation progress#
C1 completed on 2026-09-27:
FindScaleFactor1Dnow converts a copied working signal, including all named uncertainties, to the reference signal units before fitting. Compatible-unit and incompatible-unit regression tests protect the behavior without mutating the input signal.C2 completed on 2026-09-27:
IndexedAveragernow honorsoutput_processing_keyfor a single input, rejects its ambiguous use with multiple inputs, preserves the source bundle in distinct-output mode, and retains auxiliary entries in in-place mode. Its dependency contract now declares exact input reads and output-mode-specific writes.C3 completed on 2026-09-27:
Integrate1Dnow declares exact signal, axis, and optional mask reads plus output-mode-specific writes, including newly created output bundles.C4 completed on 2026-09-27: source contracts now include flat-plate transmission uncertainty sources and the plain source identifiers in NeXus detector-frame and sample-position mappings.
C5 completed on 2026-09-27:
ApplyMaskconverts non-uint32masks only into a local working array and no longer mutates its declared read-only mask.Phases 1 and 3 completed on 2026-09-27: executable guardrails now cover every exported step, generated-reference discovery, exact contract values, and the absence of runtime assertions. All identified wildcard dependency contracts were narrowed to the paths actually read or written.
Phase 4 completed on 2026-09-27: default required keys, modified keys, calling identifiers, return annotations, and argument descriptions now agree with the current public implementations. The generated module reference was rebuilt from the 36-step public surface.
Phase 5 completed on 2026-09-27: module input checks now raise explicit exceptions;
_EstimatorSpecis a validatedattrsclass; and the source and sink registration helpers no longer use mutable function defaults.Phase 6 completed on 2026-09-27: reusable quadrature, scale-fitting, planar attenuation, and polarization kernels now live under
modacor.models. TheirProcessStepadapters retain unit conversion,BaseDatahandling, dependency declarations, configuration, and pipeline mutation. An import guardrail protects the documentedmodelsandgeometryboundaries.
Contract hierarchy#
Four related contracts must remain distinct:
ProcessStepDescriber.required_data_keysandmodifiesdocument the public default behavior and drive generated reference material.dependency_contract()drives partial-rerun invalidation and must describe every actual external source read andProcessingDataread/write.Mutations made directly to
self.processing_dataare the authoritative execution result.The optional mapping returned by
calculate()is only a record of touched current bundles;execute()stores it asproduced_outputsand does not merge it into pipeline data.
A change is coherent only when all four views agree where they overlap.
Findings requiring behavioral correction#
These items can produce incorrect scientific values or incorrect partial-rerun selection and should be addressed before metadata cleanup or refactoring.
C1. FindScaleFactor1D dependent-unit handling (completed)#
At audit time, the module converted the independent axes to a common unit but fitted the raw magnitudes of the dependent signals. Physically equal signals expressed in metres and centimetres consequently produced a dimensionless scale of 100 instead of 1.
Implemented outcome:
require compatible dependent units;
convert the working signal and all its uncertainty components to the reference signal unit through
BaseData.to_units()before extracting arrays;keep the fitted scale dimensionless after conversion and the optional background in the reference signal unit; and
add compatible-unit and incompatible-unit regression tests.
C2. IndexedAverager advertised and actual output disagree (completed)#
At audit time, output_processing_key was accepted and changed the inherited
dependency contract, but calculation ignored it and replaced each selected
input bundle. The class documentation also said pixel_index and masks
remained present even though the replacement bundle contained only signal,
Q, and Psi.
Implemented outcome:
a distinct output is supported for one input and leaves the source bundle intact;
configuring one output for multiple inputs is rejected as ambiguous;
in-place operation retains auxiliary entries while replacing
signal,Q, andPsi; andthe dependency contract, return mapping, public metadata, and tests describe that behavior.
C3. Integrate1D does not declare new output bundles (completed)#
At audit time, configuring output_processing_keys created those bundles but
the inherited dependency contract declared only writes to the input bundles.
Implemented outcome: the custom contract declares exact signal, axis, and
optional mask reads, and declares either input.output_key writes or each new
output_processing_key.* write according to the configured mode.
C4. Incomplete external source tracking (completed)#
At audit time:
FlatPlateSelfAbsorptionCorrectionreadtransmission_uncertainties_sourcesbut omitted those references from its contract; andPixelCoordinates3DandXSGeometryFromPixelCoordinatesmissed plain source identifiers nested in NeXusdetector_frameandsample_z_overridedictionaries because the genericref::pathextractor cannot infer those fields.
Implemented outcome: the field-specific contracts include those source identifiers explicitly without making the generic extractor treat arbitrary configuration strings as source references.
C5. ApplyMask mutates a read-only dependency (completed)#
At audit time, the mask was declared as a processing read, but non-uint32
masks were converted and written back to the source BaseData. It is now
converted into a local working array without mutating the mask.
Dependency-contract precision (completed)#
At audit time, the following modules used whole-bundle invalidation where exact paths were available:
CopyDataBundleKeys;Divide,Multiply, andSubtract;DivideDatabundles,MultiplyDatabundles, andSubtractDatabundles;IndexPixels;PoissonUncertainties;SolidAngleCorrection;UnitsLabelUpdate;AttenuatorPlateCorrection; andDetectorEfficiencyCorrection.
These contracts now name exact source references and BaseData paths. The
two-bundle arithmetic steps use asymmetric contracts that write only their
first operand; configurable-key steps derive paths from their configuration;
and the material corrections name their signal, geometry, and correction-map
paths rather than invalidating whole bundles.
Focused tests assert equality for all three ProcessStepDependencies fields so
future wildcards and undeclared dependencies cannot pass as subsets.
Public metadata reconciliation (completed)#
At audit time, the generated module reference was incomplete or misleading in these areas:
AttenuatorPlateCorrection,DetectorEfficiencyCorrection,FlatPlateSelfAbsorptionCorrection, andPolarizationCorrectioncreate correction-map entries not listed inmodifies.IndexPixels,Integrate1D,DilateMask,ReduceMask, andThresholdMaskadd or replace entries while declaringmodifies={}.PoissonUncertaintiesdescribesvariances.Poisson, although it writessignal.uncertainties["Poisson"].UnitsLabelUpdateuses empty-string placeholders inrequired_data_keysandmodifies.FindScaleFactor1D,Integrate1D, andDivideDatabundlesunderstated their default required inputs.BitwiseOrMasks,Divide,Multiply, andSubtracthavecalling_idvalues different from the class names used by the registry and pipeline YAML.
The current descriptors now state default public behavior, while argument
documentation explains configurable keys. Empty strings are no longer used as
dynamic placeholders. If exact dynamic outputs become necessary for runtime
introspection, extend the descriptor with a deliberate machine-readable
mechanism instead of overloading modifies.
The reference pages were regenerated after the corrections. A discovery test
now requires the exports, discoverable ProcessStep implementations, generated
targets, filenames, and index entries to remain in agreement.
Validation and typing (completed for the current public surface)#
At audit time, runtime input validation relied on assert in ApplyMask,
BitwiseOrMasks, CopyDataBundleKeys, DilateMask, DivideDatabundles,
MultiplyDatabundles, ReduceMask, SubtractDatabundles, and
ThresholdMask. These checks now raise explicit TypeError or ValueError
exceptions. An AST guardrail rejects new runtime assertions in public modules.
Four internal carriers use Python’s built-in dataclasses:
ReduceDimensionality._EstimatorSpec;CapillarySelfAbsorptionCorrection._ResolvedSampleMu;material_attenuation.MaterialAttenuation; andstatistics.WeightedScatterEstimates.
_EstimatorSpec now owns its semantic constraints as an attrs class with a
converter and validators. The remaining three are passive internal result
carriers; migrate them only when they become validation boundaries or when
consistency materially simplifies the code. Do not perform a mechanical
dataclass-to-attrs rewrite without a contract benefit.
The documented return annotation is now present on every public calculate()
method, including PixelCoordinates3D, PoissonUncertainties, and
XSGeometryFromPixelCoordinates.
Units, uncertainties, and data containers#
Most scientific arithmetic already follows the intended design:
arithmetic corrections use
BaseDataoperators;geometry modules use Pint and
BaseDatafor unit conversion and uncertainty propagation at their adapter boundary; andreduction modules that require array kernels reconstruct explicit units, weights, axes, ranks, and named uncertainties.
The confirmed unit correctness defect is C1. UnitsLabelUpdate is an
intentional metadata repair tool, not a converter. Keep that limitation explicit
and never use it where BaseData.to_units() or a Pint quantity conversion is
required. Numeric thresholds in ThresholdMask are documented as magnitudes
in the source signal’s current units. An explicit threshold-unit configuration
remains necessary before cross-unit thresholds can be supported.
Package-boundary cleanup (completed for the audited candidates)#
The audit identified these reusable numerical or physical kernels:
Integrate1D._quadrature_weightsand the fit/interpolation kernel inFindScaleFactor1Dformodacor.models;the pure transmission and efficiency equations in
AttenuatorPlateCorrectionandDetectorEfficiencyCorrectionfor the attenuation models package; andthe linear polarization-factor equation in
PolarizationCorrectionfor a scattering model module.
They now reside in modacor.models.integration, modacor.models.scaling,
modacor.models.attenuation.planar, and
modacor.models.scattering.polarization, respectively. The ProcessStep
classes retain configuration interpretation, source resolution, BaseData
adaptation, dependency declarations, and mutation of ProcessingData. Pure
kernels do not import ProcessStep, BaseData, or I/O registries, and a
package-boundary test enforces that direction.
Recommended implementation sequence#
Phase 1: add executable guardrails (completed)#
Add regression tests for C1–C5 before changing behavior.
Add exact dependency-contract tests for every module touched in later phases.
Add lightweight consistency tests for public classes: non-empty class-name
calling_id, no empty metadata keys, validcalculate()return annotation, and public export/reference-page alignment.
These checks should report concrete class names rather than attempting to infer all reads and writes statically.
Phase 2: correct values and false invalidation claims (completed)#
Implement C1 through C5 in this order:
FindScaleFactor1Dunits (completed);IndexedAverageroutput semantics (completed);Integrate1Doutput dependencies (completed);missing external source references (completed); and
ApplyMaskmask mutation (completed).
Keep each behavioral correction in a focused change with its own tests. Do not combine these fixes with package moves.
Phase 3: make dependency contracts exact (completed)#
Repair fixed-key single-bundle arithmetic steps.
Repair asymmetric two-bundle operations and
CopyDataBundleKeys.Repair dynamic-output modules such as
IndexPixelsandUnitsLabelUpdate.Narrow the material-correction contracts.
Run the server partial-rerun tests after each group because an under-declared contract is a correctness error, while an over-declared contract is a performance and coherence defect.
Phase 4: reconcile public metadata and generated documentation (completed)#
Update required_data_keys, modifies, calling_id, argument descriptions,
and module notes. Regenerate all public module pages and ensure examples use the
class names accepted by ProcessStepRegistry.
Phase 5: harden validation and class contracts (completed)#
Replace runtime assertions, migrate _EstimatorSpec to validated attrs, add
missing return annotations, and remove mutable function defaults in
AppendSource and AppendSink while those files are being touched.
Phase 6: extract reusable kernels (completed)#
Move reusable numerical functions into models only after behavioral and
contract tests are stable. Keep module adapters thin and verify that package
imports still point in the documented direction.
Phase 7: final verification (completed)#
Final verification on 2026-09-27 produced these results:
the full test suite passed with 935 tests, one opt-in memory test skipped, and three established
BaseDatanumerical-domain warnings;repository-wide Ruff checks passed for
src,tests, andscripts;generated-reference discovery covered exactly all 36 exported steps and the generator pruned the obsolete analyser-angle page; and
Sphinx completed with
-E -W --keep-goingand no warnings.
Completion criteria#
This coherence programme is complete when:
every public step has an exact dependency contract or a documented reason for conservative wildcard behavior;
every nontrivial contract has an exact regression test;
Pint conversions and
BaseDataarithmetic protect all compatible-unit and uncertainty-bearing operations at module boundaries;public metadata and generated reference pages match default runtime behavior;
no runtime validation relies on
assert;validation-owning data classes use
attrs;all public
calculate()methods satisfy the mutation, return, and annotation contract;reusable pure kernels reside below the module adapter layer; and
the full tests, lint checks, and documentation build pass.