Skip to content

ogar-obo: write the reason module (OWL-EL completion + edge walk) and close the bake - #232

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/medcare-rs-continue-ufsazd
Aug 2, 2026
Merged

ogar-obo: write the reason module (OWL-EL completion + edge walk) and close the bake#232
AdaWorldAPI merged 1 commit into
mainfrom
claude/medcare-rs-continue-ufsazd

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

ogar-obo: write the reason module (OWL-EL completion + edge walk) and close the bake

The ogar-obo crate declared pub mod reason; with no file — the OWL-EL
completion the OBO EL profile exercises, and the "CURIE edge-propagation"
obo-crosswalk.json defers to "a later session". So the crate did not compile.
This writes that module and closes the bake.

reason

  • OboGraph::from_obo_str — parse the bake's source OBO form (mondo.obo /
    hp.obo / uberon.obo, or a faithful slice) into typed edges over the
    crate's own TermId / Predicate / OboNode model.
  • ancestors — the is_a subsumption saturation (transitive closure).
  • related_via_ancestry + anatomy_of (→Uberon) / phenotypes_of
    (→HPO) — transitive-role existential propagation: a subject inherits its
    is_a ancestors' existential edges (pneumonia inherits its parents'
    disease_has_location).
  • map_relationship — OBO predicate token → RO byte palette
    (disease_has_location→HasLocation, disease_has_feature/has_phenotype
    HasPhenotype, has_characteristic→HasQuality, part_of→PartOf).
  • bake_rows — parse → the canonical 512-byte SoA NodeRow buffer (one
    Row512 per term; classid = namespace.render_classid(0); CURIE numeric as
    the 24-bit identity; per-predicate edge-degree histogram), deterministic key
    order. This closes the crate's stated purpose and exercises the previously
    dead pack_key/pack_row primitives (made pub(crate)).

Posture

Pure public CC-BY / CC0 OBO reference — no PHI, no serialization, no cross-repo
compile dep (the loader tie stays a byte-layout contract). 6 unit tests (parse;
is_a saturation transitivity; existential propagation through ancestry;
deterministic bake; unknown-prefix drop). Clippy-clean, missing_docs-clean.

Consumer

