Skip to content

fix(lib): isolate build-mode store caches and tolerate non-numeric cells in release filters - #140

Merged
SkyeAv merged 2 commits into
mainfrom
fix/release-mode-store-cache
Sep 4, 2026
Merged

fix(lib): isolate build-mode store caches and tolerate non-numeric cells in release filters#140
SkyeAv merged 2 commits into
mainfrom
fix/release-mode-store-cache

Conversation

@SkyeAv

@SkyeAv SkyeAv commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

tablassert build-kg --release shipped applied_to_treat edges with number_of_cases < 25 even though the release-mode case-count filter exists. Reproduced against installed 16.6.0: a 1-case edge shipped in a --release KG. Two independent bugs, both fixed here.

Bug 1 — section-parquet cache key ignores build mode

In build_graph_pipeline (stage 3), each section's cached subgraph parquet was named STORE / f"{mkhash(s)}.parquet" where s is only the section config dict; release was injected into Tcode.model_validate({**s, ..., "release": release}) outside the hashed dict. Tcode.collect() then quick-exits on any existing store file (if self.store.is_file(): return self.store), skipping the entire op chain — including the release filters wired in _source_ops. So a parquet cached by a non-release build (or a pre-16.6.0 build, where the filter silently no-op'd per #137) was reused verbatim in --release builds: cache poisoning shipped the 1-case edge.

--head already dodged this class of bug with a distinct .head.parquet name. The fix generalizes that idiom: a small _section_store_path(h, head, release, qc) helper gives each build-mode flag its own suffix (<h>.parquet, <h>.head.parquet, <h>.release.parquet, <h>.qc.parquet), composing when flags combine (<h>.head.release.qc.parquet).

--qc is included in the fix, not deferred: fullmap_audit drops QC rejects from the output LazyFrame (qc.py:138-139), so a --qc build's cached parquet genuinely differs in content from a non-qc one — the same cache-poisoning class, fixed for free by the same helper.

Bug 2 — strict cast crashes on header rows

drop_low_number_of_cases and drop_zero_effect_size filtered with pl.col(col).cast(pl.Float64) (strict). The csv op reads sources with has_header=False, so a TSV's header row flows through as a data row and the strict cast raised polars.exceptions.InvalidOperationError on the non-numeric header cell (reproduced: conversion from str to f64 failed in column 'number_of_cases' ... ["case_count"]).

Both ops now cast with strict=False — the clean_numeric idiom. Non-numeric cells become null and are kept (ne_missing(0.0) for effect size; .ge(threshold).fill_null(True) for case counts, since polars 1.40 has no ge_missing): header rows die later at entity resolution as they did before the filters existed. This tolerates arbitrary non-numeric cells, including the pending downstream TSV rename of the case_count header to number_of_cases. drop_not_significant casts to String and was never affected.

Test coverage

  • test_drop_low_number_of_cases_keeps_non_numeric_cells — header-cell row and nulls kept, "10" dropped, "30" kept.
  • test_drop_zero_effect_size_keeps_non_numeric_cells — same tolerance.
  • test_section_store_path_suffixes_every_build_mode — every flag combination gets a distinct, composing cache name.
  • test_tcode_collect_quick_exit_never_serves_another_mode_cached_parquet — a non-release cached parquet never quick-exits a release build; once the release parquet exists, the quick exit serves it.

uv run pytest tests/test_lib.py -k "release or number_of_cases or effect_size or store or not_significant or head_mode" → 32 passed; tests/test_cover_cli.py → 52 passed; ruff check/format and pyright clean.

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: d4438638-3d95-4642-8a18-8831aaa03074


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…lls in release filters

Two release-mode bugs shipped applied_to_treat edges with
number_of_cases < 25 in --release KGs:

1. The section-parquet cache key ignored build mode. Stage 3 named each
   section's cached subgraph <mkhash(s)>.parquet with release injected
   outside the hashed dict, and Tcode.collect quick-exits on any existing
   store file -- so a parquet cached by a non-release (or pre-16.6.0,
   filter-no-op) build was reused verbatim in --release builds, skipping
   the release filters. --head already dodged this with .head.parquet;
   _section_store_path generalizes that: each of head/release/qc gets
   its own suffix, composing when combined. qc belongs because
   fullmap_audit drops rejects from the cached parquet too.

2. drop_low_number_of_cases and drop_zero_effect_size cast with strict
   Float64, but the csv op reads sources with has_header=False, so a
   TSV header row flows through as data and the strict cast raised
   InvalidOperationError on the non-numeric header cell. Both ops now
   cast with strict=False (the clean_numeric idiom); non-numeric cells
   become null and are kept, dying later at entity resolution as before.

Tests: non-numeric/null tolerance for both drop ops, per-mode store-path
suffixes, and a quick-exit regression proving a non-release parquet is
never served to a release build.
@SkyeAv
SkyeAv force-pushed the fix/release-mode-store-cache branch from fd9c24b to ea5a0b4 Compare September 4, 2026 20:37
@SkyeAv
SkyeAv merged commit 965f30f into main Sep 4, 2026
5 checks passed
@SkyeAv
SkyeAv deleted the fix/release-mode-store-cache branch September 4, 2026 20:37
SkyeAv added a commit that referenced this pull request Sep 4, 2026
Cut 16.6.2 and bump the package version in pyproject.toml, uv.lock, and
CITATION.cff.

Patch: two fix PRs ship. `number_of_cases` is now emitted as a JSON
integer instead of raw source text — the slot is typed `int` on
biolink-model but sat outside `lib.numeric_columns`, so string cells
shipped on the edge NDJSON and left the graph type-inconsistent edge to
edge (#139). `--release` builds no longer reuse section parquets cached
by other build modes: `--head`, `--release`, and `--qc` now cache under
distinct suffixes via `_section_store_path`, closing a cache-poisoning
bug where release filters were silently skipped and `applied_to_treat`
edges with `number_of_cases < 25` shipped (#140). The release-mode
`drop_low_number_of_cases` and `drop_zero_effect_size` filters also
tolerate non-numeric cells (e.g. a TSV header row read as data) instead
of crashing with `InvalidOperationError` (#140).

Changelog:
- Versioned the Unreleased section as 16.6.2 and gave the
  `number_of_cases` entry its missing PR link (#139).

Docs: none needed here — #140's `cli.py` change is an internal cache-key
helper and #139 changes emission types only; both are fully documented
in their changelog entries.

Testing:
- uv run pytest -q -> 1253 passed, 3 skipped (96% coverage)
- uv run ruff check . && uv run ruff format --check . && uv run pyright -> clean / 0 errors
- uv lock --check -> up to date
- uv run mkdocs build --strict -> clean
SkyeAv added a commit to glusman-team/dakp that referenced this pull request Sep 4, 2026
Picks up the 16.6.2 fix for --release section-parquet cache poisoning
(SkyeAv/Tablassert#140): the cache key previously ignored the build mode,
so --release builds could reuse parquets cached by --head/--qc/pre-16.6.0
builds and ship applied_to_treat edges with number_of_cases < 25. --qc
gets its own suffix for the same reason (fullmap_audit drops rejects from
the cached parquet).

Floor bumped 16.6.0 -> 16.6.2 and uv.lock re-locked. Verified:
uv run pyright (0 errors), uv run pytest (1058 passed, 100.00%
coverage), uvx ruff check (clean).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant