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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
run: python -m unittest discover -s tests -v
- name: Run the Python library example
run: python examples/decision_time.py
- name: Run notebook cells against the current package without the pinned install cell
run: python scripts/check_notebook.py
- name: Verify the scale runner on a small deterministic workload
run: pitbridge benchmark --rows 2000 --repeats 2 --out outputs/scaling-ci
- name: Replay committed evidence
Expand Down Expand Up @@ -83,6 +85,8 @@ jobs:
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
.\.venv\Scripts\python.exe examples/decision_time.py
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
.\.venv\Scripts\python.exe scripts/check_notebook.py
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
.\.venv\Scripts\python.exe -m pitbridge demo --out outputs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
.\.venv\Scripts\python.exe -m pitbridge verify --out outputs
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
**Financial features as they were known when a decision was made.**

[![CI](https://github.com/dev-belly/PITBridge/actions/workflows/ci.yml/badge.svg)](https://github.com/dev-belly/PITBridge/actions/workflows/ci.yml)
[Try one invoice](examples/README.md#a-correction-that-arrives-later) · [Bring CSV tables](examples/README.md#optional-pandascsv-input) · [Open the online report](https://dev-belly.github.io/PITBridge/) · [中文说明](README.zh-CN.md)
[Run in Colab](https://colab.research.google.com/github/dev-belly/PITBridge/blob/main/examples/decision_time.ipynb) · [Try one invoice locally](examples/README.md#a-correction-that-arrives-later) · [Bring CSV tables](examples/README.md#optional-pandascsv-input) · [Open the online report](https://dev-belly.github.io/PITBridge/) · [中文说明](README.zh-CN.md)

The notebook installs **dev-belly/PITBridge** from a full source URL pinned to one
commit; no local checkout is needed. Change a decision or ingestion time to see
which invoice revision remains available.

[Methodology](docs/METHODOLOGY.md) · [Data contract](docs/DATA_CONTRACT.md) · [Interview notes](docs/INTERVIEW.md)

Expand Down
5 changes: 4 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

**核心问题:做出信贷决策时,银行当时到底能看到哪个版本的数据?**

[先试一张发票](examples/README.md#a-correction-that-arrives-later) · [从 CSV 表格开始](examples/README.md#optional-pandascsv-input) · [在线报告](https://dev-belly.github.io/PITBridge/) · [提交试用反馈](https://github.com/dev-belly/PITBridge/issues/new?template=trial_feedback.md)
[浏览器里试一张发票(Colab)](https://colab.research.google.com/github/dev-belly/PITBridge/blob/main/examples/decision_time.ipynb) · [本地案例](examples/README.md#a-correction-that-arrives-later) · [从 CSV 表格开始](examples/README.md#optional-pandascsv-input) · [在线报告](https://dev-belly.github.io/PITBridge/) · [提交试用反馈](https://github.com/dev-belly/PITBridge/issues/new?template=trial_feedback.md)

Notebook 使用 **dev-belly/PITBridge** 的完整源码 URL,固定到一个提交版本,无需克隆。
修改决策或入库时间,即可查看当时应选择哪个发票版本。

例如,一月份开票金额在二月份公开、三月份才进入银行系统,四月份又被修订。直接按照“一月份”去关联历史申请,会把后来的信息放进早期特征,导致回测过于乐观。

Expand Down
7 changes: 7 additions & 0 deletions docs/SHARE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Share the invoice counterexample

For a browser trial, [open the editable invoice notebook in Colab](https://colab.research.google.com/github/dev-belly/PITBridge/blob/main/examples/decision_time.ipynb):
no local clone is needed. Its full installation URL selects dev-belly/PITBridge
at a fixed source commit. All records are synthetic.

也可直接[在浏览器里改两个时间、试一次发票选择](https://colab.research.google.com/github/dev-belly/PITBridge/blob/main/examples/decision_time.ipynb),
无需本地克隆;Notebook 通过完整源码 URL 安装固定提交版本,记录均为合成数据。

## 中文

做金融数据或特征工程时,事后修订容易被误用到历史决策。我做了 [PITBridge](https://github.com/dev-belly/PITBridge):用合成发票说明,同为2026年1月31日数据,v1于2月5日发布、6日入库,值10万元;v2于4月1日发布、3日入库,值13.5万元。90天新鲜度下,2月1日未可用,2月15日选v1,4月15日选v2;SQL与独立Python逐行核对。[小示例](https://github.com/dev-belly/PITBridge/blob/main/examples/README.md)可本地运行;[在线演示](https://dev-belly.github.io/PITBridge/)展示11条观测、45次查询的大案例。想请你试试CSV输入,反馈时点与修订选择是否贴合你的场景:[提交反馈](https://github.com/dev-belly/PITBridge/issues/new?template=trial_feedback.md)。
Expand Down
8 changes: 8 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Small, runnable examples

Want to edit a timestamp without setting up a local checkout?
[Open the invoice notebook in Colab](https://colab.research.google.com/github/dev-belly/PITBridge/blob/main/examples/decision_time.ipynb)
or [inspect its source](decision_time.ipynb). It installs dev-belly/PITBridge from
a full source URL pinned to one commit, checks the three fixed decisions, and
then lets you change decision and correction-ingestion times. Only the standard
library is used at runtime; installation needs network access. The separate
editable scenario does not reuse the fixed fixture's expected results.

Install the repository in a Python 3.11+ virtual environment using the
[quick start](../README.md#run-in-one-minute), then run:

Expand Down
237 changes: 237 additions & 0 deletions examples/decision_time.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "intro",
"metadata": {},
"source": [
"# Which invoice revision could this decision actually know?\n",
"\n",
"One synthetic invoice, two revisions, three decision dates. Compare a deliberately\n",
"wrong event-date join with availability-aware financial / ML features, then edit\n",
"two timestamps yourself. **These invented CNY amounts demonstrate a data contract;\n",
"they do not establish model accuracy, investment returns or lending outcomes.**\n",
"\n",
"一张合成发票,两个修订版本:一月份发生的数据,二月份和四月份的决策分别能看到什么?\n",
"\n",
"Use Python 3.11+ and **Run all** in a fresh runtime. No clone, GPU, dataset account\n",
"or third-party runtime library is needed. Installation requires network access;\n",
"the example then runs locally in the notebook. Colab sign-in follows Google's\n",
"platform requirements."
]
},
{
"cell_type": "markdown",
"id": "install-notes",
"metadata": {},
"source": [
"## 1. Install this exact source snapshot\n",
"\n",
"The full URL below selects **dev-belly/PITBridge**, pinned to commit\n",
"`a5e423debda20555a0cef4500606c9533908c6fd`. Use this source URL to identify this\n",
"project rather than relying on a package name alone. Pip may download build tools;\n",
"PITBridge itself has no third-party runtime dependencies."
]
},
{
"cell_type": "code",
"id": "install-source",
"metadata": {
"tags": [
"pitbridge-install"
]
},
"source": [
"import subprocess\n",
"import sys\n",
"\n",
"if sys.version_info < (3, 11):\n",
" raise RuntimeError(\"This example requires Python 3.11 or newer\")\n",
"\n",
"# Exact source owner and commit, without Git or a source checkout.\n",
"SOURCE_COMMIT = \"a5e423debda20555a0cef4500606c9533908c6fd\"\n",
"SOURCE_URL = f\"https://codeload.github.com/dev-belly/PITBridge/zip/{SOURCE_COMMIT}\"\n",
"subprocess.check_call([\n",
" sys.executable, \"-m\", \"pip\", \"install\", \"--quiet\",\n",
" \"--disable-pip-version-check\", \"--force-reinstall\", \"--no-deps\", SOURCE_URL,\n",
"])\n",
"print(f\"Installed dev-belly/PITBridge from {SOURCE_COMMIT}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "invoice-contract",
"metadata": {},
"source": [
"## 2. Check the three original decisions\n",
"\n",
"Both records describe January 31. The original amount is published February 5\n",
"and ingested February 6; the correction is published April 1 and ingested April 3.\n",
"Availability is **the later of publication and ingestion**, with a 90-day\n",
"freshness limit. The hand-calculated expectations are:\n",
"\n",
"| Decision (UTC) | Expected result |\n",
"| --- | --- |\n",
"| February 1 | missing: `not_available` |\n",
"| February 15 | `invoice-v1`, CNY 100,000 |\n",
"| April 15 | `invoice-v2`, CNY 135,000 |\n",
"\n",
"SQL must match both these fixed expectations and the independent Python\n",
"enumerator. These assertions belong to this unchanged fixture; the editable\n",
"scenario appears separately below."
]
},
{
"cell_type": "code",
"id": "fixed-expectations",
"metadata": {},
"source": [
"import sys\n",
"import pitbridge\n",
"from pitbridge import Decision, FeatureSpec, Observation, build_snapshot, reference_snapshot\n",
"from pitbridge.bundle import compare_unsafe\n",
"\n",
"print(f\"Python {sys.version.split()[0]}; PITBridge imported from {pitbridge.__file__}\")\n",
"\n",
"def invoice_observations(correction_ingested_at=\"2026-04-03T00:00:00Z\"):\n",
" return [\n",
" Observation(\n",
" record_id=\"invoice-v1\", entity_id=\"SME-001\", source=\"tax\",\n",
" feature=\"invoice_revenue\", event_at=\"2026-01-31T00:00:00Z\",\n",
" published_at=\"2026-02-05T00:00:00Z\", ingested_at=\"2026-02-06T00:00:00Z\",\n",
" revision=1, value=100000,\n",
" ),\n",
" Observation(\n",
" record_id=\"invoice-v2\", entity_id=\"SME-001\", source=\"tax\",\n",
" feature=\"invoice_revenue\", event_at=\"2026-01-31T00:00:00Z\",\n",
" published_at=\"2026-04-01T00:00:00Z\", ingested_at=correction_ingested_at,\n",
" revision=2, value=135000,\n",
" ),\n",
" ]\n",
"\n",
"observations = invoice_observations()\n",
"decisions = [\n",
" Decision(\"01-before-arrival\", \"SME-001\", \"2026-02-01T00:00:00Z\"),\n",
" Decision(\"02-original-known\", \"SME-001\", \"2026-02-15T00:00:00Z\"),\n",
" Decision(\"03-correction-known\", \"SME-001\", \"2026-04-15T00:00:00Z\"),\n",
"]\n",
"specs = [FeatureSpec(\"tax\", \"invoice_revenue\", max_age_days=90)]\n",
"snapshots = build_snapshot(observations, decisions, specs)\n",
"assert snapshots == reference_snapshot(observations, decisions, specs)\n",
"expected = [\n",
" (\"01-before-arrival\", None, \"not_available\", None),\n",
" (\"02-original-known\", 100000.0, \"selected\", \"invoice-v1\"),\n",
" (\"03-correction-known\", 135000.0, \"selected\", \"invoice-v2\"),\n",
"]\n",
"actual = [(row.decision_id, row.value, row.status, row.record_id) for row in snapshots]\n",
"assert actual == expected, (actual, expected)\n",
"print(\"All three hand-calculated decisions match SQL and independent Python replay.\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "unsafe-baseline",
"metadata": {},
"source": [
"## 3. See what the wrong join does\n",
"\n",
"This is the same deliberately unsafe comparator used in the saved PITBridge\n",
"report. It filters only on event date and takes the highest revision, ignoring\n",
"availability, tombstones and freshness. **It is a counterexample, never a\n",
"feature-building recommendation.** Its April correction leaks into both February\n",
"decisions, even though all records share the same January event date."
]
},
{
"cell_type": "code",
"id": "compare-selections",
"metadata": {},
"source": [
"def show_comparison(source, rows):\n",
" comparison = compare_unsafe(source, rows)\n",
" print(\"Decision UTC | Wrong event-only record/value | Availability-aware result | Future knowledge?\")\n",
" for row, item in zip(rows, comparison, strict=True):\n",
" wrong = f\"{item['unsafe_record_id']}: {item['unsafe_value']}\"\n",
" safe = f\"{row.status}, {row.record_id}: {row.value}\"\n",
" print(f\"{row.decision_at[:10]} | {wrong} | {safe} | {item['unsafe_uses_future_knowledge']}\")\n",
" return comparison\n",
"\n",
"comparison = show_comparison(observations, snapshots)\n",
"assert [row[\"unsafe_record_id\"] for row in comparison] == [\"invoice-v2\"] * 3\n",
"assert [row[\"unsafe_uses_future_knowledge\"] for row in comparison] == [True, True, False]\n",
"print(\"The wrong join uses future knowledge in 2 of these 3 invented decisions.\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "edit-time-notes",
"metadata": {},
"source": [
"## 4. Change two timestamps and rerun just the next cell\n",
"\n",
"Try moving `decision_at` from April 2 to April 4, or move\n",
"`correction_ingested_at` from April 3 to April 1. Use timezone-aware ISO strings\n",
"(`Z` means UTC). The correction's publication date remains April 1: an earlier\n",
"ingestion timestamp cannot make it available before publication.\n",
"\n",
"This separate scenario checks SQL against the independent enumerator. It does\n",
"not reuse the original fixture's fixed expectations, so valid edits can change\n",
"the result. The core contract will still reject invalid input timestamps."
]
},
{
"cell_type": "code",
"id": "editable-times",
"metadata": {},
"source": [
"# Edit either string, then rerun this cell.\n",
"decision_at = \"2026-04-02T00:00:00Z\"\n",
"correction_ingested_at = \"2026-04-03T00:00:00Z\"\n",
"\n",
"custom_observations = invoice_observations(correction_ingested_at)\n",
"custom_decisions = [Decision(\"editable-decision\", \"SME-001\", decision_at)]\n",
"custom_rows = build_snapshot(custom_observations, custom_decisions, specs)\n",
"assert custom_rows == reference_snapshot(custom_observations, custom_decisions, specs)\n",
"print(f\"Correction available at: {custom_observations[1].available_at}\")\n",
"custom_comparison = show_comparison(custom_observations, custom_rows)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "next-steps",
"metadata": {},
"source": [
"## Apply the timing question to your own feature pipeline\n",
"\n",
"What timestamp in your feed establishes when a revision was genuinely available?\n",
"This notebook does not authenticate that upstream timestamp or demonstrate a\n",
"real trading or credit outcome. Read the [data contract](https://github.com/dev-belly/PITBridge/blob/main/docs/DATA_CONTRACT.md)\n",
"before adapting the API. For existing tables, the [optional CSV example](https://github.com/dev-belly/PITBridge/blob/main/examples/README.md#optional-pandascsv-input)\n",
"is a separate local adapter requiring pandas.\n",
"\n",
"If a small synthetic counterexample exposes a mismatch with your workflow,\n",
"[share the timestamps, expected selection and observed result](https://github.com/dev-belly/PITBridge/issues/new?template=trial_feedback.md)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading