Skip to content
Development documentation. The PyPI package predates these APIs. Install from GitHub instead: pip install 'increment @ git+https://github.com/kylejcaron/increment.git' Keep any extras requested by the guide, such as increment[dashboard].

Pre-1.0 compatibility

Increment is alpha software. This section says what may change between prereleases, what is written to disk or a warehouse, and how to migrate. It is a software-compatibility policy, not scientific certification: a stable interface says nothing about whether a method’s assumptions hold for your experiment. Method assumptions and known gaps live in Statistical limitations, whose path table remains the single statement of what runs where.

These are the interfaces this policy covers:

  • documented root-package workflow and configuration imports (from increment import ...);
  • the documented receive-only types in increment.results and the solvers in increment.power;
  • result schemas: field names, types, row sets, null reasons and the reference/guarantee labels that qualify an interval;
  • stable refusal codes and the named fields of their context;
  • each versioned portable or artifact format listed below.

Underscore modules and private research kernels are not public APIs and may change without notice.

  • Pin the exact prerelease and the versions of its dependencies (Narwhals, Ibis, Arrow, DuckDB, pandas/Polars as used) for any decision you need to reproduce.
  • Breaking changes are permitted between 0.x prereleases. Each is listed in the changelog with the affected paths and a migration route. Nothing here implies 1.0 stability or a deprecation window.
  • A released bug fix may change a numerical answer that was wrong. The changelog names the correction; the incorrect value is not preserved for compatibility.
  • A refusal code’s meaning is never reassigned to a different condition. Message prose is not stable; branch on .code and the context fields.
  • A code that is retired keeps its entry in increment.errors.RETIRED_CODES, which maps it to its replacement code, a tuple of replacements when it split, or None when it was removed with no replacement. Retiring a code is a contract change and is listed in the changelog.
  • Consumers of results must preserve numeric nulls and the reason, guarantee and reference metadata that accompany a number. Dropping a null reason or reading a value without its reference label changes what the number claims.
  • Removing a result field or changing its type requires an explicit migration notice.

Moments cubes. Analysis.export writes a moments_format stamp on every row; Analysis.from_moments reads it. There is one format number per kind of writer:

FormatWritten byRead by from_moments
7no longer writtenyes (fixed horizon)
8fixed-horizon exportyes
9registered sequential export (one typed checkpoint envelope)yes
below 7no longer writtenrefused, moments.format.unsupported_legacy
above 9not written by this releaserefused, moments.format.unsupported_future

A cube must use one format (moments.format.mixed) and repeat no (metric, group_id) row (moments.rows.duplicate). Format 8 files from a sequential analysis cannot resume the sequential process: they are refused with sequential.continuation.legacy. Re-export from the raw definitions with the current release, or pin the release that wrote the file. Nothing is downgraded or filled in silently.

Unit-day artifacts. Artifact context format 2 is the only supported context. It carries no fact, dimension or exposure SQL; source recipes are represented by domain-tagged SHA-256 digests. Opening or publishing with a format-1 context is refused with artifact.format.unsupported, which names the received and supported versions. The refusal does not upgrade an old artifact: republish from trusted definitions. Copies of a format-1 manifest that you already stored or shared still contain the SQL they were written with. See Canonical unit-day artifacts.

Republish and re-register after the window-day and label fixes. An artifact context now binds window_days, the boundary-local day of each declared window edge. A context without it is refused with artifact.context.mismatch (missing window_days) before any manifest, relation or pin is used; republish from trusted definitions. A native sequential registration made before this change binds a different source recipe and refuses with sequential.source.invalid; register again. Frame registrations are unaffected. Artifacts published earlier also label Boolean breakout and factor values True/False; republishing yields true/false/__null__.

Names added in this release. increment.semantics.models.local_day and window_days; Experiment.start_day, Experiment.end_day and Experiment.observation_horizon_day; increment.query.artifact_contract.open_trusted_manifest_snapshot; WarehouseArtifactStore.abandon_generation(artifact_id, generation_id), which durably hides a generation that may or may not have a manifest row yet (safe to repeat); the roles= argument of increment.impute.pooled_mean. A pooled_mean call without roles= still fills but now emits the impute.pooled_mean_role_undeclared warning.

Site-volume coverage ends at end. An experiment that declares observation_end after end now publishes site-volume coverage through end, the window its rows and the estimand use, instead of through observation_end. Its context and site-volume extension digests change, so republish such artifacts. Experiments whose observation_end is not on a later local day than end are byte-identical.

New refusal codes. query.session.warehouse_artifact.publication_cleanup_incomplete (relations could not be dropped before the manifest insert began) and query.session.warehouse_artifact.publication_state_unknown (cleanup was incomplete after it began: the tombstone could not be written, or relation erasure failed after it was) are raised when a publication block exits normally without a manifest and cleanup could not finish. A propagating error is re-raised unchanged with a note instead. Both codes carry the qualified relation names and a recovery route in their context.

Sequential checkpoints and segment labels. A saved checkpoint is evidence for one monitoring process and is never relabelled or rehashed. Frame segment labels are canonical strings: true/false for Booleans and __null__ for missing values. A checkpoint captured under an earlier spelling (True, None) still replays unchanged, but continuing it from a source that labels the same units canonically is refused with sequential.continuation.rewrite. That refusal is not permission to restart monitoring and spend alpha again: keep the earlier checkpoint as the record, or register a new protocol you can defend. See Sequential inference.

Encodings are format-specific. Aliases, tuple encoding, collection order, duplicate rejection and which values are runtime-only are defined by each format’s own contract, not by a general promise that a value is “JSON compatible”.

A digest proves that bytes match the encoding that was hashed. It does not prove who wrote them or that a result is statistically valid, and it does not hide a secret that can be guessed. The caller-pinned reference and the store’s own authorization are the authenticity boundary for artifacts.

Portable files (moments parquet/rows, JSON, artifacts) are the interchange formats above. Python pickle is not: it is a trusted-input-only convenience for objects you produced yourself, and carries no versioned-interchange guarantee across releases. Never unpickle data from an untrusted source.

TabularPolicy accepts any hashable context key, but only string-keyed tables roundtrip through JSON. A table with any other key type refuses at dump time with logged_policy.policy.json_context_key, rather than have JSON turn 1 into "1" and change the policy. Persist typed keys with model_dump(mode="python") or trusted pickle. The built-in integer-keyed TARGET_POLICY_V1 is in the Python-only group.

from increment import TabularPolicy
policy = TabularPolicy(
policy_id="p",
version="v1",
probabilities={"web": {"A": 0.5, "B": 0.5}},
default={"A": 1.0},
)
assert TabularPolicy.model_validate_json(policy.model_dump_json()) == policy
typed = TabularPolicy(
policy_id="p", version="v2", probabilities={1: {"A": 1.0}}, default={"A": 1.0}
)
assert TabularPolicy.model_validate(typed.model_dump(mode="python")) == typed
try:
typed.model_dump_json()
except Exception as exc:
assert exc.code == "logged_policy.policy.json_context_key"

See Persisting a TabularPolicy.