The grammar

Three identifier forms carry the whole referencing scheme. Each is enforced by a regular expression on the Pydantic field that holds it.1

IdentifierRegular expressionExampleEnforced on
Finding model^OIFM_[A-Z]{3,4}_[0-9]{6}$OIFM_CDE_000101FindingModelFull.oifm_id
Attribute^OIFMA_[A-Z]{3,4}_[0-9]{6}$OIFMA_CDE_000632ChoiceAttributeIded.oifma_id, NumericAttributeIded.oifma_id
Choice value^OIFMA_[A-Z]{3,4}_[0-9]{6}\.\d+$OIFMA_CDE_000632.1ChoiceValueIded.value_code

The digit count is a module constant, ID_LENGTH, set to 6 and interpolated into all three patterns, so the three move together if it ever changes.

Reading one of these left to right gives you the whole address. OIFMA_CDE_000632.1 is value 1 of attribute 000632, contributed under organization code CDE. Because the attribute identifier is globally unique rather than scoped to its finding, an attribute code alone identifies the finding it belongs to, which is what makes the registry lookup in the next section possible.

The organization segment

The three-or-four-letter block names the contributing organization. It is the same code that appears as Organization.code and Person.organization_code, both constrained to ^[A-Z]{3,4}$. See OIDM organization code.

Seven organizations ship in the package’s seeded registry.2

CodeOrganization
MSFTMicrosoft
MGBMassGeneral Brigham
GMTSRadiology Gamuts Ontology
RSNARadiological Society of North America
ACRAmerican College of Radiology
CDEACR/RSNA Common Data Elements Project
OIDMOpen Imaging Data Model

Five of the seven appear in the published corpus. Counting the 2,382 registered finding model identifiers in ids.json on main by their organization segment gives the following distribution, and the 5,709 registered attribute identifiers give the second column.3

CodeFinding modelsAttributes
GMTS1,9333,867
OIDM256512
CDE115831
MGB47378
MSFT31121
Total2,3825,709

The organization segment records provenance, not ownership of the concept. A GMTS identifier means the definition entered the corpus from the Radiology Gamuts Ontology import; a CDE identifier means it was derived from an ACR and RSNA common data element. The content breakdown is in the content catalog.

Minting

Digits are random, not sequential. generate_oifm_id(source) and generate_oifma_id(source) uppercase the organization code they are given and append six digits drawn one at a time from a uniform random draw.1 There is no central allocator and no reservation step. Collisions are prevented after the fact by the registry rather than before the fact by coordination.

Identifiers are never written by hand. The authoring tools call the generators; the repository conventions state that ids.json is auto-generated and must never be edited manually.4

The registry and its check

ids.json at the root of the findingmodels repository is a flat, regenerated map with two members.3

{
  "oifm_ids": { "OIFM_CDE_000101": "cervical_rib.fm.json" },
  "attribute_ids": { "OIFMA_CDE_000632": ["cervical_rib.fm.json", "Presence"] }
}

oifm_ids maps each finding identifier to the file that defines it. attribute_ids maps each attribute identifier to a pair of file name and attribute name. Value codes are not registered separately, because they are derived from the attribute identifier.

The registry is rebuilt from defs/ by scripts/validator.py, which walks every definition file, validates it, and fails if a finding identifier or an attribute identifier is seen twice.5 That check is the only thing standing between random minting and a collision. A pre-commit hook runs the validator on every commit and stages the regenerated ids.json, the markdown renders, and the repository index, so a definition cannot land without the registry landing with it.

Round-trip regeneration, issue 42

The one documented way to lose an identifier is to round-trip a registered model through FindingModelBase. That class has no oifm_id field, so validating a stored definition against it silently discards the finding identifier and every attribute identifier. The helper that adds identifiers then sees none present and mints new ones.6

The issue, open since 2026-04-18, describes this as “a silent data-integrity hazard for any workflow that loads an existing .fm.json as a dict, needs to mutate it, and wants ID allocation for just the new part.” It was hit in a review flow that added a change-from-prior attribute to a legacy model carrying only presence; the published finding identifier and the existing attribute identifier were both regenerated, and external references to them would have become dangling. The workaround shipped in the content work operates on the dictionary directly and allocates only the identifiers genuinely missing, building value codes from the attribute identifier by hand. Four candidate fixes are enumerated in the issue and none has been chosen.

Stability

What is documented amounts to this.

  • An identifier, once minted and committed, names one definition file, and the validator enforces that no second file claims it.
  • The registry admits that an identifier can legitimately appear on more than one row of an external list. The MGB exam-oriented sub-taxonomies note that “an ID can appear on two rows where those earlier writebacks mapped two findings onto one model.” That is a property of those files, not of the registry.
  • Nothing in the repository documents a deprecation, retirement, or supersession mechanism for an identifier. There is no version field on a finding model in the released format, and schema versioning is an open task rather than a shipped feature.
  • The round-trip defect above is the known exception to stability in practice, and it is unresolved.

Downstream systems rely on this. Exam Finding Lists store nothing but the identifier and a display string, so a changed identifier breaks the link between a stored Observation and its definition. The content repository is the single source of finding model identity for the catalog site, which consumes defs/ as a git submodule, and for the extraction platform, which resolves identifiers through the published registry. See the repository map.

Footnotes

  1. finding_model.py, findingmodel main branch ↩ ↩2

  2. base_organizations.jsonl, findingmodel main branch ↩

  3. ids.json, findingmodels repository ↩ ↩2

  4. CLAUDE.md, findingmodels repository ↩

  5. validator.py, findingmodels repository ↩

  6. Issue 42, findingmodel repository ↩