Skip to content

Gate snippets truncated at a genus abbreviation; fix the four found (#299) - #303

Merged
realmarcin merged 2 commits into
mainfrom
snippet-quality-gate-299
Aug 3, 2026
Merged

Gate snippets truncated at a genus abbreviation; fix the four found (#299)#303
realmarcin merged 2 commits into
mainfrom
snippet-quality-gate-299

Conversation

@realmarcin

@realmarcin realmarcin commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #299.

Measuring first killed the obvious fix

#299 proposed a minimum-length rule for evidence snippets. The data says that would have been wrong. Of 5,106 snippets, 627 are under 40 characters — and the shortest ones are correct:

snippet length why it is right
pH 6.8 6 evidence for a pH condition
150 g/L 7 a concentration
Firmicutes 10 a taxon
glucose 10 g/L 14 a medium component

A length floor would have buried a real signal under hundreds of false positives. Short snippets are the normal shape of evidence for quantitative and nominal fields.

The signature that works

The snippet ends in a single capital letter and a period, and the cached reference continues with a lowercase word. That second condition is what makes it precise — it separates a truncated genus from a sentence that legitimately ends in an abbreviation or a unit.

Across the whole KB it flags 4 snippets, all genuine, zero false positives. 27 °C. and 1.0 ppm F. are correctly ignored, because nothing lowercase follows them.

The four, each truncated at the word it existed to identify

The main products from C.          →  + ljungdahlii fermentation at a pH of 6.0…
…by P. aeruginosa and G.           →  + sulfurreducens respectively.        (×2)
benzoate-fermenting bacterium S.   →  + gentianae is approximately -45 kJ…

All extended to a clean clause boundary and re-verified verbatim against their caches.

Two deliberate non-decisions, recorded rather than lost

The U+2010 signature is not gated. #299's other mechanical cause — a snippet stopping immediately before a non-ASCII character present in the cache — fires exactly once repo-wide, on a snippet that ends a complete clause before an em-dash. It is currently all false positive, so gating it would cost trust for no benefit. The reasoning lives in the test's docstring so the next person does not re-derive it.

Snippets absent from their cache are skipped, not reported. There are 291 of them. That is a stale or mis-rendered cache — a different defect, already bucketed by the snippet audit (RENDERING / MISMATCH / NOCONTENT per #257/#265). Folding it into a truncation gate would make this test untrustworthy for its actual purpose.

Verification

  • Mutation-checked: re-truncating one snippet fails exactly that file's parametrised case, and nothing else.
  • The gate runs per-file (308 cases), so a failure names the record and prints what the cache says next.
  • 899 tests pass; black, ruff, mypy clean; all three edited records pass schema validation.

🤖 Generated with Claude Code


Review round

Reviewing the gate found a defect in the gate itself: its cache resolution was filesystem-order dependent.

references_cache/ holds 379 .md, 214 .txt and 78 .json files, and 63 references carry more than one — usually a .md beside a .txt for the same PMID. Those are not interchangeable: per #265 the .md typically holds open-access full text while the .txt may be only the abstract. Taking the first glob match read an arbitrary one.

Measured by running the scan twice with opposite preferences:

first match snippets located
.md 4471
.txt 4400

71 snippets were checked or silently skipped depending on directory iteration order, which is not stable across machines. It never surfaced as a failure because the four truncations fixed here are flagged under either preference — it was latent, and a future truncation among those 71 would have been caught or missed by accident.

All non-.json variants are now searched. They are deliberately not concatenated: this check inspects what follows the snippet, so joining two files would manufacture a continuation across the boundary. .json files are CrossRef metadata rather than prose and are excluded outright.

Filed as #306, which also flags that evidence_snippet_audit.py and the vendored reference validator are worth checking for the same ordering dependence — if they resolve caches this way, their RENDERING/MISMATCH/NOCONTENT buckets inherit it.

Incidentally about twice as fast (6.7s → 3.7s): the cheap regex on the snippet now short-circuits before any cache file is read.

Mutation-checked again — re-truncating a snippet whose reference has only a .md cache still fails exactly its file's case. 899 tests pass; lint clean.

…299)

**The obvious fix would have been wrong.** #299 proposed a minimum-length rule
for evidence snippets. Measuring first killed that idea: of 5106 snippets, 627
are under 40 characters and the shortest ones are correct — `pH 6.8`, `150 g/L`,
`glucose 10 g/L`, `Firmicutes`. Those are exactly the right evidence for a pH, a
concentration or a taxon, so a length floor would have buried a real signal under
hundreds of false positives.

The signature that does work is specific and self-verifying: the snippet ends in
a single capital letter and a period, *and the cached reference continues with a
lowercase word*. The second half is what gives it precision — it separates a
truncated genus from a sentence that legitimately ends in an abbreviation or a
unit. Across the whole KB it flags 4 snippets, all genuine, with no false
positives; `27 °C.` and `1.0 ppm F.` are correctly ignored because nothing
lowercase follows them.

All four are fixed. Each had stopped at precisely the word it existed to
identify:

  The main products from C.        -> + ljungdahlii fermentation at a pH of 6.0…
  …by P. aeruginosa and G.         -> + sulfurreducens respectively.   (x2)
  benzoate-fermenting bacterium S. -> + gentianae is approximately -45 kJ…

The second signature from #299 — stopping immediately before a non-ASCII
character in the cache, the U+2010 case — is deliberately *not* gated. It fires
once repo-wide, on a snippet that ends a complete clause before an em-dash, so it
is currently all false positive; the reasoning is recorded in the test's
docstring rather than lost.

Snippets absent from their cache (291) are skipped rather than reported: that is
a stale-or-mis-rendered cache, which the snippet audit already buckets, and
conflating it with truncation would make this gate untrustworthy.

Mutation-checked: re-truncating one snippet fails exactly its file's case.
899 tests pass; lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 07:17
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Network integrity findings

Reporting only — this check does not fail the build (see issue #273).

Network Integrity Audit Report
================================================================================


ANME_SRB_Anaerobic_Methanotrophic_Syntrophic_Consortia
--------------------------------------------------------------------------------
  UNKNOWN_SOURCE: Source taxon 'ANME-1' not found in taxonomy section
  UNKNOWN_TARGET: Target taxon 'Desulfofervidus' not found in taxonomy section
  UNKNOWN_SOURCE: Source taxon 'ANME-2a' not found in taxonomy section
  UNKNOWN_TARGET: Target taxon 'Seep-SRB1' not found in taxonomy section

Total: 4 issues

Bay_Area_Sewage_SARS_CoV2_Surveillance_Community
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'SARS-CoV-2 sewage population' has no interactions

Total: 1 issues

Chlorella_Keystone_Taxa_Antifungal_SynCom
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'Brevibacterium' has no interactions
  DISCONNECTED: Taxon 'Duganella' has no interactions
  DISCONNECTED: Taxon 'Fusarium oxysporum' has no interactions
  DISCONNECTED: Taxon 'strawberry' has no interactions
  DISCONNECTED: Taxon 'tomato' has no interactions

Total: 5 issues

Crucian_Carp_Gut_Disease_Resistance_SynCom
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'Cetobacterium' has no interactions
  DISCONNECTED: Taxon 'Paraclostridium' has no interactions
  DISCONNECTED: Taxon 'Pseudomonas' has no interactions

Total: 3 issues

Ensifer_YF2_Sphingobacterium_Y2_Polyethylene_Degrading_Consortium
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'Chryseobacterium sp. MF1' has no interactions

Total: 1 issues

Lunar_Martian_Simulant_PGPB_Lettuce_SynCom
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'Azotobacter chroococcum 76A' has no interactions
  DISCONNECTED: Taxon 'Kosakonia pseudosacchari TL13' has no interactions
  DISCONNECTED: Taxon 'Methylobacterium populi VP2' has no interactions
  DISCONNECTED: Taxon 'Priestia megaterium EL5' has no interactions

Total: 4 issues

Lunar_Simulant_Phosphate_Solubilizing_Bacteria_Nicotiana
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'Bacillus licheniformis (ATCC 11946)' has no interactions
  DISCONNECTED: Taxon 'Bacillus megaterium (AS1.217)' has no interactions
  DISCONNECTED: Taxon 'Bacillus mucilaginosus (AS1.232)' has no interactions
  DISCONNECTED: Taxon 'Bacillus subtilis (CMCC 63501)' has no interactions
  DISCONNECTED: Taxon 'Pseudomonas fluorescens (ATCC 13525)' has no interactions

Total: 5 issues

Moss_Microbe_Complex_Regolith_Biofertilizer
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'Bacillus cereus' has no interactions
  DISCONNECTED: Taxon 'Devosia sp.' has no interactions
  DISCONNECTED: Taxon 'Pseudomonas monteilii' has no interactions

Total: 3 issues

Pinus_armandii_Endophytic_Biocontrol_SynCom
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'Bacillus subtilis NA11' has no interactions
  DISCONNECTED: Taxon 'Bacillus velezensis NA3' has no interactions
  DISCONNECTED: Taxon 'Bacillus velezensis OB3' has no interactions
  DISCONNECTED: Taxon 'Paenibacillus terrae RE7' has no interactions
  DISCONNECTED: Taxon 'Pseudomonas koreensis RC1' has no interactions

Total: 5 issues

SynCom_ARC_Peanut_Aflatoxin_Nodulation
--------------------------------------------------------------------------------
  DISCONNECTED: Taxon 'Aspergillus flavus' has no interactions
  DISCONNECTED: Taxon 'Enterobacter (ARC strain El17-4B09)' has no interactions
  DISCONNECTED: Taxon 'peanut' has no interactions

Total: 3 issues

The full report is attached to the workflow run as an artifact.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a targeted regression test to detect evidence snippets that were mechanically truncated at an abbreviated genus (e.g., ending in C. where the cached reference continues with the species epithet), and fixes the four affected community records by extending their snippets to full, meaningful clause boundaries.

Changes:

  • Add tests/test_snippet_truncation.py to gate the “genus abbreviation truncation” signature described in #299.
  • Update three community YAMLs to replace truncated snippets (… C., … G., … S.) with complete, cache-verbatim clauses (4 occurrences total).
  • Ensure the new gate skips uncached references to avoid conflating truncation with cache-rendering/caching defects.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
tests/test_snippet_truncation.py New parametrized test that flags snippets ending in a single-letter genus abbreviation when the cache continues with a lowercase epithet.
kb/communities/Syntrophus_Methanospirillum_Gentianae_Benzoate_Coculture.yaml Extend truncated S. snippet to include S. gentianae … clause.
kb/communities/Geobacter_Pseudomonas_Formate_Fumarate_Electroactive_Coculture.yaml Extend two duplicated truncated G. snippets to include G. sulfurreducens respectively.
kb/communities/Clostridium_Ljungdahlii_Kluyveri_Syngas_Alcohol_Coculture.yaml Extend truncated C. snippet to include C. ljungdahlii … clause.
Suppressed comments (1)

tests/test_snippet_truncation.py:53

  • Consider memoizing _cache_text() (e.g., with functools.lru_cache) so each reference’s cache content is read/normalized at most once per test run.
def _cache_text(reference: str) -> str | None:

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_snippet_truncation.py Outdated
Comment on lines +55 to +58
key = reference.replace("PMID:", "PMID_").replace("doi:", "DOI_").replace("/", "_")
for candidate in CACHE.glob(key + ".*"):
return " ".join(candidate.read_text(errors="replace").split())
return None
Comment on lines +37 to +38
import re
from pathlib import Path
Review of this PR found the gate's own cache resolution was
filesystem-order dependent. `references_cache/` holds 379 .md, 214 .txt and 78
.json files, and 63 references carry more than one — usually a .md alongside a
.txt for the same PMID. Those are not interchangeable: per #265 the .md
typically holds open-access full text while the .txt may be only the abstract.

Taking the first glob match therefore read an arbitrary one. Measured by running
the scan twice with opposite preferences, 4471 snippets are locatable via .md
against 4400 via .txt — so **71 snippets were checked or silently skipped
depending on directory iteration order**, which is not stable across machines.

It never surfaced as a failure because the four truncations this PR fixes are
flagged under either preference. It was latent: a future truncation among those
71 would be caught or missed by accident.

All non-.json variants are now searched. They are deliberately not concatenated —
this check inspects what *follows* the snippet, and joining two files would
manufacture a continuation across the boundary. .json files are CrossRef metadata
rather than prose and are excluded outright.

Incidentally about twice as fast (6.7s -> 3.7s), because the cheap regex on the
snippet now short-circuits before any cache file is read.

Filed as #306, which also notes that evidence_snippet_audit.py and the vendored
reference validator are worth checking for the same ordering dependence — if they
resolve caches this way, their RENDERING/MISMATCH/NOCONTENT buckets inherit it.

Mutation-checked again: re-truncating a snippet whose reference has only a .md
cache still fails exactly its file's case. 899 tests pass; lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@realmarcin
realmarcin merged commit a797814 into main Aug 3, 2026
7 checks passed
@realmarcin
realmarcin deleted the snippet-quality-gate-299 branch August 3, 2026 02:52
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.

Nothing guards evidence-snippet quality: a 16-character fragment validates as well as a full clause

2 participants