Skip to content

Validation

bikescore-bna validates its output stage by stage against a reference directory — a ground-truth set of per-stage parquets. References are exported from brokenspoke-analyzer, the original SQL/PostGIS implementation.

A reference directory holds one parquet per stage output:

<reference>/
  parse/ways_raw.parquet   parse/nodes.parquet
  stress/stress.parquet
  scores/scores.parquet
  neighborhood/neighborhood.parquet  …
  destinations/dest_<type>.parquet   …

Run validation (CLI)

Score a city and compare every stage output against the reference:

bikescore-bna validate <city> --reference path/to/reference

<city> is a path to a city directory (its inputs are read from <city>/datasets, or pass --datasets DIR). Add --stage stress to validate a single stage (a faster partial run). Other options mirror score: --scenario, --set, --set-file.

The command prints a per-stage table and exits non-zero if any stage differs:

┏━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━━┓
┃ stage / case ┃ matched ┃ differing ┃ explained ┃ +comp ┃ +ref ┃ result ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━━━┩
│ stress       │ 831     │ 0         │ 0         │ 0     │ 0    │ PASS   │
└──────────────┴─────────┴───────────┴───────────┴───────┴──────┴────────┘

Known SQL divergences are annotated as expected (the explained column) and do not fail the run; pass --strict to treat them as differences.

Run validation (Python)

The same harness is a library function — score a city, then compare the result:

from bikescore_bna import build_config, discover_inputs, score_city
from bikescore_bna.parity import validate_result
from bikescore_bna.deviations import KNOWN_DEVIATIONS

result = score_city(discover_inputs("aspen-colorado/datasets"), build_config("default"))

for sp in validate_result(
    result, "path/to/reference", city="aspen-colorado", deviations=KNOWN_DEVIATIONS
):
    if sp.report is None:
        continue  # stage skipped (reference/computed file absent)
    print(sp.case, "PASS" if sp.passed else "FAIL")
    if not sp.passed:
        sp.report.print()   # detailed column/row diff

validate_result returns one StageParity per stage output (in pipeline order); stages=[...] restricts the comparison to named stages.

Validation report fields

Each StageParity.report is a ValidationReport:

Field Description
passed True when all diffs are within tolerance or explained by known deviations
rows_total Rows matched (inner join) and compared
n_computed / n_reference Row counts on each side before the join
rows_only_computed Keys present in output but not in reference
rows_only_reference Keys present in reference but missing from output
rows_differing Matched rows with a column-level difference
deviation_explained_rows Differing rows fully accounted for by known deviations

Tolerance and deviations

Numeric columns compare exactly by default (compare_dataframes(tolerance=...) loosens it). Known permanent divergences from the SQL reference live in bikescore_bna.deviations (KNOWN_DEVIATIONS) and are excluded from the pass/fail decision when passed in.

See Differences from brokenspoke-analyzer for the full list of accepted deviations and the reasoning behind each one.