Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions crates/ogar-cpic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@
//! is a deliberate, operator-gated follow-up, surfaced not silently taken.
//! Until then a consumer routes CPIC nodes on the reserved domain tag and keys
//! the concrete guideline by its (gene, drug) pair.
//!
//! # Curated subset of the full CPIC corpus
//!
//! This table is the small, **actionable** subset a consumer resolves directly.
//! The **full** CPIC corpus (all guidelines, pairs, alleles, diplotype→phenotype
//! maps) is the graph a reasoner walks, loaded separately as `graph:cpic`. The
//! contract: the curated row answers "what does CPIC say about *this* pair?" in
//! one lookup; the graph answers the open-ended "is there a guideline for X, and
//! what does it chain to?" A pair absent here *may* still have a CPIC guideline
//! in `graph:cpic` (this table carries only the small actionable subset) — but
//! many gene-drug pairs have no CPIC guideline at all and resolve in neither.
//! [`resolve`] returning `None` therefore means "no actionable guideline in this
//! curated table," not "no guideline exists anywhere"; consult `graph:cpic` for
//! the authoritative answer.

#![warn(missing_docs)]
#![forbid(unsafe_code)]
Expand Down Expand Up @@ -254,9 +268,18 @@ mod tests {
#[test]
fn nars_truth_leans_with_strength() {
let strong = Strength::Strong.nars_truth();
let moderate = Strength::Moderate.nars_truth();
let optional = Strength::Optional.nars_truth();
assert!(strong.0 > optional.0, "strong is higher-frequency");
assert!(strong.1 > optional.1, "strong is higher-confidence");
// Full monotone chain Strong > Moderate > Optional on BOTH axes — pins
// Moderate, which a Strong-vs-Optional-only check would leave unguarded.
assert!(
strong.0 > moderate.0 && moderate.0 > optional.0,
"frequency is monotone in strength",
);
assert!(
strong.1 > moderate.1 && moderate.1 > optional.1,
"confidence is monotone in strength",
);
// Bounded probabilities.
for s in [Strength::Strong, Strength::Moderate, Strength::Optional] {
let (f, c) = s.nars_truth();
Expand Down
94 changes: 83 additions & 11 deletions crates/ogar-fma/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@
//! name→partition→classid routing and the laterality, which carry regardless
//! of whether the numeric cross-reference is yet pinned.
//!
//! # Curated subset of the full FMA
//!
//! This atlas is the small, **classid-addressable** subset a consumer pulls —
//! the structures the clinical reasoning surfaces actually name. The **full**
//! Foundational Model of Anatomy (~80K structures) is the graph a reasoner
//! walks, loaded separately as `graph:fma`. The contract: the curated row is
//! the *address* (concept classid + name); the graph is what's *reasoned over*.
//! An `fma_id` left `None` here is the loose end that resolves against
//! `graph:fma`, never fabricated in this table.
//!
//! [`ogar-fma-skeleton`]: https://docs.rs/ogar-fma-skeleton

#![warn(missing_docs)]
Expand All @@ -65,6 +75,12 @@ pub enum FmaPartition {
System,
/// A tissue (bone marrow). Routes on `anatomical_structure` (`0x0A01`).
Tissue,
/// An articular cartilage *surface* — a named target surface on a bone
/// (femoral trochlea, talar dome, distal humeral epiphysis) that a
/// sonographic score reads cartilage from. Routes on `anatomical_structure`
/// (`0x0A01`); there is no dedicated cartilage concept in the Anatomy
/// codebook, and the full FMA structure lives in `graph:fma`.
ArticularSurface,
/// A skeletal element. Routes on `bone` (`0x0A03`); the *spatial* address
/// lives in [`ogar-fma-skeleton`](crate), not here.
Bone,
Expand All @@ -78,9 +94,10 @@ impl FmaPartition {
match self {
FmaPartition::Joint => ogar_vocab::class_ids::JOINT,
FmaPartition::Bone => ogar_vocab::class_ids::BONE,
FmaPartition::Organ | FmaPartition::System | FmaPartition::Tissue => {
ogar_vocab::class_ids::ANATOMICAL_STRUCTURE
}
FmaPartition::Organ
| FmaPartition::System
| FmaPartition::Tissue
| FmaPartition::ArticularSurface => ogar_vocab::class_ids::ANATOMICAL_STRUCTURE,
}
}
}
Expand Down Expand Up @@ -124,12 +141,13 @@ impl FmaStructure {
}

/// The full V3 render classid for this structure under a consumer's app
/// prefix (the lo-u16 render skin): `(concept as u32) << 16 | app_prefix`.
/// The hi-u16 is the shared `domain:concept` canon; the lo-u16 is the
/// per-consumer classview. Mirrors `ogar_vocab::render_classid`.
/// prefix: the hi-u16 is the shared `domain:concept` canon, the lo-u16 is
/// the per-consumer classview render skin. Delegates to
/// [`ogar_vocab::app::render_classid`] — the single source of the
/// `(concept << 16) | prefix` bit math, so this never drifts from canon.
#[must_use]
pub const fn render_classid(&self, app_prefix: u16) -> u32 {
((self.concept_id() as u32) << 16) | (app_prefix as u32)
ogar_vocab::app::render_classid(app_prefix, self.concept_id())
}
}

Expand All @@ -139,6 +157,10 @@ impl FmaStructure {
const ATLAS: &[FmaStructure] = &[
// Organs — route on `anatomical_structure` (0x0A01). FMA ids filled where
// canonical + stable (same practice as the skeleton atlas's bone ids).
// Sourced from the FMA reference ontology (FMAID xref); the viscera cluster
// numerically (kidney/lung/pancreas within FMA:719x–720x) because FMA
// entered the thoraco-abdominal organs as one adjacent block — not a sign
// of fabrication. Any id NOT confidently in a stable FMA release stays None.
FmaStructure {
name: "kidney",
fma_id: Some(7203),
Expand Down Expand Up @@ -173,10 +195,12 @@ const ATLAS: &[FmaStructure] = &[
FmaStructure {
// The classic podagra site. Numeric FMA id is a loose end (many
// per-toe MTP-joint ids exist; not fabricating the aggregate here).
// `Paired`, not `Unpaired`: the joint exists on both feet and a gout
// flare localizes to one side — a consumer must be able to prompt L/R.
name: "first metatarsophalangeal joint",
fma_id: None,
partition: FmaPartition::Joint,
laterality: Laterality::Unpaired,
laterality: Laterality::Paired,
},
// Systems / trees — route on `anatomical_structure` (0x0A01).
FmaStructure {
Expand All @@ -198,6 +222,27 @@ const ATLAS: &[FmaStructure] = &[
partition: FmaPartition::Tissue,
laterality: Laterality::Unpaired,
},
// Articular cartilage target surfaces — the sonographic-score C-plane
// targets. FMA ids are loose ends resolved through the FMA import
// (`graph:fma`); the name→partition→classid routing is stable regardless.
FmaStructure {
name: "femoral trochlea",
fma_id: None,
partition: FmaPartition::ArticularSurface,
laterality: Laterality::Paired,
},
FmaStructure {
name: "anterior talar dome",
fma_id: None,
partition: FmaPartition::ArticularSurface,
laterality: Laterality::Paired,
},
FmaStructure {
name: "anterior distal humeral epiphysis",
fma_id: None,
partition: FmaPartition::ArticularSurface,
laterality: Laterality::Paired,
},
];

/// Resolve an anatomical-structure name to its public FMA reference row.
Expand Down Expand Up @@ -285,12 +330,39 @@ mod tests {
assert_eq!(resolve("systemic arterial system").unwrap().fma_id, None,);
}

#[test]
fn cartilage_target_surfaces_resolve_and_route_on_structure() {
// The sonographic-score C-plane targets — added for the HEAD-US grounding.
for name in [
"femoral trochlea",
"anterior talar dome",
"anterior distal humeral epiphysis",
] {
let s = resolve(name).expect("cartilage surface in atlas");
assert_eq!(s.partition, FmaPartition::ArticularSurface);
assert_eq!(s.concept_id(), class_ids::ANATOMICAL_STRUCTURE);
assert_eq!(
s.fma_id, None,
"loose end resolved via graph:fma, not faked"
);
}
}

#[test]
fn render_classid_packs_concept_high_appid_low() {
let s = resolve("synovial joint").unwrap();
// q2 render skin 0x1000: hi-u16 = 0x0A04 (joint), lo-u16 = 0x1000.
assert_eq!(s.render_classid(0x1000), 0x0A04_1000);
assert_eq!((s.render_classid(0x1000) >> 16) as u16, class_ids::JOINT);
// Core prefix 0x0000 (PortSpec's default APP_PREFIX): hi-u16 = 0x0A04
// (joint), lo-u16 = the render skin. A real consumer's prefix is
// allocated by an operator naming ruling — this test does NOT bake in
// one (0x1000 is the reserved V3-adoption monitor marker, never a real
// prefix; see ogar_vocab::ports). It also pins agreement with the
// canonical bit math in ogar_vocab::app.
assert_eq!(s.render_classid(0x0000), 0x0A04_0000);
assert_eq!((s.render_classid(0x0000) >> 16) as u16, class_ids::JOINT);
assert_eq!(
s.render_classid(0x0042),
ogar_vocab::app::render_classid(0x0042, class_ids::JOINT),
);
}

#[test]
Expand Down
Loading