Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ dependencies**. The commands use a virtual environment without requiring
activation.

These commands clone `main` for the source examples. For a fixed installation
without Git, use the [checked v0.2.0 wheel](#install-the-checked-release).
v0.2.0 includes the installed CSV importer, rolling features and `benchmark`;
the earlier v0.1.0 tag lacks the CSV importer and benchmark.
without Git, use the [checked v0.2.1 wheel](#install-the-checked-release).
v0.2.1 includes the installed CSV importer, rolling features and `benchmark`,
and rejects unsupported timestamp precision before selection; the earlier
v0.1.0 tag lacks the CSV importer and benchmark.

**Linux / macOS**

Expand Down Expand Up @@ -81,21 +82,21 @@ Install this repository's wheel directly, using its full URL:

```bash
python3 -m venv .venv
.venv/bin/python -m pip install "https://github.com/dev-belly/PITBridge/releases/download/v0.2.0/pitbridge-0.2.0-py3-none-any.whl"
.venv/bin/python -m pip install "https://github.com/dev-belly/PITBridge/releases/download/v0.2.1/pitbridge-0.2.1-py3-none-any.whl"
.venv/bin/python -m pitbridge demo --out outputs
.venv/bin/python -m pitbridge verify --out outputs
```

On Windows, create the environment with `python -m venv .venv` and substitute
`.\.venv\Scripts\python.exe` for `.venv/bin/python`.
The [release page](https://github.com/dev-belly/PITBridge/releases/tag/v0.2.0)
The [release page](https://github.com/dev-belly/PITBridge/releases/tag/v0.2.1)
also provides the source archive, `installed-check.json`, an exact-commit
`release-manifest.json` and `CHECKSUMS.sha256`. The wheel is built from the
source archive, installed in a fresh environment outside the checkout and
used for scalar/rolling JSON and CSV replay plus a small benchmark. Publishing
used for scalar/rolling JSON and CSV replay, timestamp-precision checks and a small benchmark. Publishing
waits for the complete main CI, including Windows, pandas and Chrome jobs;
the publisher verifies the exact CI artifacts instead of rebuilding them.
[Release notes and replay compatibility](docs/releases/v0.2.0.md) ·
[Release notes and replay compatibility](docs/releases/v0.2.1.md) ·
[Reproduce the release checks](docs/RELEASING.md).

## Use the Python API
Expand Down
12 changes: 6 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ PITBridge 同时处理数据发生时间、公开时间、入库时间和修订

需要 Git 和 Python 3.11 或更高版本。下面是 Windows PowerShell 命令,使用独立环境,无需先激活:

这些命令克隆 `main` 以运行源码示例。固定安装可使用下面的 v0.2.0 wheel,
其中已包含内置 CSV 接入、滚动特征和 `benchmark` 命令。
这些命令克隆 `main` 以运行源码示例。固定安装可使用下面的 v0.2.1 wheel,
其中包含内置 CSV 接入、滚动特征、`benchmark` 命令及时间精度校验。
较早的 v0.1.0 标签不包含内置 CSV 接入和 benchmark。

```powershell
Expand All @@ -51,16 +51,16 @@ Linux / macOS 用 `python3 -m venv .venv` 创建环境,并将上述 `.\.venv\S

```powershell
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install "https://github.com/dev-belly/PITBridge/releases/download/v0.2.0/pitbridge-0.2.0-py3-none-any.whl"
.\.venv\Scripts\python.exe -m pip install "https://github.com/dev-belly/PITBridge/releases/download/v0.2.1/pitbridge-0.2.1-py3-none-any.whl"
.\.venv\Scripts\python.exe -m pitbridge demo --out outputs
.\.venv\Scripts\python.exe -m pitbridge verify --out outputs
```

[发布页面](https://github.com/dev-belly/PITBridge/releases/tag/v0.2.0)同时提供源码包、
[发布页面](https://github.com/dev-belly/PITBridge/releases/tag/v0.2.1)同时提供源码包、
安装核验记录、固定提交号的发布清单和 SHA-256 校验值。wheel 从源码包构建,
在离开源码目录的干净环境中检查四条 JSON/CSV 路径与小规模 benchmark;
在离开源码目录的干净环境中检查四条 JSON/CSV 路径、时间精度及小规模 benchmark;
完整主分支 CI(包括 Windows、pandas 和 Chrome)通过后才发布同一份产物。
[升级与重放边界](docs/releases/v0.2.0.md) · [复现发布检查](docs/RELEASING.md)。
[升级与重放边界](docs/releases/v0.2.1.md) · [复现发布检查](docs/RELEASING.md)。

## 可直接复用的 Python 案例

Expand Down
8 changes: 5 additions & 3 deletions docs/DATA_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ CSV and the optional pandas example use this same validation before selection
or report creation. A source with finer timestamps must choose and document an
upstream conversion policy; this engine does not infer one.

These stricter checks are on `main` after the v0.2.0 release. Existing release
assets are unchanged; use the source installation for this validation. Bundles
with timestamps already in the supported form retain their original bytes.
These stricter checks are included in v0.2.1. The unchanged v0.2.0 wheel can
truncate excess digits before selection. Previously discarded timestamp digits
cannot be recovered from normalized bundles: retain the original source and
rebuild affected inputs before reviewing decisions. Bundles with timestamps
already in the supported form retain their original bytes.

| Field | Type / rule |
| :--- | :--- |
Expand Down
6 changes: 4 additions & 2 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ the wheel without a package index into a fresh virtual environment. From a
directory outside the checkout it verifies package/runtime versions, generates
and semantically replays scalar/rolling JSON and CSV bundles, compares the CSV
results, and runs two 2,000-observation benchmark trials with the independent
oracle. The benchmark is an installation smoke check, not a release performance
claim.
oracle. The installed wheel also checks exact-cutoff versus one-microsecond-late
selection and rejects a seventh digit on all four scalar/rolling JSON/CSV CLI
routes before creating a report. The benchmark is an installation smoke check,
not a release performance claim.

To reproduce from a clean checkout with no stale files in `dist` or `release`:

Expand Down
35 changes: 35 additions & 0 deletions docs/releases/v0.2.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# PITBridge v0.2.1

The checked wheel includes the decision-time precision repair already merged
into the source. A seventh fractional-second digit could previously be silently
discarded, making a source record that arrived just after a decision appear
available at the cutoff. Timestamps finer than microseconds now fail before
selection or report creation, including trailing zero digits.

```bash
python -m pip install "https://github.com/dev-belly/PITBridge/releases/download/v0.2.1/pitbridge-0.2.1-py3-none-any.whl"
python -m pitbridge demo --out outputs
python -m pitbridge verify --out outputs
```

Python 3.11+ is required; the wheel has no third-party runtime dependencies.
The installed-package check verifies scalar and rolling selection at the cutoff
and one supported microsecond later. It exercises JSON and CSV inputs for both
commands and requires exit 2, a useful diagnostic and no output directory for
an unsupported seventh digit. The release gate requires this evidence alongside
the existing independent replay and benchmark checks.

Fractional seconds on shortened clock forms and fractional-second timezone
offsets also fail explicitly. Standard timezone-aware timestamps with up to
six digits and whole-second offsets retain their existing interpretation.
Existing supported-format demo evidence remains verifiable.

The old v0.2.0 wheel remains available unchanged. If old input timestamps were
already normalized, discarded digits cannot be reconstructed from a saved
bundle. Keep the original source records and rebuild affected cases under a
documented upstream precision policy before relying on their historical
selections. Package version numbers are separate from the existing bundle
format markers; this release does not claim source timestamp authenticity.

[Full timestamp contract](../DATA_CONTRACT.md) ·
[CSV intake](../CSV_INPUTS.md) · [Reproduce release checks](../RELEASING.md).
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pitbridge"
version = "0.2.0"
version = "0.2.1"
description = "Availability-aware financial feature snapshots with revision lineage"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
118 changes: 118 additions & 0 deletions scripts/check_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import tomllib
import venv
from pathlib import Path
from textwrap import dedent


def check(directory: Path) -> dict:
Expand Down Expand Up @@ -73,6 +74,118 @@ def run(*arguments: str) -> str:
"Installed distribution/runtime version differs from the source version"
)
run(str(python), "-I", "-m", "pitbridge", "--help")

# An arrival just after a decision must never be rounded into visibility.
# Exercise the actual installed package, not the source checkout.
boundary = json.loads(
run(
str(python),
"-I",
"-c",
dedent("""
import json
from pitbridge import Decision, FeatureSpec, Observation, build_snapshot
from pitbridge.rolling import RollingSpec, build_rolling

cutoff = "2026-01-01T00:00:00Z"
decision = [Decision("d", "e", cutoff)]
scalar_spec = [FeatureSpec("s", "f", 1)]
rolling_spec = [RollingSpec("total", "s", "f", 1)]
for ingestion, available in (
(cutoff, True), ("2026-01-01T00:00:00.000001Z", False)
):
obs = [Observation("r", "e", "s", "f", cutoff, cutoff,
ingestion, 1, 10)]
scalar = build_snapshot(obs, decision, scalar_spec)[0]
rolling, members = build_rolling(obs, decision, rolling_spec)
if ((scalar.record_id == "r") != available
or (len(members) == 1) != available
or (rolling[0].status != "not_available") != available):
raise ValueError("Incorrect installed cutoff selection")
print(json.dumps({"cutoff_and_late_microsecond": True}))
"""),
)
)

cutoff = "2026-01-01T00:00:00Z"
late = "2026-01-01T00:00:00.0000009Z"
for command, spec in (("build", "specs"), ("aggregate", "rolling_specs")):
case = {
"observations": [
{
"record_id": "r",
"entity_id": "e",
"source": "s",
"feature": "f",
"event_at": cutoff,
"published_at": cutoff,
"ingested_at": late,
"revision": 1,
"value": 10,
"deleted": False,
}
],
"decisions": [
{"decision_id": "d", "entity_id": "e", "decision_at": cutoff}
],
spec: (
[{"source": "s", "feature": "f", "max_age_days": 1}]
if command == "build"
else [
{
"name": "total",
"source": "s",
"feature": "f",
"window_days": 1,
"aggregation": "sum",
}
]
),
}
inputs = work / f"precision-{command}.json"
inputs.write_text(json.dumps(case), encoding="utf-8")
tables = work / f"precision-{command}-tables"
tables.mkdir()
for section, rows in case.items():
with (tables / f"{section}.csv").open(
"w", encoding="utf-8", newline=""
) as handle:
writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(
{
key: str(value).lower()
if isinstance(value, bool)
else value
for key, value in row.items()
}
for row in rows
)
for route, source in (("--inputs", inputs), ("--csv-dir", tables)):
output = work / f"precision-{command}-{route[2:]}-output"
result = subprocess.run(
(str(cli), command, route, str(source), "--out", str(output)),
cwd=work,
env=environment,
text=True,
encoding="utf-8",
errors="replace",
capture_output=True,
check=False,
)
if (
result.returncode != 2
or result.stdout
or output.exists()
or "microsecond precision" not in result.stderr
or "Traceback" in result.stderr
):
raise ValueError(
f"Installed {command} {route} did not reject excess precision"
)
if boundary != {"cutoff_and_late_microsecond": True}:
raise ValueError("Installed package changed supported cutoff semantics")

run(str(cli), "demo", "--out", "scalar")
scalar = json.loads(run(str(cli), "verify", "--out", "scalar"))
run(str(cli), "rolling-demo", "--out", "rolling")
Expand Down Expand Up @@ -138,6 +251,11 @@ def run(*arguments: str) -> str:
"python": sys.version.split()[0],
"platform": sys.platform,
"installation": "non-editable wheel; clean venv; cwd outside the source tree",
"timestamp_precision": {
"cutoff_and_late_microsecond": True,
"json_and_csv_rejections": 4,
"no_reports_on_rejection": True,
},
"scalar": scalar,
"rolling": rolling,
"csv": csv_results,
Expand Down
9 changes: 9 additions & 0 deletions scripts/release_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ def validate_check(path: Path, version: str) -> None:
or check.get("version") != version
):
raise ValueError("Installed check must pass for the source version")
precision = check.get("timestamp_precision")
if (
not isinstance(precision, dict)
or precision.get("cutoff_and_late_microsecond") is not True
or type(precision.get("json_and_csv_rejections")) is not int
or precision["json_and_csv_rejections"] != 4
or precision.get("no_reports_on_rejection") is not True
):
raise ValueError("Installed timestamp-precision boundary evidence is required")
for section, key in (
("scalar", "independent_replay"),
("rolling", "independent_temporal_replay"),
Expand Down
2 changes: 1 addition & 1 deletion src/pitbridge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
from .core import Decision, FeatureSpec, Observation, Snapshot, build_snapshot, reference_snapshot
from .csv_inputs import load_csv_inputs

__version__ = "0.2.0"
__version__ = "0.2.1"
__all__ = ["Decision", "FeatureSpec", "Observation", "Snapshot", "build_snapshot", "reference_snapshot", "load_csv_inputs"]
25 changes: 25 additions & 0 deletions tests/test_release_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ def setUp(self):
self.result = {
"status": "verified",
"version": self.version,
"timestamp_precision": {
"cutoff_and_late_microsecond": True,
"json_and_csv_rejections": 4,
"no_reports_on_rejection": True,
},
"scalar": scalar,
"rolling": rolling,
"csv": {"scalar": scalar, "rolling": rolling},
Expand Down Expand Up @@ -122,6 +127,26 @@ def test_benchmark_disagreement_prevents_publication(self):
self.prepare()
self.assertFalse(self.destination.exists())

def test_missing_or_false_installed_precision_evidence_prevents_publication(self):
original = copy.deepcopy(self.result)
for field, value in (
(None, None),
("cutoff_and_late_microsecond", False),
("json_and_csv_rejections", 3),
("json_and_csv_rejections", True),
("no_reports_on_rejection", False),
):
self.result = copy.deepcopy(original)
if field is None:
self.result.pop("timestamp_precision")
else:
self.result["timestamp_precision"][field] = value
self.write_check()
with self.subTest(field=field, value=value):
with self.assertRaisesRegex(ValueError, "timestamp-precision"):
self.prepare()
self.assertFalse(self.destination.exists())

def test_wrong_distribution_metadata_prevents_publication(self):
for kwargs in (
{"wheel_version": "9.0.0"},
Expand Down
Loading