AdaWorldAPI/MedCare-rs (PR #333) consumes ogar_obo::reason::OboGraph to walk
disease → anatomy/phenotype reasoning edges, replacing a local mirror it carried
while this crate did not compile ("consume the Core, never re-implement").

🤖 Generated with Claude Code

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 72b8046. Configure here.

if line == "[Term]" {
commit(&mut cur, &mut node, &mut name, &mut graph);
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-Term stanzas corrupt parsing

High Severity

from_obo_str only commits the current stanza when it sees [Term]. Real OBO files (mondo.obo, hp.obo, etc.) interleave [Typedef], [Instance], and other bracket headers. A later id: line can retarget cur without flushing the in-progress term, so terms are dropped or stored under the wrong id with another stanza’s edges.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 72b8046. Configure here.

&& let Some(t) = TermId::parse(obj)
{
node.rel.push((map_relationship(pred), t));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Obsolete terms keep edges and rows

Medium Severity

Docs state obsolete terms are kept for id only with no edges, and OboNode marks them as not tiled, but parsing still records is_a/relationship after is_obsolete: true, and bake_rows packs every node without checking obsolete. Reasoning and the SoA buffer can include retired terms and their relationships.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 72b8046. Configure here.

Comment thread crates/ogar-obo/src/reason.rs Outdated
}
frontier = next;
}
out

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cyclic is_a includes query term

Medium Severity

ancestors documents excluding the query term, but seen never includes the starting id. If the is_a graph cycles back to that term (including a direct self-parent), the term can appear in the returned ancestor list and pollute related_via_ancestry.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 72b8046. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72b8046db2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/ogar-obo/src/lib.rs Outdated
Comment on lines +114 to +118
Namespace::Uberon => 0x0A02,
Namespace::Mondo => 0x0B01,
Namespace::Hpo => 0x0B02,
Namespace::Pato => 0x0B03,
Namespace::Ro => 0x0B04,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use registered canonical concept IDs

Every baked OBO row currently aliases an existing canonical class: 0x0A02 is SKELETON, while 0x0B01 through 0x0B04 are AUTH_STORE, AUTH_ZITADEL, AUTH_ZANZIBAR, and AUTH_ORY_KETO in ogar-vocab. Consequently, loaders that route or select schemas by classid will interpret Uberon as skeleton and the clinical ontologies as IAM records. Allocate non-conflicting IDs in the canonical codebook and consume those constants here rather than hard-coding occupied slots.

Useful? React with 👍 / 👎.

Comment on lines +247 to +252
deg[Predicate::IsA as usize] = node.is_a.len() as u16;
for (p, _) in &node.rel {
deg[*p as usize] = deg[*p as usize].saturating_add(1);
}
for (i, d) in deg.iter().enumerate() {
row.0[EDGES_OFFSET + i] = (*d).min(255) as u8;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the canonical EdgeBlock encoding

For any node with an is_a or relationship edge, this writes predicate counts into bytes that the stated NodeRow consumer interprets as the canonical 12-in-family/4-out-of-family EdgeBlock; the existing compiled_class_to_noderow path in crates/ogar-from-ruff/src/lance_sink.rs accordingly constructs an actual EdgeBlock, not a histogram. node_rows_from_le_bytes will accept these rows structurally but downstream traversal will read degree bytes as adjacency data. Emit a valid EdgeBlock, or leave it reserved and return the promised out-of-line triples separately.

Useful? React with 👍 / 👎.

Comment thread crates/ogar-obo/src/reason.rs Outdated
Comment on lines +85 to +87
if line == "[Term]" {
commit(&mut cur, &mut node, &mut name, &mut graph);
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge End term parsing at every stanza boundary

When a normal full OBO file places [Typedef] stanzas after its terms, only [Term] triggers commit; the following typedef id: therefore overwrites the last term's cur, dropping that term and eventually inserting an RO typedef as an OboNode with accumulated term/typedef state. Commit and clear on every stanza header, and only consume term tags while inside a [Term] stanza.

Useful? React with 👍 / 👎.

Comment thread crates/ogar-obo/src/reason.rs Outdated
Comment on lines +101 to +106
} else if let Some(rest) = line.strip_prefix("relationship: ") {
let mut it = rest.split_whitespace();
if let (Some(pred), Some(obj)) = (it.next(), it.next())
&& let Some(t) = TermId::parse(obj)
{
node.rel.push((map_relationship(pred), t));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse intersection_of logical definitions

For HPO genus-differentia definitions expressed as intersection_of: <role> <CURIE>, this parser falls through because it recognizes only relationship: edges. As a result, the advertised anatomy and quality edges harvested from HPO logical definitions are never added—particularly HasAnatomy, which no current parsing branch can produce—so reasoning walks over those standard definitions return empty results. Parse intersection_of role/object forms and map the supported anatomy and quality roles.

Useful? React with 👍 / 👎.

@AdaWorldAPI
AdaWorldAPI force-pushed the claude/medcare-rs-continue-ufsazd branch from 72b8046 to 23426c6 Compare August 2, 2026 15:32
…_of / phenotypes_of)

`saturate` computes the aggregate EL closure; a consumer resolving ONE entity
(a disease → its anatomy site + phenotypes, e.g. medcare-first-thought's
ontology grounding) needs the per-term walk. These operate directly on
`parse_obo`'s `id -> node` map, applying the same is_a-saturation +
existential-role-propagation rules to a single subject:

  * `ancestors` — transitive is_a closure of a term (self excluded, cycle-safe:
    the start id is pre-seeded into `seen` so a self/loop parent never re-emits
    the query term), deterministic order, depth-capped.
  * `related_via_ancestry` — every `pred` target reachable from the term or its
    is_a ancestors (the subject inherits its ancestors' existential edges).
  * `anatomy_of` / `phenotypes_of` — the Uberon (Mondo→Uberon = HasLocation) /
    HPO (Mondo→Hpo = HasPhenotype) conveniences over that walk, matching the
    crate's namespace-pair `classify`.

Additive over the merged #231 crosswalk/saturate work; no change to the bake,
crosswalk, concept-ids, or `saturate`. 1 new unit test (ancestry + inherited
anatomy + own phenotype + self-exclusion).

Generated by [Claude Code](https://claude.com/claude-code)
@AdaWorldAPI
AdaWorldAPI force-pushed the claude/medcare-rs-continue-ufsazd branch from 23426c6 to 2fd1675 Compare August 2, 2026 15:50
@AdaWorldAPI
AdaWorldAPI merged commit 2764927 into main Aug 2, 2026
1 check passed
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.

2 participants