diff --git a/.gitignore b/.gitignore index 9b30ece..091379c 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,5 @@ Thumbs.db # uv .uv/ uv.lock.bak + +runs/CL_9999999 diff --git a/clara_workflow/agent_instructions.md b/clara_workflow/agent_instructions.md index 837bba0..b2357f3 100644 --- a/clara_workflow/agent_instructions.md +++ b/clara_workflow/agent_instructions.md @@ -41,6 +41,9 @@ Normalise ids as follows: The routing payload contains `targets`. Each target has one of these routes: - `ntr` — new term bundle; contains decomposable text plus structural context +- `text_revision` — revised prose on an existing term; decomposable +- `refs_added` — prose on an existing term that did **not** change, with one or + more references newly attached to it - `relationship` — a single atomic structural axiom to check - `synonym` — a single atomic synonym axiom to check @@ -56,6 +59,8 @@ Relevant target fields: - `term_level_candidate_refs` - for `ntr`: `textual_changes` (legacy alias `definition_changes`) and `relationship_changes` +- for `text_revision`: `textual_changes`, plus `prior_value` on each change +- for `refs_added`: `textual_changes` and `refs_added` - for `relationship` / `synonym`: `change` ### Contract rules @@ -108,6 +113,32 @@ For `ntr` targets: 4. Also take each `added` change from `relationship_changes` and convert it into one atomic `core` assertion. +### Route: `text_revision` + +The prose changed on an existing term. Treat it like the textual half of an +`ntr` target: + +1. Decompose the `added` change's `value` into atomic assertions. +2. Tag each `core` or `background` as above. + +`prior_value` is the previous wording, provided as context only. Do not verify +it, and do not report assertions that are unchanged between the two wordings as +new claims — the reviewable content is the current `value`. + +### Route: `refs_added` + +The prose did **not** change; a reference was attached to it. The claim under +review is that the *newly added* reference supports the existing text. + +1. Decompose the `value` into atomic assertions as usual. +2. Verify them **only** against `refs_added` — not the full ref list on the + axiom. The pre-existing refs were justified when they were added. +3. If every assertion is `uncertain` against the new refs, that is a real + result: the added reference does not visibly support the definition. Say so + in the report rather than falling back to the older refs. + +Do not re-verify the definition against its whole reference set on this route. + ### Route: `relationship` For `relationship` targets: @@ -144,14 +175,28 @@ Examples: - `synonym_related`: - "`{value}` is a related synonym of `{term_label}`." -For `equivalent_class` routed as a structural target: +For `equivalent_class` routed as a structural target, `value` carries the bare +axiom in Manchester syntax, e.g. -- If the routed change does not expose enough semantics to verbalise the - logical definition faithfully, create one `core` assertion noting that the - equivalent-class axiom for the term requires support. -- If you cannot verify it from the exposed routing data, it is acceptable to - leave that assertion `uncertain` with a note explaining that the routed - payload does not provide enough structure for faithful verbalisation. +```text +buccal mucosa cell EquivalentTo keratinocyte and (part of some mouth mucosa) +``` + +`EquivalentTo` states necessary **and sufficient** conditions, so it makes two +claims. Emit **both** as separate `core` assertions: + +- necessary — "Every `{term_label}` is a `{genus}` that `{differentia}`." +- sufficient — "Every `{genus}` that `{differentia}` is a `{term_label}`." + +The sufficient direction is the one editors get wrong and the one literature +rarely states outright. Do not mark it `pass` on evidence that only supports +the necessary direction, and do not collapse the two into a single assertion. +If the axiom nests operators too deeply to verbalise faithfully, verbalise what +you can and note the residue — do not silently drop a conjunct. + +Only if `value` is absent or empty may you fall back to a single `core` +assertion noting that the equivalent-class axiom requires support, left +`uncertain` with a note that the payload exposed no axiom text. ## Reference scope per assertion @@ -165,7 +210,16 @@ Reference selection rules: `term_level_candidate_refs`. - For `relationship` targets, prefer `candidate_refs`; if empty, fall back to `term_level_candidate_refs`. +- For `equivalent_class` changes, the refs are the **text definition's** refs. + A logical axiom carries no dbxrefs of its own, so the producer fills + `candidate_refs` from the definition — the logical definition formalises the + prose, so the prose's evidence is what has to support it. Treat an empty list + here as "the definition itself is uncited", not as a routing error. - For `synonym` targets, use `candidate_refs`. +- For `text_revision` targets, use `candidate_refs`; if empty, fall back to + `term_level_candidate_refs`. +- For `refs_added` targets, use `refs_added` only. Never widen to the axiom's + pre-existing refs. - Assume these ref lists were prefiltered upstream; only normalize case and slash formatting needed by the search tools. diff --git a/clara_workflow/stage1/README.md b/clara_workflow/stage1/README.md index 728a993..fe22d7b 100644 --- a/clara_workflow/stage1/README.md +++ b/clara_workflow/stage1/README.md @@ -37,6 +37,38 @@ Any of the following, on either side (`added` or `removed`): Each of these carries its own `hasDbXref` list in the parser output; stage 2/3 check that list when deciding whether the change is justified. +`definition_refs()` reads each term's definition dbxrefs out of the **head** edit +file rather than the diff, because `robot diff` reports only changed axioms: a PR +that adds a logical definition to an untouched term carries no definition axiom, +yet the text definition it formalises is what justifies it. These land in +`by_term[*].definition_refs`. + +`equivalent_class` changes carry the axiom itself in `value`, as bare Manchester +syntax (`buccal mucosa cell EquivalentTo keratinocyte and (part of some mouth +mucosa)`). ROBOT's markdown diff already emits Manchester syntax; the parser just +strips the markdown links off the labels. + +## Paired text changes (`text_deltas()`) + +ROBOT reports an annotated axiom as an independent `Removed` + `Added` bullet, +and an OWL axiom's identity includes its annotations. So attaching one dbxref to +an untouched definition is indistinguishable from a rewrite unless the two sides +are paired and their values compared. + +`text_deltas()` does that pairing for `text_def` / `comment`, giving each a +status: + +| `status` | Meaning | What stages 2/3 should check | +|-------------|---------|---| +| `revised` | The prose changed. | The new prose, against the axiom's refs. | +| `refs_only` | The prose is byte-identical; only refs changed. | The existing prose, against `refs_added` **only**. | +| `added` | No matching removed side. | The prose, as a new claim. | +| `removed` | No matching added side. | Nothing — removals aren't justified by refs. | + +Without this split, a ref-only edit re-verifies an unchanged definition against +its entire reference list, and the actual claim — "this newly added paper +supports the existing definition" — never gets asked. + ## Not reviewed (kept in output for traceability, but skipped by `reviewable_changes()`) | `kind` | Why ignored | diff --git a/clara_workflow/stage1/__init__.py b/clara_workflow/stage1/__init__.py index bc1c3f6..84168f3 100644 --- a/clara_workflow/stage1/__init__.py +++ b/clara_workflow/stage1/__init__.py @@ -11,9 +11,12 @@ CONTENT_KINDS, DECOMPOSABLE_KINDS, decomposable_changes, + definition_refs, parse_diff_markdown, reviewable_changes, summarise_by_term, + text_deltas, + TextDelta, ) __all__ = [ @@ -21,7 +24,10 @@ "CONTENT_KINDS", "DECOMPOSABLE_KINDS", "decomposable_changes", + "definition_refs", "parse_diff_markdown", "reviewable_changes", "summarise_by_term", + "text_deltas", + "TextDelta", ] diff --git a/clara_workflow/stage1/extract.py b/clara_workflow/stage1/extract.py index 3b8d25e..c53157d 100644 --- a/clara_workflow/stage1/extract.py +++ b/clara_workflow/stage1/extract.py @@ -27,9 +27,11 @@ from clara_workflow.stage1.parse import ( Change, decomposable_changes, + definition_refs, parse_diff_markdown, reviewable_changes, summarise_by_term, + text_deltas, ) @@ -67,8 +69,13 @@ def extract( right_ref: str, edit_file: str, robot: str = "robot", -) -> list[Change]: - """Resolve two refs against `edit_file`, run robot diff, parse, return changes.""" +) -> tuple[list[Change], dict[str, list[str]]]: + """Resolve two refs against `edit_file`, run robot diff, parse. + + Returns the parsed changes plus the head-state definition refs per term, + which is where a logical definition's justification lives when the text + definition itself wasn't touched by the PR. + """ with tempfile.TemporaryDirectory() as td: tdp = Path(td) left = tdp / "left.owl" @@ -77,7 +84,10 @@ def extract( _git_show(repo, left_ref, edit_file, left) _git_show(repo, right_ref, edit_file, right) _robot_diff(left, right, diff_md, robot=robot) - return parse_diff_markdown(diff_md.read_text()) + return ( + parse_diff_markdown(diff_md.read_text()), + definition_refs(right.read_text()), + ) def main(argv: list[str] | None = None) -> int: @@ -94,7 +104,7 @@ def main(argv: list[str] | None = None) -> int: print(f"robot executable not found: {args.robot}", file=sys.stderr) return 2 - changes = extract( + changes, head_definition_refs = extract( repo=args.repo, left_ref=args.left, right_ref=args.right, @@ -109,10 +119,16 @@ def main(argv: list[str] | None = None) -> int: "changes": [_change_to_dict(c) for c in changes], "reviewable": [_change_to_dict(c) for c in reviewable], "decomposable": [_change_to_dict(c) for c in decomposable], + # Removed/added text axioms paired so a ref-only edit is distinguishable + # from a rewrite; consumed by the router to pick a route. + "text_deltas": [dataclasses.asdict(d) for d in text_deltas(changes)], "by_term": { tid: { **{k: v for k, v in entry.items() if k != "changes"}, "changes": [_change_to_dict(c) for c in entry["changes"]], + # Refs on the term's definition as it stands at the head ref — + # present whether or not this PR touched the definition. + "definition_refs": head_definition_refs.get(tid, []), } for tid, entry in summarise_by_term(changes).items() }, diff --git a/clara_workflow/stage1/parse.py b/clara_workflow/stage1/parse.py index 17fc4fd..a03bff0 100644 --- a/clara_workflow/stage1/parse.py +++ b/clara_workflow/stage1/parse.py @@ -120,6 +120,18 @@ def _iter_links(s: str) -> Iterator[tuple[str, str]]: yield m.group("text"), m.group("url") +def _bare_axiom(body: str) -> str: + """Reduce a diff bullet to a bare Manchester-syntax axiom. + + `robot diff --format markdown --labels true` already emits class expressions + in Manchester syntax; the labels just arrive wrapped in markdown links. + Dropping the links leaves the axiom as an editor would write it, e.g. + + buccal mucosa cell EquivalentTo keratinocyte and (part of some mouth mucosa) + """ + return re.sub(r"\s+", " ", _LINK_RE.sub(lambda m: m.group("text"), body)).strip() + + def _parse_bullet(line: str, term_id: str, term_label: str, side: str) -> Change: """Parse one top-level `- ...` bullet into a Change. @@ -165,9 +177,11 @@ def _parse_bullet(line: str, term_id: str, term_label: str, side: str) -> Change # EquivalentClasses. if " EquivalentClasses " in body or "EquivalentTo" in body: + # The logical definition is the claim to verify, so carry the axiom + # itself rather than dropping it (issue #2). return Change( term_id=term_id, term_label=term_label, side=side, - kind="equivalent_class", raw=raw, + kind="equivalent_class", value=_bare_axiom(body), raw=raw, ) # Annotation-style: [subj] [pred](IRI) @@ -326,3 +340,124 @@ def decomposable_changes(changes: list[Change]) -> list[Change]: without an intermediate decomposition step. """ return [c for c in reviewable_changes(changes) if c.kind in DECOMPOSABLE_KINDS] + + +# --- text change pairing --------------------------------------------------- + +@dataclass +class TextDelta: + """One decomposable text axiom, paired across the removed/added sides. + + `robot diff` reports an annotated axiom as an independent removed + added + bullet, and an OWL axiom's identity includes its annotations. So adding a + dbxref to an untouched definition looks exactly like a definition rewrite + unless the two sides are paired and their values compared. + + status: + revised — the prose changed (verify the new prose) + refs_only — the prose is byte-identical; only the refs changed (verify + the existing prose against `refs_added`, nothing else) + added — no matching removed side (new axiom on an existing term) + removed — no matching added side (axiom deleted) + """ + term_id: str + term_label: str + kind: str # text_def | comment + status: str # revised | refs_only | added | removed + value: str | None = None # the added (current) prose + prior_value: str | None = None # the removed (previous) prose + refs: list[str] = field(default_factory=list) # refs on the added side + refs_added: list[str] = field(default_factory=list) + refs_removed: list[str] = field(default_factory=list) + + +def text_deltas(changes: list[Change]) -> list[TextDelta]: + """Pair removed/added decomposable text axioms per (term, kind). + + Obsoleted terms and non-content kinds are excluded, matching + `reviewable_changes`. + """ + grouped: dict[tuple[str, str], dict[str, list[Change]]] = {} + for c in reviewable_changes(changes): + if c.kind not in DECOMPOSABLE_KINDS: + continue + sides = grouped.setdefault((c.term_id, c.kind), {"added": [], "removed": []}) + sides[c.side].append(c) + + def _delta(kind: str, added: Change | None, removed: Change | None) -> TextDelta: + src = added or removed + assert src is not None + if added is not None and removed is not None: + status = "refs_only" if added.value == removed.value else "revised" + else: + status = "added" if added is not None else "removed" + before = set(removed.refs) if removed is not None else set() + after = set(added.refs) if added is not None else set() + return TextDelta( + term_id=src.term_id, + term_label=src.term_label, + kind=kind, + status=status, + value=added.value if added is not None else None, + prior_value=removed.value if removed is not None else None, + refs=list(added.refs) if added is not None else [], + refs_added=sorted(after - before), + refs_removed=sorted(before - after), + ) + + out: list[TextDelta] = [] + for (_term_id, kind), sides in grouped.items(): + added = list(sides["added"]) + removed = list(sides["removed"]) + + # Pair identical prose first — those are ref-only edits, and matching + # them by value keeps a genuine rewrite from stealing the partner. + for a in list(added): + match = next((r for r in removed if r.value == a.value), None) + if match is not None: + added.remove(a) + removed.remove(match) + out.append(_delta(kind, a, match)) + + # Then pair what's left positionally; a term rarely carries more than + # one definition, so this only matters for multi-comment edits. + while added and removed: + out.append(_delta(kind, added.pop(0), removed.pop(0))) + + out.extend(_delta(kind, a, None) for a in added) + out.extend(_delta(kind, None, r) for r in removed) + + return out + +# --- head-state definition refs ------------------------------------------- + +# One axiom per line in ROBOT's functional syntax, so a definition's dbxrefs are +# whatever `hasDbXref` annotations sit to the left of the IAO_0000115 predicate. +_DEF_PREDICATE_RE = re.compile(r"obo:IAO_0000115 obo:([A-Za-z]+_\d+)") +_XREF_VALUE_RE = re.compile(r'oboInOwl:hasDbXref "([^"]+)"') + + +def definition_refs(owl_text: str) -> dict[str, list[str]]: + """Map term CURIE -> refs on that term's definition, from an edit file. + + `robot diff` only reports axioms that changed, so a PR that adds a logical + definition to an untouched term carries no definition axiom at all. The + references that justify a logical definition are the ones on the text + definition it formalises, which means reading them out of the head state + rather than the diff. + + Parsed line-wise on purpose: a nested-paren regex misses definitions whose + dbxref values contain brackets (e.g. Wikipedia URLs with parenthesised + disambiguators). + """ + out: dict[str, list[str]] = {} + for line in owl_text.splitlines(): + stripped = line.strip() + if not stripped.startswith("AnnotationAssertion("): + continue + match = _DEF_PREDICATE_RE.search(stripped) + if match is None: + continue + term_id = match.group(1).replace("_", ":", 1) + out[term_id] = _XREF_VALUE_RE.findall(stripped[: match.start()]) + return out diff --git a/fixtures/stage1/3717-buccal-mucosa-cell-edits/diff.md b/fixtures/stage1/3717-buccal-mucosa-cell-edits/diff.md new file mode 100644 index 0000000..ab1db6e --- /dev/null +++ b/fixtures/stage1/3717-buccal-mucosa-cell-edits/diff.md @@ -0,0 +1,87 @@ +# Ontology comparison + +## Left +- Ontology IRI: `http://purl.obolibrary.org/obo/cl.owl` +- Version IRI: *None* +- Loaded from: `file:left.owl` + +## Right +- Ontology IRI: `http://purl.obolibrary.org/obo/cl.owl` +- Version IRI: *None* +- Loaded from: `file:right.owl` + +### Ontology imports + + + +### Ontology annotations + + + +### buccal mucosa cell `http://purl.obolibrary.org/obo/CL_0002336` +#### Removed +- [buccal mucosa cell](http://purl.obolibrary.org/obo/CL_0002336) [definition](http://purl.obolibrary.org/obo/IAO_0000115) "An endothelial cell that lines the oral cavity including the mucosa of the gums, the palate, the lip, and the cheek." + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "MESH:D009061" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "GOC:tfm" + +- [buccal mucosa cell](http://purl.obolibrary.org/obo/CL_0002336) SubClassOf [endothelial cell of viscerocranial mucosa](http://purl.obolibrary.org/obo/CL_0002261) + +#### Added +- [buccal mucosa cell](http://purl.obolibrary.org/obo/CL_0002336) [definition](http://purl.obolibrary.org/obo/IAO_0000115) "An epithelial cell that lines the oral cavity including the mucosa of the gums, the palate, the lip, and the cheek." + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "MESH:D009061" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "GOC:tfm" + +- [buccal mucosa cell](http://purl.obolibrary.org/obo/CL_0002336) [comment](http://www.w3.org/2000/01/rdf-schema#comment) "Buccal mucosa cell is sometimes only used to refer to as the lining of the cheeks, as part of the lining mucosa." + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "https://www.scielo.org.mx/scielo.php?pid=S1870-199X2013000400001&script=sci_arttext&tlng=en" + +- [buccal mucosa cell](http://purl.obolibrary.org/obo/CL_0002336) EquivalentTo [keratinocyte](http://purl.obolibrary.org/obo/CL_0000312) and ([part of](http://purl.obolibrary.org/obo/BFO_0000050) some [mouth mucosa](http://purl.obolibrary.org/obo/UBERON_0003729)) + + +### keratinized cell of the oral mucosa `http://purl.obolibrary.org/obo/CL_0002170` +#### Removed +- [keratinized cell of the oral mucosa](http://purl.obolibrary.org/obo/CL_0002170) [definition](http://purl.obolibrary.org/obo/IAO_0000115) "A keratinized cell located in the hard palate or gingiva." + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:12014572" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "GOC:tfm" + +- [keratinized cell of the oral mucosa](http://purl.obolibrary.org/obo/CL_0002170) EquivalentTo [keratinizing barrier epithelial cell](http://purl.obolibrary.org/obo/CL_0000237) and ([part of](http://purl.obolibrary.org/obo/BFO_0000050) some [mouth mucosa](http://purl.obolibrary.org/obo/UBERON_0003729)) + +- [keratinized cell of the oral mucosa](http://purl.obolibrary.org/obo/CL_0002170) SubClassOf [keratinizing barrier epithelial cell](http://purl.obolibrary.org/obo/CL_0000237) + +- [keratinized cell of the oral mucosa](http://purl.obolibrary.org/obo/CL_0002170) SubClassOf [part of](http://purl.obolibrary.org/obo/BFO_0000050) some [mouth mucosa](http://purl.obolibrary.org/obo/UBERON_0003729) + +#### Added +- [keratinized cell of the oral mucosa](http://purl.obolibrary.org/obo/CL_0002170) [definition](http://purl.obolibrary.org/obo/IAO_0000115) "A keratinized buccal mucosa cell. These are mostly located in the hard palate and gingiva." + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:12014572" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "GOC:tfm" + + +### keratinocyte `http://purl.obolibrary.org/obo/CL_0000312` +#### Removed +- [keratinocyte](http://purl.obolibrary.org/obo/CL_0000312) [definition](http://purl.obolibrary.org/obo/IAO_0000115) "An epithelial cell of stratified squamous tissues, including skin, oral mucosa (Hiroshima et al., 2011), and esophagus (Whelan et al., 2018), that produces keratin proteins and secretes antimicrobial peptides to form a resilient barrier against environmental damage, dehydration, pathogens, and microbial invasion. It undergoes successive stages of differentiation marked by changes in keratin expression, supporting tissue integrity, wound repair, and contributing to immune defense." + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:19256306" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:19727116" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:37873034" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:15749908" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:29713660" + +#### Added +- [keratinocyte](http://purl.obolibrary.org/obo/CL_0000312) [definition](http://purl.obolibrary.org/obo/IAO_0000115) "An epithelial cell of stratified squamous tissues, including skin, oral mucosa (Hiroshima et al., 2011), and esophagus (Whelan et al., 2018), that produces keratin proteins and secretes antimicrobial peptides to form a resilient barrier against environmental damage, dehydration, pathogens, and microbial invasion. It undergoes successive stages of differentiation marked by changes in keratin expression, supporting tissue integrity, wound repair, and contributing to immune defense." + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:21316034" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:19256306" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:19727116" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:37873034" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:15749908" + + - [has cross-reference](http://www.geneontology.org/formats/oboInOwl#hasDbXref) "PMID:29713660" diff --git a/fixtures/stage1/3717-buccal-mucosa-cell-edits/parsed.json b/fixtures/stage1/3717-buccal-mucosa-cell-edits/parsed.json new file mode 100644 index 0000000..55bb49c --- /dev/null +++ b/fixtures/stage1/3717-buccal-mucosa-cell-edits/parsed.json @@ -0,0 +1,261 @@ +{ + "n_changes": 12, + "n_reviewable": 12, + "n_decomposable": 7, + "by_term": { + "CL:0002336": { + "term_label": "buccal mucosa cell", + "is_new_term": false, + "is_obsoleted": false, + "is_reviewable": true, + "n_changes": 5, + "n_content": 5, + "changes": [ + { + "term_id": "CL:0002336", + "term_label": "buccal mucosa cell", + "side": "removed", + "kind": "text_def", + "predicate_iri": "http://purl.obolibrary.org/obo/IAO_0000115", + "predicate_label": "definition", + "value": "An endothelial cell that lines the oral cavity including the mucosa of the gums, the palate, the lip, and the cheek.", + "value_label": null, + "refs": [ + "MESH:D009061", + "GOC:tfm" + ] + }, + { + "term_id": "CL:0002336", + "term_label": "buccal mucosa cell", + "side": "removed", + "kind": "subclass", + "predicate_iri": null, + "predicate_label": null, + "value": "http://purl.obolibrary.org/obo/CL_0002261", + "value_label": "endothelial cell of viscerocranial mucosa", + "refs": [] + }, + { + "term_id": "CL:0002336", + "term_label": "buccal mucosa cell", + "side": "added", + "kind": "text_def", + "predicate_iri": "http://purl.obolibrary.org/obo/IAO_0000115", + "predicate_label": "definition", + "value": "An epithelial cell that lines the oral cavity including the mucosa of the gums, the palate, the lip, and the cheek.", + "value_label": null, + "refs": [ + "MESH:D009061", + "GOC:tfm" + ] + }, + { + "term_id": "CL:0002336", + "term_label": "buccal mucosa cell", + "side": "added", + "kind": "comment", + "predicate_iri": "http://www.w3.org/2000/01/rdf-schema#comment", + "predicate_label": "comment", + "value": "Buccal mucosa cell is sometimes only used to refer to as the lining of the cheeks, as part of the lining mucosa.", + "value_label": null, + "refs": [ + "https://www.scielo.org.mx/scielo.php?pid=S1870-199X2013000400001&script=sci_arttext&tlng=en" + ] + }, + { + "term_id": "CL:0002336", + "term_label": "buccal mucosa cell", + "side": "added", + "kind": "equivalent_class", + "predicate_iri": null, + "predicate_label": null, + "value": "buccal mucosa cell EquivalentTo keratinocyte and (part of some mouth mucosa)", + "value_label": null, + "refs": [] + } + ] + }, + "CL:0002170": { + "term_label": "keratinized cell of the oral mucosa", + "is_new_term": false, + "is_obsoleted": false, + "is_reviewable": true, + "n_changes": 5, + "n_content": 5, + "changes": [ + { + "term_id": "CL:0002170", + "term_label": "keratinized cell of the oral mucosa", + "side": "removed", + "kind": "text_def", + "predicate_iri": "http://purl.obolibrary.org/obo/IAO_0000115", + "predicate_label": "definition", + "value": "A keratinized cell located in the hard palate or gingiva.", + "value_label": null, + "refs": [ + "PMID:12014572", + "GOC:tfm" + ] + }, + { + "term_id": "CL:0002170", + "term_label": "keratinized cell of the oral mucosa", + "side": "removed", + "kind": "equivalent_class", + "predicate_iri": null, + "predicate_label": null, + "value": "keratinized cell of the oral mucosa EquivalentTo keratinizing barrier epithelial cell and (part of some mouth mucosa)", + "value_label": null, + "refs": [] + }, + { + "term_id": "CL:0002170", + "term_label": "keratinized cell of the oral mucosa", + "side": "removed", + "kind": "subclass", + "predicate_iri": null, + "predicate_label": null, + "value": "http://purl.obolibrary.org/obo/CL_0000237", + "value_label": "keratinizing barrier epithelial cell", + "refs": [] + }, + { + "term_id": "CL:0002170", + "term_label": "keratinized cell of the oral mucosa", + "side": "removed", + "kind": "relationship", + "predicate_iri": "http://purl.obolibrary.org/obo/BFO_0000050", + "predicate_label": "part of", + "value": "http://purl.obolibrary.org/obo/UBERON_0003729", + "value_label": "mouth mucosa", + "refs": [] + }, + { + "term_id": "CL:0002170", + "term_label": "keratinized cell of the oral mucosa", + "side": "added", + "kind": "text_def", + "predicate_iri": "http://purl.obolibrary.org/obo/IAO_0000115", + "predicate_label": "definition", + "value": "A keratinized buccal mucosa cell. These are mostly located in the hard palate and gingiva.", + "value_label": null, + "refs": [ + "PMID:12014572", + "GOC:tfm" + ] + } + ] + }, + "CL:0000312": { + "term_label": "keratinocyte", + "is_new_term": false, + "is_obsoleted": false, + "is_reviewable": true, + "n_changes": 2, + "n_content": 2, + "changes": [ + { + "term_id": "CL:0000312", + "term_label": "keratinocyte", + "side": "removed", + "kind": "text_def", + "predicate_iri": "http://purl.obolibrary.org/obo/IAO_0000115", + "predicate_label": "definition", + "value": "An epithelial cell of stratified squamous tissues, including skin, oral mucosa (Hiroshima et al., 2011), and esophagus (Whelan et al., 2018), that produces keratin proteins and secretes antimicrobial peptides to form a resilient barrier against environmental damage, dehydration, pathogens, and microbial invasion. It undergoes successive stages of differentiation marked by changes in keratin expression, supporting tissue integrity, wound repair, and contributing to immune defense.", + "value_label": null, + "refs": [ + "PMID:19256306", + "PMID:19727116", + "PMID:37873034", + "PMID:15749908", + "PMID:29713660" + ] + }, + { + "term_id": "CL:0000312", + "term_label": "keratinocyte", + "side": "added", + "kind": "text_def", + "predicate_iri": "http://purl.obolibrary.org/obo/IAO_0000115", + "predicate_label": "definition", + "value": "An epithelial cell of stratified squamous tissues, including skin, oral mucosa (Hiroshima et al., 2011), and esophagus (Whelan et al., 2018), that produces keratin proteins and secretes antimicrobial peptides to form a resilient barrier against environmental damage, dehydration, pathogens, and microbial invasion. It undergoes successive stages of differentiation marked by changes in keratin expression, supporting tissue integrity, wound repair, and contributing to immune defense.", + "value_label": null, + "refs": [ + "PMID:21316034", + "PMID:19256306", + "PMID:19727116", + "PMID:37873034", + "PMID:15749908", + "PMID:29713660" + ] + } + ] + } + }, + "text_deltas": [ + { + "term_id": "CL:0002336", + "term_label": "buccal mucosa cell", + "kind": "text_def", + "status": "revised", + "value": "An epithelial cell that lines the oral cavity including the mucosa of the gums, the palate, the lip, and the cheek.", + "prior_value": "An endothelial cell that lines the oral cavity including the mucosa of the gums, the palate, the lip, and the cheek.", + "refs": [ + "MESH:D009061", + "GOC:tfm" + ], + "refs_added": [], + "refs_removed": [] + }, + { + "term_id": "CL:0002336", + "term_label": "buccal mucosa cell", + "kind": "comment", + "status": "added", + "value": "Buccal mucosa cell is sometimes only used to refer to as the lining of the cheeks, as part of the lining mucosa.", + "prior_value": null, + "refs": [ + "https://www.scielo.org.mx/scielo.php?pid=S1870-199X2013000400001&script=sci_arttext&tlng=en" + ], + "refs_added": [ + "https://www.scielo.org.mx/scielo.php?pid=S1870-199X2013000400001&script=sci_arttext&tlng=en" + ], + "refs_removed": [] + }, + { + "term_id": "CL:0002170", + "term_label": "keratinized cell of the oral mucosa", + "kind": "text_def", + "status": "revised", + "value": "A keratinized buccal mucosa cell. These are mostly located in the hard palate and gingiva.", + "prior_value": "A keratinized cell located in the hard palate or gingiva.", + "refs": [ + "PMID:12014572", + "GOC:tfm" + ], + "refs_added": [], + "refs_removed": [] + }, + { + "term_id": "CL:0000312", + "term_label": "keratinocyte", + "kind": "text_def", + "status": "refs_only", + "value": "An epithelial cell of stratified squamous tissues, including skin, oral mucosa (Hiroshima et al., 2011), and esophagus (Whelan et al., 2018), that produces keratin proteins and secretes antimicrobial peptides to form a resilient barrier against environmental damage, dehydration, pathogens, and microbial invasion. It undergoes successive stages of differentiation marked by changes in keratin expression, supporting tissue integrity, wound repair, and contributing to immune defense.", + "prior_value": "An epithelial cell of stratified squamous tissues, including skin, oral mucosa (Hiroshima et al., 2011), and esophagus (Whelan et al., 2018), that produces keratin proteins and secretes antimicrobial peptides to form a resilient barrier against environmental damage, dehydration, pathogens, and microbial invasion. It undergoes successive stages of differentiation marked by changes in keratin expression, supporting tissue integrity, wound repair, and contributing to immune defense.", + "refs": [ + "PMID:21316034", + "PMID:19256306", + "PMID:19727116", + "PMID:37873034", + "PMID:15749908", + "PMID:29713660" + ], + "refs_added": [ + "PMID:21316034" + ], + "refs_removed": [] + } + ] +} diff --git a/fixtures/stage1/3717-buccal-mucosa-cell-edits/refs.txt b/fixtures/stage1/3717-buccal-mucosa-cell-edits/refs.txt new file mode 100644 index 0000000..2b1bf22 --- /dev/null +++ b/fixtures/stage1/3717-buccal-mucosa-cell-edits/refs.txt @@ -0,0 +1,5 @@ +pr=3717 (Buccal mucosa cell edits) +base=d7ff69b907623c8a7d0904f5044fa4724233901a +tip=51ef20015a068078888bb4c42ae5ae861c846fca +base_source=merge-base(pull/3717/head, origin/master) +note=tip is the head reviewed by CLARA run 33852530599 diff --git a/runs/CL_0000312/report.md b/runs/CL_0000312/report.md new file mode 100644 index 0000000..f8cb6fb --- /dev/null +++ b/runs/CL_0000312/report.md @@ -0,0 +1,59 @@ +# CL:0000312 — keratinocyte + +> Stage B ran against Asta `snippet_search`, scoped by `paper_ids`. Stage C (`get_europepmc_full_text`) returned empty output for every identifier tried (see issue #5), so unresolved core assertions could not be escalated to full text. + +## Routed targets + +| target_id | route | refs in scope | where those refs come from | +|---|---|---|---| +| `refs_added:CL:0000312:1` | refs_added | `PMID:21316034` | refs newly attached to the definition by this PR (delta only) | + +**Definition xrefs at head:** `PMID:15749908`, `PMID:19256306`, `PMID:19727116`, `PMID:29713660`, `PMID:37873034`, `PMID:21316034` — searchable: `PMID:15749908`, `PMID:19256306`, `PMID:19727116`, `PMID:29713660`, `PMID:37873034`, `PMID:21316034` + +## Assertions + +| # | Assertion | Cat | Verdict | +|---|---|---|---| +| a1 | Keratinocytes are epithelial cells of stratified squamous tissues. | core | **uncertain** | +| a2 | Keratinocytes are present in skin. | core | **pass** | +| a3 | Keratinocytes are present in oral mucosa. | core | **pass** | +| a4 | Keratinocytes are present in esophagus. | core | **uncertain** | +| a5 | Keratinocytes produce keratin proteins. | core | **uncertain** | +| a6 | Keratinocytes secrete antimicrobial peptides. | core | **pass** | +| a7 | Keratinocytes form a barrier against environmental damage. | core | **uncertain** | +| a8 | Keratinocytes form a barrier against dehydration. | core | **uncertain** | +| a9 | Keratinocytes form a barrier against pathogens and microbial invasion. | core | **pass** | +| a10 | Keratinocytes undergo successive stages of differentiation marked by changes in keratin expression. | core | **uncertain** | +| a11 | Keratinocyte differentiation supports tissue integrity and wound repair. | core | **uncertain** | +| a12 | Keratinocytes contribute to immune defense. | core | **pass** | + +## Supporting evidence + +**a2, a3, a6, a9, a12** — PMID:21316034: + +> In the oral cavity, mucosal keratinocytes resist bacterial infection, in part, by producing broad-spectrum antimicrobial peptides (AMPs) including defensin, adrenomedullin and calprotectin. Epidermal keratinocyte expression of many AMPs increases in response to interleukin-1α (IL-1α). IL-1α is produced by epidermal keratinocytes and regulates cell differentiation. To better understand innate immunity in the oral cavity, we sought to determine how IL-1α might regulate expression of AMPs by human gingival keratinocytes (HGKs) using DNA microarray and western blot analyses. HGKs from three subjects expressed eleven AMPs, including S100A7, S100A8, S100A9, S100A12, secretory leukocyte protease inhibitor, lipocalin 2 (LCN2), cystatin C and β-defensin 2. Of the expressed AMPs, S100A7, S100A12 and LCN2 were up-regulated by IL-1α (inducible AMPs); the other AMPs were considered to be constitutive. Human gingival keratinocytes, therefore, express constitutive and IL-1α-inducible AMPs to provide a rapid and robust innate response to microbial infection. + +- `a2`: Snippet refers to epidermal keratinocytes. + +## Unresolved + +**searched** (6) — no snippet in PMID:21316034 addresses this claim. + +- `a1` Keratinocytes are epithelial cells of stratified squamous tissues. +- `a4` Keratinocytes are present in esophagus. +- `a5` Keratinocytes produce keratin proteins. +- `a7` Keratinocytes form a barrier against environmental damage. +- `a8` Keratinocytes form a barrier against dehydration. +- `a11` Keratinocyte differentiation supports tissue integrity and wound repair. + +**searched** (1) — the snippet states IL-1a regulates cell differentiation but says nothing about keratin-marked stages. + +- `a10` Keratinocytes undergo successive stages of differentiation marked by changes in keratin expression. + +## Summary + +- Assertions: 12 (12 core, 0 background) +- Core: pass 5 · fail 0 · uncertain 7 +- Stage C escalation: not available (issue #5) + +**Term-level verdict: UNCERTAIN** diff --git a/runs/CL_0000312/tool_calls.jsonl b/runs/CL_0000312/tool_calls.jsonl new file mode 100644 index 0000000..d2977ce --- /dev/null +++ b/runs/CL_0000312/tool_calls.jsonl @@ -0,0 +1,13 @@ +{"tool": "snippet_search", "assertion_id": "a1", "target_id": "refs_added:CL:0000312:1", "query": "keratinocyte epithelial cell of stratified squamous tissue", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a2", "target_id": "refs_added:CL:0000312:1", "query": "keratinocytes in skin epidermis", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a3", "target_id": "refs_added:CL:0000312:1", "query": "keratinocytes of the oral mucosa", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a4", "target_id": "refs_added:CL:0000312:1", "query": "keratinocytes in esophagus", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a5", "target_id": "refs_added:CL:0000312:1", "query": "keratinocytes produce keratin proteins", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a6", "target_id": "refs_added:CL:0000312:1", "query": "keratinocytes secrete antimicrobial peptides", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a7", "target_id": "refs_added:CL:0000312:1", "query": "keratinocyte barrier against environmental damage", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a8", "target_id": "refs_added:CL:0000312:1", "query": "keratinocyte barrier against dehydration water loss", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a9", "target_id": "refs_added:CL:0000312:1", "query": "keratinocyte barrier against pathogens microbial invasion", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a10", "target_id": "refs_added:CL:0000312:1", "query": "keratinocyte differentiation stages keratin expression", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a11", "target_id": "refs_added:CL:0000312:1", "query": "keratinocytes tissue integrity wound repair", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a12", "target_id": "refs_added:CL:0000312:1", "query": "keratinocytes contribute to immune defense", "paper_ids": "PMID:21316034", "returned_corpus_ids": ["205651077", "205651077"], "n_hits": 2, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "get_full_text", "target_id": "refs_added:CL:0000312:1", "identifier": "PMID:21316034", "available": false, "note": "empty response"} diff --git a/runs/CL_0000312/verdicts.json b/runs/CL_0000312/verdicts.json new file mode 100644 index 0000000..e72edfb --- /dev/null +++ b/runs/CL_0000312/verdicts.json @@ -0,0 +1,311 @@ +{ + "cell_id": "CL_0000312", + "term_id": "CL:0000312", + "name": "keratinocyte", + "run_note": "Stage B ran against Asta `snippet_search`, scoped by `paper_ids`. Stage C (`get_europepmc_full_text`) returned empty output for every identifier tried (see issue #5), so unresolved core assertions could not be escalated to full text.", + "targets_processed": [ + { + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "validation_mode": "validate_new_refs_against_existing_text" + } + ], + "references": [ + "PMID:21316034" + ], + "assertions": [ + { + "id": "a1", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes are epithelial cells of stratified squamous tissues.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "searched \u2014 no snippet in PMID:21316034 addresses this claim." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a2", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes are present in skin.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "pass", + "evidence": "In the oral cavity, mucosal keratinocytes resist bacterial infection, in part, by producing broad-spectrum antimicrobial peptides (AMPs) including defensin, adrenomedullin and calprotectin. Epidermal keratinocyte expression of many AMPs increases in response to interleukin-1\u03b1 (IL-1\u03b1). IL-1\u03b1 is produced by epidermal keratinocytes and regulates cell differentiation. To better understand innate immunity in the oral cavity, we sought to determine how IL-1\u03b1 might regulate expression of AMPs by human gingival keratinocytes (HGKs) using DNA microarray and western blot analyses. HGKs from three subjects expressed eleven AMPs, including S100A7, S100A8, S100A9, S100A12, secretory leukocyte protease inhibitor, lipocalin 2 (LCN2), cystatin C and \u03b2-defensin 2. Of the expressed AMPs, S100A7, S100A12 and LCN2 were up-regulated by IL-1\u03b1 (inducible AMPs); the other AMPs were considered to be constitutive. Human gingival keratinocytes, therefore, express constitutive and IL-1\u03b1-inducible AMPs to provide a rapid and robust innate response to microbial infection.", + "source": "PMID:21316034", + "note": "Snippet refers to epidermal keratinocytes." + }, + "full_text_stage": null, + "final_verdict": "pass", + "warn_background": false + }, + { + "id": "a3", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes are present in oral mucosa.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "pass", + "evidence": "In the oral cavity, mucosal keratinocytes resist bacterial infection, in part, by producing broad-spectrum antimicrobial peptides (AMPs) including defensin, adrenomedullin and calprotectin. Epidermal keratinocyte expression of many AMPs increases in response to interleukin-1\u03b1 (IL-1\u03b1). IL-1\u03b1 is produced by epidermal keratinocytes and regulates cell differentiation. To better understand innate immunity in the oral cavity, we sought to determine how IL-1\u03b1 might regulate expression of AMPs by human gingival keratinocytes (HGKs) using DNA microarray and western blot analyses. HGKs from three subjects expressed eleven AMPs, including S100A7, S100A8, S100A9, S100A12, secretory leukocyte protease inhibitor, lipocalin 2 (LCN2), cystatin C and \u03b2-defensin 2. Of the expressed AMPs, S100A7, S100A12 and LCN2 were up-regulated by IL-1\u03b1 (inducible AMPs); the other AMPs were considered to be constitutive. Human gingival keratinocytes, therefore, express constitutive and IL-1\u03b1-inducible AMPs to provide a rapid and robust innate response to microbial infection.", + "source": "PMID:21316034", + "note": null + }, + "full_text_stage": null, + "final_verdict": "pass", + "warn_background": false + }, + { + "id": "a4", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes are present in esophagus.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "searched \u2014 no snippet in PMID:21316034 addresses this claim." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a5", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes produce keratin proteins.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "searched \u2014 no snippet in PMID:21316034 addresses this claim." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a6", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes secrete antimicrobial peptides.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "pass", + "evidence": "In the oral cavity, mucosal keratinocytes resist bacterial infection, in part, by producing broad-spectrum antimicrobial peptides (AMPs) including defensin, adrenomedullin and calprotectin. Epidermal keratinocyte expression of many AMPs increases in response to interleukin-1\u03b1 (IL-1\u03b1). IL-1\u03b1 is produced by epidermal keratinocytes and regulates cell differentiation. To better understand innate immunity in the oral cavity, we sought to determine how IL-1\u03b1 might regulate expression of AMPs by human gingival keratinocytes (HGKs) using DNA microarray and western blot analyses. HGKs from three subjects expressed eleven AMPs, including S100A7, S100A8, S100A9, S100A12, secretory leukocyte protease inhibitor, lipocalin 2 (LCN2), cystatin C and \u03b2-defensin 2. Of the expressed AMPs, S100A7, S100A12 and LCN2 were up-regulated by IL-1\u03b1 (inducible AMPs); the other AMPs were considered to be constitutive. Human gingival keratinocytes, therefore, express constitutive and IL-1\u03b1-inducible AMPs to provide a rapid and robust innate response to microbial infection.", + "source": "PMID:21316034", + "note": null + }, + "full_text_stage": null, + "final_verdict": "pass", + "warn_background": false + }, + { + "id": "a7", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes form a barrier against environmental damage.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "searched \u2014 no snippet in PMID:21316034 addresses this claim." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a8", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes form a barrier against dehydration.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "searched \u2014 no snippet in PMID:21316034 addresses this claim." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a9", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes form a barrier against pathogens and microbial invasion.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "pass", + "evidence": "In the oral cavity, mucosal keratinocytes resist bacterial infection, in part, by producing broad-spectrum antimicrobial peptides (AMPs) including defensin, adrenomedullin and calprotectin. Epidermal keratinocyte expression of many AMPs increases in response to interleukin-1\u03b1 (IL-1\u03b1). IL-1\u03b1 is produced by epidermal keratinocytes and regulates cell differentiation. To better understand innate immunity in the oral cavity, we sought to determine how IL-1\u03b1 might regulate expression of AMPs by human gingival keratinocytes (HGKs) using DNA microarray and western blot analyses. HGKs from three subjects expressed eleven AMPs, including S100A7, S100A8, S100A9, S100A12, secretory leukocyte protease inhibitor, lipocalin 2 (LCN2), cystatin C and \u03b2-defensin 2. Of the expressed AMPs, S100A7, S100A12 and LCN2 were up-regulated by IL-1\u03b1 (inducible AMPs); the other AMPs were considered to be constitutive. Human gingival keratinocytes, therefore, express constitutive and IL-1\u03b1-inducible AMPs to provide a rapid and robust innate response to microbial infection.", + "source": "PMID:21316034", + "note": null + }, + "full_text_stage": null, + "final_verdict": "pass", + "warn_background": false + }, + { + "id": "a10", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes undergo successive stages of differentiation marked by changes in keratin expression.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "searched \u2014 the snippet states IL-1a regulates cell differentiation but says nothing about keratin-marked stages." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a11", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocyte differentiation supports tissue integrity and wound repair.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "searched \u2014 no snippet in PMID:21316034 addresses this claim." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a12", + "target_id": "refs_added:CL:0000312:1", + "route": "refs_added", + "source_change_kind": "text_def", + "text": "Keratinocytes contribute to immune defense.", + "category": "core", + "refs_in_scope": [ + "PMID:21316034" + ], + "snippet_stage": { + "verdict": "pass", + "evidence": "In the oral cavity, mucosal keratinocytes resist bacterial infection, in part, by producing broad-spectrum antimicrobial peptides (AMPs) including defensin, adrenomedullin and calprotectin. Epidermal keratinocyte expression of many AMPs increases in response to interleukin-1\u03b1 (IL-1\u03b1). IL-1\u03b1 is produced by epidermal keratinocytes and regulates cell differentiation. To better understand innate immunity in the oral cavity, we sought to determine how IL-1\u03b1 might regulate expression of AMPs by human gingival keratinocytes (HGKs) using DNA microarray and western blot analyses. HGKs from three subjects expressed eleven AMPs, including S100A7, S100A8, S100A9, S100A12, secretory leukocyte protease inhibitor, lipocalin 2 (LCN2), cystatin C and \u03b2-defensin 2. Of the expressed AMPs, S100A7, S100A12 and LCN2 were up-regulated by IL-1\u03b1 (inducible AMPs); the other AMPs were considered to be constitutive. Human gingival keratinocytes, therefore, express constitutive and IL-1\u03b1-inducible AMPs to provide a rapid and robust innate response to microbial infection.", + "source": "PMID:21316034", + "note": null + }, + "full_text_stage": null, + "final_verdict": "pass", + "warn_background": false + } + ], + "summary": { + "total": 12, + "total_core": 12, + "total_background": 0, + "core_pass_snippet": 5, + "core_pass_full_text": 0, + "core_fail": 0, + "core_uncertain": 7, + "background_pass": 0, + "background_warn": 0 + } +} \ No newline at end of file diff --git a/runs/CL_0002170/report.md b/runs/CL_0002170/report.md new file mode 100644 index 0000000..78f14dc --- /dev/null +++ b/runs/CL_0002170/report.md @@ -0,0 +1,35 @@ +# CL:0002170 — keratinized cell of the oral mucosa + +> Stage B ran against Asta `snippet_search`, scoped by `paper_ids`. Stage C (`get_europepmc_full_text`) returned empty output for every identifier tried (see issue #5), so unresolved core assertions could not be escalated to full text. + +## Routed targets + +| target_id | route | refs in scope | where those refs come from | +|---|---|---|---| +| `text_revision:CL:0002170:1` | text_revision | `PMID:12014572` | refs on the revised definition axiom | + +**Definition xrefs at head:** `GOC:tfm`, `PMID:12014572` — searchable: `PMID:12014572` + +## Assertions + +| # | Assertion | Cat | Verdict | +|---|---|---|---| +| a1 | A keratinized cell of the oral mucosa is a keratinized buccal mucosa cell. | core | **uncertain** | +| a2 | Keratinized cells of the oral mucosa are mostly located in the hard palate. | core | **uncertain** | +| a3 | Keratinized cells of the oral mucosa are mostly located in the gingiva. | core | **uncertain** | + +## Unresolved + +**not retrievable** (3) — `snippet_search` returned 0 hits because PMID:12014572 is absent from the Asta corpus, and the paper is `inEPMC=N` so Stage C cannot reach it either. The reference was in scope but could not be read. + +- `a1` A keratinized cell of the oral mucosa is a keratinized buccal mucosa cell. +- `a2` Keratinized cells of the oral mucosa are mostly located in the hard palate. +- `a3` Keratinized cells of the oral mucosa are mostly located in the gingiva. + +## Summary + +- Assertions: 3 (3 core, 0 background) +- Core: pass 0 · fail 0 · uncertain 3 +- Stage C escalation: not available (issue #5) + +**Term-level verdict: UNCERTAIN** diff --git a/runs/CL_0002170/tool_calls.jsonl b/runs/CL_0002170/tool_calls.jsonl new file mode 100644 index 0000000..497b718 --- /dev/null +++ b/runs/CL_0002170/tool_calls.jsonl @@ -0,0 +1,4 @@ +{"tool": "snippet_search", "assertion_id": "a1", "target_id": "text_revision:CL:0002170:1", "query": "keratinized cell of oral mucosa is a buccal mucosa cell", "paper_ids": "PMID:12014572", "returned_corpus_ids": [], "n_hits": 0, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a2", "target_id": "text_revision:CL:0002170:1", "query": "keratinized oral epithelium hard palate", "paper_ids": "PMID:12014572", "returned_corpus_ids": [], "n_hits": 0, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "snippet_search", "assertion_id": "a3", "target_id": "text_revision:CL:0002170:1", "query": "keratinized oral epithelium gingiva", "paper_ids": "PMID:12014572", "returned_corpus_ids": [], "n_hits": 0, "n_leaked": 0, "note": "externalIds absent from response; leak check done on corpusId/title"} +{"tool": "get_full_text", "target_id": "text_revision:CL:0002170:1", "identifier": "PMID:12014572", "available": false, "note": "empty response"} diff --git a/runs/CL_0002170/verdicts.json b/runs/CL_0002170/verdicts.json new file mode 100644 index 0000000..bb6745c --- /dev/null +++ b/runs/CL_0002170/verdicts.json @@ -0,0 +1,107 @@ +{ + "cell_id": "CL_0002170", + "term_id": "CL:0002170", + "name": "keratinized cell of the oral mucosa", + "run_note": "Stage B ran against Asta `snippet_search`, scoped by `paper_ids`. Stage C (`get_europepmc_full_text`) returned empty output for every identifier tried (see issue #5), so unresolved core assertions could not be escalated to full text.", + "targets_processed": [ + { + "target_id": "text_revision:CL:0002170:1", + "route": "text_revision", + "validation_mode": "decompose_revised_text" + } + ], + "references": [ + "PMID:12014572" + ], + "assertions": [ + { + "id": "a1", + "target_id": "text_revision:CL:0002170:1", + "route": "text_revision", + "source_change_kind": "text_def", + "text": "A keratinized cell of the oral mucosa is a keratinized buccal mucosa cell.", + "category": "core", + "refs_in_scope": [ + "PMID:12014572" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "not retrievable \u2014 `snippet_search` returned 0 hits because PMID:12014572 is absent from the Asta corpus, and the paper is `inEPMC=N` so Stage C cannot reach it either. The reference was in scope but could not be read." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a2", + "target_id": "text_revision:CL:0002170:1", + "route": "text_revision", + "source_change_kind": "text_def", + "text": "Keratinized cells of the oral mucosa are mostly located in the hard palate.", + "category": "core", + "refs_in_scope": [ + "PMID:12014572" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "not retrievable \u2014 `snippet_search` returned 0 hits because PMID:12014572 is absent from the Asta corpus, and the paper is `inEPMC=N` so Stage C cannot reach it either. The reference was in scope but could not be read." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a3", + "target_id": "text_revision:CL:0002170:1", + "route": "text_revision", + "source_change_kind": "text_def", + "text": "Keratinized cells of the oral mucosa are mostly located in the gingiva.", + "category": "core", + "refs_in_scope": [ + "PMID:12014572" + ], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "not retrievable \u2014 `snippet_search` returned 0 hits because PMID:12014572 is absent from the Asta corpus, and the paper is `inEPMC=N` so Stage C cannot reach it either. The reference was in scope but could not be read." + }, + "full_text_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "full_text_available": false, + "note": "get_europepmc_full_text returned empty output for this identifier." + }, + "final_verdict": "uncertain", + "warn_background": false + } + ], + "summary": { + "total": 3, + "total_core": 3, + "total_background": 0, + "core_pass_snippet": 0, + "core_pass_full_text": 0, + "core_fail": 0, + "core_uncertain": 3, + "background_pass": 0, + "background_warn": 0 + } +} \ No newline at end of file diff --git a/runs/CL_0002336/report.md b/runs/CL_0002336/report.md new file mode 100644 index 0000000..5101491 --- /dev/null +++ b/runs/CL_0002336/report.md @@ -0,0 +1,47 @@ +# CL:0002336 — buccal mucosa cell + +> Stage B ran against Asta `snippet_search`, scoped by `paper_ids`. Stage C (`get_europepmc_full_text`) returned empty output for every identifier tried (see issue #5), so unresolved core assertions could not be escalated to full text. + +## Routed targets + +| target_id | route | refs in scope | where those refs come from | +|---|---|---|---| +| `text_revision:CL:0002336:1` | text_revision | _none searchable_ | refs on the revised definition axiom | +| `text_revision:CL:0002336:2` | text_revision | _none searchable_ | refs on the revised definition axiom | +| `relationship:CL:0002336:1` | relationship | _none searchable_ | refs on the text definition the axiom formalises | + +**Definition xrefs at head:** `GOC:tfm`, `MESH:D009061` — searchable: **none** + +## Assertions + +| # | Assertion | Cat | Verdict | +|---|---|---|---| +| a1 | Buccal mucosa cells are epithelial cells. | core | **uncertain** | +| a2 | Buccal mucosa cells line the oral cavity, including the mucosa of the gums, palate, lip and cheek. | core | **uncertain** | +| a3 | The term 'buccal mucosa cell' is sometimes used only for the lining of the cheeks, as part of the lining mucosa. | background | **uncertain** | +| a4 | NECESSARY: Every buccal mucosa cell is a keratinocyte that is part of the mouth mucosa. | core | **uncertain** | +| a5 | SUFFICIENT: Every keratinocyte that is part of the mouth mucosa is a buccal mucosa cell. | core | **uncertain** | + +## Unresolved + +**uncited** (2) — this definition carries no searchable reference (only `GOC:tfm` and `MESH:D009061`). No search was possible. + +- `a1` Buccal mucosa cells are epithelial cells. +- `a2` Buccal mucosa cells line the oral cavity, including the mucosa of the gums, palate, lip and cheek. + +**uncited** (1) — the comment's only xref is a scielo URL, which is not a `PMID:`/`DOI:` id the search tools accept. + +- `a3` The term 'buccal mucosa cell' is sometimes used only for the lining of the cheeks, as part of the lining mucosa. + +**uncited** (2) — the definition this axiom formalises carries no searchable reference (only `GOC:tfm`, a curator attribution, and `MESH:D009061`, the MeSH descriptor "Mouth Mucosa"). No search was possible. + +- `a4` NECESSARY: Every buccal mucosa cell is a keratinocyte that is part of the mouth mucosa. +- `a5` SUFFICIENT: Every keratinocyte that is part of the mouth mucosa is a buccal mucosa cell. + +## Summary + +- Assertions: 5 (4 core, 1 background) +- Core: pass 0 · fail 0 · uncertain 4 +- Stage C escalation: not available (issue #5) + +**Term-level verdict: UNCERTAIN** diff --git a/runs/CL_0002336/tool_calls.jsonl b/runs/CL_0002336/tool_calls.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/runs/CL_0002336/verdicts.json b/runs/CL_0002336/verdicts.json new file mode 100644 index 0000000..0593053 --- /dev/null +++ b/runs/CL_0002336/verdicts.json @@ -0,0 +1,127 @@ +{ + "cell_id": "CL_0002336", + "term_id": "CL:0002336", + "name": "buccal mucosa cell", + "run_note": "Stage B ran against Asta `snippet_search`, scoped by `paper_ids`. Stage C (`get_europepmc_full_text`) returned empty output for every identifier tried (see issue #5), so unresolved core assertions could not be escalated to full text.", + "targets_processed": [ + { + "target_id": "text_revision:CL:0002336:1", + "route": "text_revision", + "validation_mode": "decompose_revised_text" + }, + { + "target_id": "text_revision:CL:0002336:2", + "route": "text_revision", + "validation_mode": "decompose_revised_text" + }, + { + "target_id": "relationship:CL:0002336:1", + "route": "relationship", + "validation_mode": "validate_equivalent_class_axiom" + } + ], + "references": [], + "assertions": [ + { + "id": "a1", + "target_id": "text_revision:CL:0002336:1", + "route": "text_revision", + "source_change_kind": "text_def", + "text": "Buccal mucosa cells are epithelial cells.", + "category": "core", + "refs_in_scope": [], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "uncited \u2014 this definition carries no searchable reference (only `GOC:tfm` and `MESH:D009061`). No search was possible." + }, + "full_text_stage": null, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a2", + "target_id": "text_revision:CL:0002336:1", + "route": "text_revision", + "source_change_kind": "text_def", + "text": "Buccal mucosa cells line the oral cavity, including the mucosa of the gums, palate, lip and cheek.", + "category": "core", + "refs_in_scope": [], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "uncited \u2014 this definition carries no searchable reference (only `GOC:tfm` and `MESH:D009061`). No search was possible." + }, + "full_text_stage": null, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a3", + "target_id": "text_revision:CL:0002336:2", + "route": "text_revision", + "source_change_kind": "comment", + "text": "The term 'buccal mucosa cell' is sometimes used only for the lining of the cheeks, as part of the lining mucosa.", + "category": "background", + "refs_in_scope": [], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "uncited \u2014 the comment's only xref is a scielo URL, which is not a `PMID:`/`DOI:` id the search tools accept." + }, + "full_text_stage": null, + "final_verdict": "uncertain", + "warn_background": true + }, + { + "id": "a4", + "target_id": "relationship:CL:0002336:1", + "route": "relationship", + "source_change_kind": "equivalent_class", + "text": "NECESSARY: Every buccal mucosa cell is a keratinocyte that is part of the mouth mucosa.", + "category": "core", + "refs_in_scope": [], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "uncited \u2014 the definition this axiom formalises carries no searchable reference (only `GOC:tfm`, a curator attribution, and `MESH:D009061`, the MeSH descriptor \"Mouth Mucosa\"). No search was possible." + }, + "full_text_stage": null, + "final_verdict": "uncertain", + "warn_background": false + }, + { + "id": "a5", + "target_id": "relationship:CL:0002336:1", + "route": "relationship", + "source_change_kind": "equivalent_class", + "text": "SUFFICIENT: Every keratinocyte that is part of the mouth mucosa is a buccal mucosa cell.", + "category": "core", + "refs_in_scope": [], + "snippet_stage": { + "verdict": "uncertain", + "evidence": null, + "source": null, + "note": "uncited \u2014 the definition this axiom formalises carries no searchable reference (only `GOC:tfm`, a curator attribution, and `MESH:D009061`, the MeSH descriptor \"Mouth Mucosa\"). No search was possible." + }, + "full_text_stage": null, + "final_verdict": "uncertain", + "warn_background": false + } + ], + "summary": { + "total": 5, + "total_core": 4, + "total_background": 1, + "core_pass_snippet": 0, + "core_pass_full_text": 0, + "core_fail": 0, + "core_uncertain": 4, + "background_pass": 0, + "background_warn": 1 + } +} \ No newline at end of file diff --git a/tests/test_stage1_parse.py b/tests/test_stage1_parse.py index e6f226b..dd40e5e 100644 --- a/tests/test_stage1_parse.py +++ b/tests/test_stage1_parse.py @@ -17,12 +17,23 @@ decomposable_changes, parse_diff_markdown, reviewable_changes, + definition_refs, summarise_by_term, + text_deltas, ) FIXTURES = Path(__file__).parent.parent / "fixtures" / "stage1" +ALL_FIXTURES = [ + "2508-multiciliated-ependymal-cell", + "2441-revise-epithelial-cell-of-uterus-defs", + "2485-ntr-pancreatic-islet-capillary-endothelial-cell", + "2480-text-and-log-def-syncytial-epithelial-cell", + "2471-juxtaglomerular-complex-cells-merge", + "3717-buccal-mucosa-cell-edits", +] + def _load(slug: str) -> str: return (FIXTURES / slug / "diff.md").read_text() @@ -117,13 +128,7 @@ def test_2471_non_obsoleted_terms_still_reviewable(): # --- reviewable filter invariants ----------------------------------------- -@pytest.mark.parametrize("slug", [ - "2508-multiciliated-ependymal-cell", - "2441-revise-epithelial-cell-of-uterus-defs", - "2485-ntr-pancreatic-islet-capillary-endothelial-cell", - "2480-text-and-log-def-syncytial-epithelial-cell", - "2471-juxtaglomerular-complex-cells-merge", -]) +@pytest.mark.parametrize("slug", ALL_FIXTURES) def test_reviewable_is_subset_of_all(slug): changes = parse_diff_markdown(_load(slug)) reviewed = reviewable_changes(changes) @@ -132,13 +137,7 @@ def test_reviewable_is_subset_of_all(slug): assert all(c.kind in CONTENT_KINDS for c in reviewed) -@pytest.mark.parametrize("slug", [ - "2508-multiciliated-ependymal-cell", - "2441-revise-epithelial-cell-of-uterus-defs", - "2485-ntr-pancreatic-islet-capillary-endothelial-cell", - "2480-text-and-log-def-syncytial-epithelial-cell", - "2471-juxtaglomerular-complex-cells-merge", -]) +@pytest.mark.parametrize("slug", ALL_FIXTURES) def test_decomposable_is_subset_of_reviewable(slug): """decomposable ⊆ reviewable, and contains only text_def / comment.""" changes = parse_diff_markdown(_load(slug)) @@ -151,15 +150,149 @@ def test_decomposable_is_subset_of_reviewable(slug): assert all(id(c) in rev_ids for c in decomp) -@pytest.mark.parametrize("slug", [ - "2508-multiciliated-ependymal-cell", - "2441-revise-epithelial-cell-of-uterus-defs", - "2485-ntr-pancreatic-islet-capillary-endothelial-cell", - "2480-text-and-log-def-syncytial-epithelial-cell", - "2471-juxtaglomerular-complex-cells-merge", -]) +@pytest.mark.parametrize("slug", ALL_FIXTURES) def test_no_unknown_changes(slug): """Every bullet in every fixture must be classified; unknowns flag a parser gap.""" changes = parse_diff_markdown(_load(slug)) unknown = [c for c in changes if c.kind == "unknown"] assert not unknown, f"unclassified bullets: {[c.raw for c in unknown[:3]]}" + + +# --- 3717: equivalence axioms + ref-only def edits (issue #2) -------------- + +SLUG_3717 = "3717-buccal-mucosa-cell-edits" + + +def _deltas(slug: str, term_id: str, kind: str = "text_def"): + ds = [ + d for d in text_deltas(parse_diff_markdown(_load(slug))) + if d.term_id == term_id and d.kind == kind + ] + assert len(ds) == 1, f"expected one {kind} delta for {term_id}, got {len(ds)}" + return ds[0] + + +def test_3717_equivalent_class_carries_manchester_axiom(): + """EquivalentTo axioms must reach the agent as verifiable text, not a null.""" + changes = parse_diff_markdown(_load(SLUG_3717)) + added = [ + c for c in changes + if c.kind == "equivalent_class" and c.side == "added" + ] + assert len(added) == 1 + assert added[0].value == ( + "buccal mucosa cell EquivalentTo keratinocyte " + "and (part of some mouth mucosa)" + ) + # No markdown or IRIs may leak into the assertion text. + assert "](" not in added[0].value + assert "http" not in added[0].value + + +def test_equivalent_class_axiom_populated_across_fixtures(): + for slug in ALL_FIXTURES: + for c in parse_diff_markdown(_load(slug)): + if c.kind == "equivalent_class": + assert c.value, f"{slug}: {c.term_id} equivalent_class has no axiom text" + assert "EquivalentTo" in c.value or "EquivalentClasses" in c.value + + +def test_3717_ref_only_def_edit_is_not_a_revision(): + """CL:0000312 keeps its definition verbatim and gains one PMID.""" + d = _deltas(SLUG_3717, "CL:0000312") + assert d.status == "refs_only" + assert d.value == d.prior_value + assert d.refs_added == ["PMID:21316034"] + assert d.refs_removed == [] + + +def test_3717_revised_defs_flagged_as_revised(): + """Both genuinely rewritten definitions must not be mistaken for ref edits.""" + revised = _deltas(SLUG_3717, "CL:0002170") + assert revised.status == "revised" + assert revised.value.startswith("A keratinized buccal mucosa cell") + assert revised.prior_value.startswith("A keratinized cell located") + assert revised.refs_added == [] # same refs, new prose + + swap = _deltas(SLUG_3717, "CL:0002336") + assert swap.status == "revised" + assert swap.value.startswith("An epithelial cell that lines") + assert swap.prior_value.startswith("An endothelial cell that lines") + + +def test_3717_new_comment_has_no_prior_side(): + d = _deltas(SLUG_3717, "CL:0002336", kind="comment") + assert d.status == "added" + assert d.prior_value is None + + +def test_2441_def_revision_still_reads_as_revised(): + """Regression: the pre-existing def-revision fixture must not become refs_only.""" + d = _deltas("2441-revise-epithelial-cell-of-uterus-defs", "CL:0002149") + assert d.status == "revised" + assert "mesodermal origin" in d.value + + +def test_text_deltas_cover_every_reviewable_text_change(): + """Every decomposable change lands in exactly one delta, on the right side.""" + for slug in ALL_FIXTURES: + changes = parse_diff_markdown(_load(slug)) + decomp = decomposable_changes(changes) + deltas = text_deltas(changes) + n_added = sum(1 for d in deltas if d.value is not None) + n_removed = sum(1 for d in deltas if d.prior_value is not None) + assert n_added == sum(1 for c in decomp if c.side == "added"), slug + assert n_removed == sum(1 for c in decomp if c.side == "removed"), slug + + +def test_text_deltas_exclude_obsoleted_terms(): + deltas = text_deltas(parse_diff_markdown(_load("2471-juxtaglomerular-complex-cells-merge"))) + assert all(d.term_id != "CL:1000618" for d in deltas) + + +# --- definition_refs: head-state refs for logical definitions -------------- + +_EDIT_FILE_SAMPLE = """Prefix(obo:=) + +# Class: obo:CL_0002336 (buccal mucosa cell) + +AnnotationAssertion(Annotation(oboInOwl:hasDbXref "GOC:tfm") Annotation(oboInOwl:hasDbXref "MESH:D009061") obo:IAO_0000115 obo:CL_0002336 "An epithelial cell that lines the oral cavity.") +AnnotationAssertion(rdfs:label obo:CL_0002336 "buccal mucosa cell") +EquivalentClasses(obo:CL_0002336 ObjectIntersectionOf(obo:CL_0000312 ObjectSomeValuesFrom(obo:BFO_0000050 obo:UBERON_0003729))) + +# Class: obo:CL_0000037 (hematopoietic stem cell) + +AnnotationAssertion(Annotation(oboInOwl:hasDbXref "GOC:tfm") Annotation(oboInOwl:hasDbXref "PMID:19022770") Annotation(oboInOwl:hasDbXref "http://en.wikipedia.org/wiki/Hematopoietic_stem_cell_(HSC)") obo:IAO_0000115 obo:CL_0000037 "A stem cell.") + +# Class: obo:CL_9999999 (undefined cell) + +AnnotationAssertion(rdfs:label obo:CL_9999999 "undefined cell") +""" + + +def test_definition_refs_reads_dbxrefs_off_the_definition(): + refs = definition_refs(_EDIT_FILE_SAMPLE) + assert refs["CL:0002336"] == ["GOC:tfm", "MESH:D009061"] + + +def test_definition_refs_survives_parens_in_a_xref_value(): + """A bracketed Wikipedia disambiguator must not truncate the ref list.""" + refs = definition_refs(_EDIT_FILE_SAMPLE) + assert refs["CL:0000037"] == [ + "GOC:tfm", + "PMID:19022770", + "http://en.wikipedia.org/wiki/Hematopoietic_stem_cell_(HSC)", + ] + + +def test_definition_refs_omits_terms_without_a_definition(): + refs = definition_refs(_EDIT_FILE_SAMPLE) + assert "CL:9999999" not in refs + + +def test_definition_refs_ignores_labels_and_logical_axioms(): + """Only IAO:0000115 assertions count; a label or EquivalentClasses must not.""" + refs = definition_refs(_EDIT_FILE_SAMPLE) + assert set(refs) == {"CL:0002336", "CL:0000037"} + # the xrefs must come from the definition axiom, not from any other line + assert all("rdfs:label" not in r for rs in refs.values() for r in rs)