honestclock
Point-in-time correctness for regulatory panel data. Separate what was knowable at a forecast date from what merely happened — and make the distinction a property of your types rather than a convention a reviewer has to check.
pip install honestclock
The problem
Records describing the same period arrive on different schedules.
In EPA's discharge-monitoring data, an effluent exceedance becomes visible a median of 20 days after the monitoring period, because the report that reveals it is the report that carries it. A missing report generates no row at all, and the non-compliance is not recorded for a median of 59 days.
Between those two dates, a facility whose compliance has already broken down still looks compliant to any filter written against the monitoring month.
The consequence is not a metric correction. It changes who is eligible: a pool that looks clean at the forecast date already contains failures. One dataset holds two prediction problems — detecting breakdowns already underway, and forecasting those still to come — and conflating them inflates what a precision number means.
What this package does
from honestclock import AsOfClock, PointInTimePanel, features as F
panel = PointInTimePanel(
df,
entity_keys=["permit_id", "parameter_code"],
reference_col="month", # the period the row describes
availability_col="received_at", # when the row became knowable
)
clock = AsOfClock("2025-07", lookback=9, horizon=3)
visible = panel.visible_at(clock) # cannot contain future data
labels = panel.labels_at(clock, "exceedance")
X = F.assemble(
visible,
F.reporting_gap(visible),
F.event_count(visible, "exceedance"),
F.value_stats(visible, "ratio", clip_upper=5.0),
)
Three guarantees:
AsOfClock |
Derives a feature window and a label window from a forecast time and refuses to let them overlap. |
VisibleFrame |
Cannot be constructed holding an observation that postdates its clock. The constructor validates and raises LookaheadError. Feature builders accept only this type. |
audit_lookahead |
Tests a whole pipeline for dependence on unavailable data — catching leaks that never place a future row in a feature. |
The audit is the part you probably need
A type can guarantee a frame holds no future rows. It cannot guarantee the pipeline is honest. An eligibility filter, a join against a "latest" snapshot, or a groupby computed before the cut can each admit information that did not exist at forecast time, without any future row ever appearing.
audit_lookahead answers the question empirically, without reading your code:
Does this function's output change when future rows are removed?
from honestclock import audit_lookahead
report = audit_lookahead(df, build_features, clocks, "received_at")
report.raise_for_status() # AssertionError if the output depends on the future
It reports how many future rows were withheld at each forecast time, so a
clean result cannot be mistaken for a strong one — an audit that withheld
nothing proves nothing, and says so (report.vacuous).
A trap worth knowing about
event_share and event_count describe the same history and are not
interchangeable as ranking signals. A share is bounded in [0, 1] and
saturates: thousands of entities tie at 1.0, a top-k cut lands inside the
tie group, and realised precision collapses toward the group's base rate.
In the study this package came from, a silence baseline built as a share scored 17.1% at a national top-100; the same history built as a count scored 45.2%. A 28-point difference that was an artefact of the data type, not the signal. Any comparison between a model and a one-column heuristic must build the heuristic as a count, or it is measuring representations.
Scope, honestly
This package is the point-in-time layer, not a model. It gives you a leak-resistant feature/label split and an auditor. It does not contain the Circuit Rider AI model, its trained weights, or any facility-level score, and it deliberately publishes no per-facility rankings.
The ECHO adapter is a schema contract and a panel builder
(honestclock.echo), not a downloader. EPA's bulk files are large and their
schema shifts between vintages; the contract is pinned in one module so a
change surfaces as one failing test rather than as a quietly wrong number.
Domain-neutral core. Nothing outside honestclock.echo knows about EPA.
Any panel with a reference time and an availability time works — drinking
water, air permits, workplace safety, clinical registries, credit files.
Development
pip install -e ".[dev,parquet]"
pytest # 153 tests, 100% branch coverage
ruff check src tests examples
ruff format --check src tests examples
mypy src/honestclock
python examples/quickstart.py
The suite includes property-based tests (via Hypothesis) asserting the availability invariant over thousands of generated panels, and adversarial tests in which deliberately leaky feature functions — modelled on real defects — must be caught by the auditor.
What is verified, and where
Because this package is about not making unsupported claims, here is the status of its own:
| Claim | Verified by |
|---|---|
| 153 tests, 100% statement and branch coverage | local + CI (--cov-fail-under=100) |
Annotations are accurate (py.typed) |
mypy at default strictness, in CI |
Works on the declared floor, pandas>=1.5 |
run against pandas 1.5.3 locally and in CI |
Works on pandas 2.x |
pandas 2.3.3 locally, 2.0 and latest in CI |
| Installs and works as a built wheel | clean-venv install, suite re-run against the artifact |
| The ECHO adapter parses real bulk data | run against a real 4.5M-row FY2026 DMR extract: 1.67M pair-months, invariants held, median availability lag 16 days |
requires-python >=3.9 |
3.9–3.13 all green in CI (9-job matrix) |
mypy --strict is not clean: pandas' stubs return Any from most
operations, and satisfying it would mean scattering casts that obscure the
logic. Default strictness is the level this package claims.
Provenance
Extracted from the evaluation pipeline of Circuit Rider AI: Assistance-Oriented Compliance Triage for Small Wastewater Systems from Public Data. The 20-day/59-day latency figures, the share-versus-count artefact, and the two leak shapes the auditor models are findings from that work.
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file honestclock-0.1.0.tar.gz.
File metadata
- Download URL: honestclock-0.1.0.tar.gz
- Upload date:
- Size: 38.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8ec1d6375944ecbac26d65dd513f31cb405f204ae4984a1a6ff96d6f50311ae
|
|
| MD5 |
6562a5a7089b7012d547b032bc34ee9f
|
|
| BLAKE2b-256 |
52d1a28f66ab3a21399e57547529d2fd71bdc717d7ad157ed2875a895ebb486c
|
File details
Details for the file honestclock-0.1.0-py3-none-any.whl.
File metadata
- Download URL: honestclock-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7feb6f0a6fb89860fb88e4accf1b158eb44e7a84ae842e91787742af0c0218b7
|
|
| MD5 |
dc95b9d5f835e1c3c19f7c83fa9bf20b
|
|
| BLAKE2b-256 |
dd5a7c74cfab76db5bcbe9e15fa0710964a11bfa8f83b2be3e420a4e551694ce
|