Skip to content

fix(lib): don't fabricate a supporting study with no identity and no content - #99

Merged
SkyeAv merged 1 commit into
mainfrom
skip-contentless-supporting-study
Aug 14, 2026
Merged

fix(lib): don't fabricate a supporting study with no identity and no content#99
SkyeAv merged 1 commit into
mainfrom
skip-contentless-supporting-study

Conversation

@SkyeAv

@SkyeAv SkyeAv commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Summary

inline_supporting_study emitted has_supporting_studies unconditionally, so a section declaring no publications shipped a fabricated Study on every edge.

Changes

  • What was changedinline_supporting_study now emits the has_supporting_studies struct only when it carries something. It takes a new identified argument (whether study_id names a real publication rather than falling back to the config filename), threaded from Tcode._provenance_ops as bool(publication_values). When a section is unidentified and has no routed UNSATISFIABLE_EDGE_FIELDS and no _pruned_by_class values, the struct is skipped. The sheet-name and row-number columns are dropped either way — they are never meant to reach the edge.

  • Why this change is needed — Without a publication, study_id falls back to the config filename, so the emitted Study was keyed my_table.yaml and its single StudyResult was a row index into a file the pipeline regenerates:

    "has_supporting_studies": {"approved_treats.yaml": {
      "id": "approved_treats.yaml", "name": "approved_treats.yaml",
      "has_study_results": [{"id": "approved_treats.yaml#row6848", "name": "row 6848"}]}}

    Biolink defines has supporting studies as "studies that produced information used as evidence". Nothing in NCATSTranslator/translator-ingests models evidence this way — there a Study is a real cohort (ICEES), dataset (COHD), trial (CTKP), or text-mining group (SemMedDB/TMKP) carrying typed StudyResult slots, never a struct keyed by a filename. Asserting a study that never existed, on every edge, is worse than emitting nothing.

  • How it affects the project — Narrow and additive. A section with a real publication keeps the struct byte-for-byte unchanged; PMID:123#Table_S7 row 12 is genuine provenance and that is the case the feature exists for. A section with statistics to preserve also keeps it, published or not — the rescue path is the whole reason the filename fallback exists, and losing a class-refused value would be worse than naming its carrier after a config file. Only the empty-and-anonymous combination changes.

    Note for downstream: because prune_to_class creates _pruned_by_class at the schema level, any frame carrying a prunable column (today, species_context_qualifier on a class that lacks the slot) still counts as having content. Graphs will see this fix take effect once that column stops being derived.

Testing

  • Commands run and resultsuv run pytest → 958 passed, 1 skipped. The single failure (tests/test_agent_eval.py::test_gepa_metric_returns_prediction) is pre-existing and reproduces identically on a clean main checkout. uv run ruff check . → passed. uv run ruff format --check . → passed. uv run pyright → 0 errors. Coverage of src/tablassert/lib.py holds at 99% with the same six uncovered lines as baseline, so the new branch is fully exercised and no gap was introduced.
  • Unit tests added/updated — three direct inline_supporting_study tests in tests/test_lib.py, covering each arm of the decision: unidentified + empty (no struct, row/sheet still consumed), unidentified + pruned statistics (struct kept, description intact), and identified + empty (struct kept, no description key).
  • Integration tests added/updated — none needed; the existing end-to-end sections all declare publications and continue to pass unchanged, which is the no-regression evidence.
  • Manual testing performed — verified against a real tablassert build-kg in a downstream graph: edges lose the contentless struct while published-section edges keep theirs.

Related Issues

  • None

Checklist

  • Tests pass
  • Code style compliant
  • Documentation updated
  • No breaking changes

🤖 Generated with Claude Code

https://claude.ai/code/session_014gQKmpqbfNTVPfdzYw97L2

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 638a9741-c803-4e4b-875e-25b54b80b62f


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.

@SkyeAv
SkyeAv force-pushed the skip-contentless-supporting-study branch from dc8227e to 3a27ae7 Compare August 14, 2026 19:03
SkyeAv added a commit to glusman-team/dakp that referenced this pull request Aug 14, 2026
)

Every DAKP edge resolves to ChemicalEntityToDiseaseOrPhenotypicFeatureAssociation, so
an annotation name only reaches the edge if THAT class declares the slot. Two did not,
and Tablassert's prune_to_class nulled them and stringified them into the inlined
StudyResult description as "name=value":

    "has_supporting_studies": {"approved_treats.yaml": {..., "has_study_results":
      [{"id": "approved_treats.yaml#row6848", "description":
        "supporting_documents=<url>, <url>"}]}}

Nothing in NCATSTranslator/translator-ingests models evidence that way -- there a Study
is a real cohort (ICEES), dataset (COHD), trial (CTKP), or text-mining group
(SemMedDB/TMKP) carrying TYPED StudyResult slots. DAKP has no studies at all: the one
above is a config filename and its result a row index into a regenerated file.

Both values now ride the edge in slots the class actually holds:

- supporting_documents -> has_evidence. The Biolink slot is deprecated and attached to
  no association class. has_evidence (list[str], range information content entity) now
  carries BOTH DailyMed granularities -- the set label URL and the #<loinc> section URL
  -- from one new supporting_spl_evidence column built by spl_evidence_pipe(). It has
  to be one column: Tablassert applies annotations as with_columns(...alias(name)) in
  declaration order with no duplicate check, so a second has_evidence entry would
  SILENTLY overwrite the first. The per-granularity columns stay in the TSV as the
  debuggable split, just unannotated.

- number_of_cases -> evidence_count. number_of_cases is declared on
  EntityToDiseaseAssociation, a SIBLING of DAKP's class rather than an ancestor, so no
  class carries both it and disease_context_qualifier. evidence_count is on Association
  itself and means the same thing for a FAERS report count.

source_score keeps folding into supporting_text on purpose: has_confidence_score is
mechanically available, but the column is the max NER SPAN score -- confidence that a
mention was recognized, not that the statement is true -- so promoting it would mislead
any consumer that weights edges by confidence.

Two new config guards lock this in: annotation names must be unique per table, and
every annotated Biolink slot must be one the resolved association class declares (the
class is derived via derived_edge_category + resolve_association_class, not hardcoded).

After this, the only thing left in the study description is the auto-derived
species_context_qualifier, which Tablassert drops in a follow-up; SkyeAv/Tablassert#99
then removes the contentless struct itself.


Claude-Session: https://claude.ai/code/session_014gQKmpqbfNTVPfdzYw97L2

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…content

`inline_supporting_study` emitted `has_supporting_studies` unconditionally, so a
section declaring no `publications` got a Study keyed by its own config filename
(`my_table.yaml`) whose single StudyResult was a row index into a file the pipeline
regenerates.

Biolink defines `has supporting studies` as "studies that produced information used
as evidence", and nothing in NCATSTranslator/translator-ingests models evidence that
way -- there a Study is a real cohort (ICEES), dataset (COHD), trial (CTKP), or
text-mining group (SemMedDB/TMKP) carrying typed StudyResult slots, never a key/value
dump keyed by a filename.

The struct is now emitted only when it carries something. A section with a real
publication keeps it unchanged (`PMID:123#Table_S7 row 12` is genuine provenance), as
does any section with routed UNSATISFIABLE_EDGE_FIELDS statistics or class-pruned
values to preserve -- the rescue path is why the filename fallback exists at all. The
sheet-name and row-number columns are consumed either way; they never reach the edge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014gQKmpqbfNTVPfdzYw97L2
@SkyeAv
SkyeAv force-pushed the skip-contentless-supporting-study branch from 3a27ae7 to 8c5b2f7 Compare August 14, 2026 19:21
@SkyeAv
SkyeAv merged commit 217c322 into main Aug 14, 2026
5 checks passed
SkyeAv added a commit that referenced this pull request Aug 14, 2026
Cut 12.0.0 and bump the package version in pyproject.toml, uv.lock, and
CITATION.cff.

Major, not minor: three breaking changes accumulated since 11.0.0 — the
agent now builds into a caller-owned graph config instead of maintaining
an internal registry (the graph_registry module, <state-dir>/graph.yaml,
and the rebuild-agent-graph subcommand are gone), the QC embedding stage
migrated from BioBERT to SapBERT with the model cache moving to
.tablassert/sapbert/ (#93), and build-fullmap's prebuilt extraction moved
into the Rust extension with force-build-contract validation before
install (#97).

The changelog needed restructuring, not just promoting: #93 and #97
appended their entries inside the 11.0.0 section, so the SapBERT,
abbreviation-stage, and prebuilt-extraction entries are moved into 12.0.0
where they belong, and the caller-owned-graph-config change landed with no
entry at all, so its breaking change (the removed --fullmap flag and
rebuild-agent-graph subcommand, in-place graph mutation, orphaned
registries, and the graph=/graph_path= API) is written up here.

Also ships in this release: approval_ids as a curated pass-through edge
field, build-kg --threads for parallel fullmap reads scaling past the 16
record shards (#101), release-mode dropping of zero effect-size edges
(#95), the species_context_qualifier derivation fix, and the fix that
stops fabricating a supporting study for sections with no publication and
nothing to preserve (#99).
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