Skip to main content

schema-firewall

Three checks that catch the leakage and schema bugs that slip past peer review.

pip install schema-firewall

CI PyPI Python License

Production usage. Extracted from the firewall layer of nyc-real-estate-predictor — the flagship pins schema-firewall==0.1.3 in requirements.txt and re-validates the integration in its External Benchmark CI job, which runs weekly and on pushes/PRs touching the benchmark's paths (path-filtered, not every push). It shows the library is used and CI-exercised downstream — not that every contract is enforced there. The pin trails this README by a minor version (0.1.3 predates the 0.2.x breaking hardening documented below); bumping it is a separate flagship decision.


The problem

In the last five years, published and competition-grade ML systems have repeatedly shipped with one of these three bugs:

Bug Real example Impact
Feature statistically mirrors the target COVID-19 chest X-ray classifiers learned hospital-ID confounders, not pulmonary features Internal AUC 0.99, external-hospital AUC near-chance
Forbidden / post-outcome feature in the input JAMA Network Open 2024: 40.2% of MIMIC same-admission prediction studies fed in ICD codes finalised at discharge AUROC 0.97 from leaky codes alone
Transform that reads across the whole dataset Kaggle Santander 2019 "magic" leak: frequency features computed on (train ∪ real-test) Public AUC jumped 0.90 → 0.92

Each one escaped peer review, code review, or competition scrutiny — because the bug isn't a type error. It's a statistical / semantic contract violation.

schema-firewall provides three drop-in checks, one per bug class.


Usage

import pandas as pd
from schema_firewall import (
    check_leakage,
    check_schema,
    check_stateless,
    SchemaContract,
    LeakageError,
)

X: pd.DataFrame  # your feature frame
y: pd.Series     # your target

# 1. Statistical leakage — Pearson + Spearman + adjusted mutual information.
#    Pearson catches linear copies, Spearman monotonic transforms, and the
#    chance-corrected MI catches NON-monotone and discrete deterministic leakage
#    (y=x**2, |x|, low-order oscillations, binary/k-class target encodings) that
#    both correlations miss — while leaving honest noisy predictors alone.
#    Detection is per-column (a multi-column combination like y = x1 XOR x2 is a
#    documented non-goal, as are high-frequency oscillatory encodings). Needs
#    >=100 rows. Raises LeakageError on fail.
check_leakage(X, y)

# 2. Schema contract — forbidden columns, required columns, dtypes.
#    Catches ICD-code-style post-outcome features and schema drift.
contract = SchemaContract(
    forbidden_columns=frozenset({"SALE PRICE", "PRICE_PER_SQFT"}),
    required_columns=frozenset({"sqft", "year_built"}),
)
check_schema(X, contract)

# 3. Statelessness — runs your feature pipeline on the full frame vs a
#    single-row subset. Flags any transform whose per-row output depends
#    on other rows: mean encoders, frequency encoders, target encoders
#    applied outside CV, ComBat/global normalisation, etc.
check_stateless(my_pipeline_fn, raw_frame)

Each function raises on failure and returns None on pass. No silent degradation.


The demo notebook

examples/leakage_demo.ipynb — 60 seconds, California housing dataset, one deliberate leak, one library call.

Open it. It reproduces the target-encoding bug that sits in real production pipelines, shows an R² that looks impressive, then one call to check_stateless catches the leak before the model ships.

If you've ever applied .mean(), .value_counts(), TargetEncoder, or ComBat/fit_transform to your full dataset before cross-validation, the notebook is pointed at you.


Verified invariants under execution

The library is in production use today as a pinned dep of nyc-real-estate-predictor. The flagship's External Benchmark CI job re-checks these invariants against the published wheel on a weekly schedule and on pushes/PRs touching the benchmark's paths (the job is path-filtered):

  • Statistical leakage detection triggers on the bundled California housing demo. Build a target-mean-encoded feature on rounded lat/lon buckets — Ridge regression returns R² = 0.9495 (leaky). Apply the same target encoding per train fold only — R² collapses to 0.4384 (honest). Both check_leakage and check_stateless raise on the leaky pipeline. Reproducible in 60 seconds via examples/leakage_demo.ipynb.

  • Statelessness holds under subset perturbation. check_stateless runs the user pipeline on the full frame, then on a one-row subset. Any transform whose per-row output depends on other rows (frequency encoders, target-mean encoders, ComBat-style global normalisation) fails this invariant by construction. The default spot-check deliberately targets the rows a global transform is most likely to edit — the min/max rows of every numeric column (winsorise/clip/quantile filters touch the tails, and a low-variance standardised column is as likely a target as a high-variance one, so no column is skipped), NaN-bearing rows (the first 10 by default — fillna(df.mean()) edits every NaN row identically, so a capped sample still catches it), and a fixed-stride spread across the rest — rather than being fooled by a plain stride sample that misses a tail- or NaN-only edit. Cost is two pipeline calls per numeric column; pass an explicit sample_indices to bound it on very wide frames or to check every row (the strongest guarantee).

  • Forbidden-column gate raises on the documented set. nyc-real-estate-predictor configures SchemaContract(forbidden_columns=frozenset({"SALE PRICE", "SALE DATE", "PRICE_PER_SQFT", "TARGET", "log_price"})). Verifiable from this repo: the parametrized tests/test_checks.py::test_schema_rejects_forbidden_column asserts check_schema raises on each of those names. The flagship additionally re-validates the integration in its own CI (see the production-usage note above); its internal test suite is that repo's claim, not verified here.

  • Determinism check catches non-deterministic transforms. Two consecutive pipeline_fn(raw) calls must produce identical frames. Unseeded random initialisation, dict-order dependency, and side-effecting transforms all fail. Internal pd.testing.assert_frame_equal.

These hold across the test matrix; numbers (test counts, coverage %) age — the invariants don't.


What this is NOT

  • Not a replacement for train/test splitting, cross-validation, or sklearn Pipeline.
  • Not a feature-importance tool.
  • Not a drift-monitoring service.
  • Not a validation framework with its own DSL.

Three checks. One contract class. Four exceptions. That's the whole library.


Design constraints (locked)

  • ≤ 500 LoC of core implementation across src/schema_firewall/, enforced by a test so the budget can't silently rot. Count the code lines yourself: find src/schema_firewall -name '*.py' -exec grep -vhE '^\s*(#|$)' {} + | wc -l.
  • 3 public check functionscheck_leakage, check_schema, check_stateless. No more.
  • An adversarial test for every documented failure mode (and a regression test for each fixed bug).
  • Three dependencies: numpy, pandas, scikit-learn. Nothing else.

If schema-firewall is missing a check you need, the library is wrong for your use case. Build the check in-line. Its surface will not grow to absorb it.


When to use each check

You did this Run this
Built any feature-engineering function that reads the full frame check_stateless(pipeline_fn, raw)
Joined multiple datasets with different origins / schemas / timestamps check_schema(X, SchemaContract(forbidden_columns=…))
Want a fast sanity gate before training check_leakage(X, y) on the final feature frame

What it caught in production (dogfood)

The schema-firewall checks are the same ones used by the NYC Real Estate Predictor external benchmark against NYC.gov 2024 Rolling Sales data. The flagship benchmark uses schema-firewall as a dependency, not a vendored copy. When the library breaks, the benchmark breaks. This is by design.


Attribution

Extracted from the firewall layer of the NYC Real Estate Predictor's external benchmark. The scoring-determinism pattern comes from the Protocol-based core of the Job Decision Engine project. Credit for the underlying problem classes goes to:

  • DeGrave et al. (Nature Machine Intelligence, 2021) — COVID X-ray shortcut learning
  • Rosenblatt et al. (Nature Communications, 2024) — connectome leakage
  • Ramadan et al. (JAMIA, 2024) — clinical label-leakage framework
  • YaG320 — Santander "magic" competition kernel

License

MIT. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

schema_firewall-0.2.1.tar.gz (34.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

schema_firewall-0.2.1-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

Details for the file schema_firewall-0.2.1.tar.gz.

File metadata

  • Download URL: schema_firewall-0.2.1.tar.gz
  • Upload date:
  • Size: 34.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for schema_firewall-0.2.1.tar.gz
Algorithm Hash digest
SHA256 f2830d40751b04db25adfede933ccc3a4b90f34704b4657c83fd9d27dd8224eb
MD5 e8eeb0c48ae78a7de5a5994b69755093
BLAKE2b-256 f32b98f82f5e1f47cdedc8ecca8763cc4ce048447ae9a9a8ac23c8df286ce77d

See more details on using hashes here.

File details

Details for the file schema_firewall-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for schema_firewall-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 beb1229970abb7363b310447efb5421e356e9c6096bb8a2eb45041d9fbcb4f3e
MD5 594e47681832a244f21a15b579fc993b
BLAKE2b-256 dc013339abecb0365b08874a87bbc293e45addfa424888ecb28d423ad8baab37

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.2

2 files

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page