From 8bfe1ec299c0c703ded6269078693728517cf4b3 Mon Sep 17 00:00:00 2001 From: James Kent Date: Sat, 17 Oct 2026 13:02:15 -0500 Subject: [PATCH 1/4] Make pondie's schema checks pass on main - Regenerate the extraction YAMLs with pondie.schema.generate: formatting only (quote style, wrapping, key order), plus the header now naming the generator. - extraction-to-storage map: add derivations for CategoryDistribution.category_normalized, CoordinateSet.id/.table/.text_excerpt and Group.age_unit_normalized, the five deterministic fields it did not say how to fill. - storage-parameter-priorities: add the 15 storage fields with no entry. pondie's generate --check, map, priorities, linkml and field_provenance checks pass. Co-Authored-By: Claude Opus 5.5 --- .../linkml/extraction-to-storage.map.yaml | 40 +++++++ .../linkml/neuroimaging-study-extraction.yaml | 2 +- .../acquisition.yaml | 8 +- .../analysis.yaml | 32 +++--- .../analysis_enums.yaml | 101 +++++++++--------- .../assessment.yaml | 4 +- .../neuroimaging-study-extraction/design.yaml | 8 +- .../neuroimaging-study-extraction/group.yaml | 4 +- .../neuroimaging-study-extraction/region.yaml | 4 +- .../neuroimaging-study-extraction/study.yaml | 4 +- .../neuroimaging-study-extraction/task.yaml | 8 +- storage-parameter-priorities.yaml | 15 +++ 12 files changed, 141 insertions(+), 89 deletions(-) diff --git a/src/study_schema/linkml/extraction-to-storage.map.yaml b/src/study_schema/linkml/extraction-to-storage.map.yaml index 55dd9c6..d7917b0 100644 --- a/src/study_schema/linkml/extraction-to-storage.map.yaml +++ b/src/study_schema/linkml/extraction-to-storage.map.yaml @@ -81,6 +81,15 @@ derivations: transform: generate Assessment.id: transform: generate + CategoryDistribution.category_normalized: + transform: derive + source_representation: sibling_field + source_field: CategoryDistribution.category + notes: >- + `pondie.normalization.sex_distribution` and `handedness_distribution` fold the + study's wording in `category` onto the answer set of the distribution the entry + belongs to. A distribution with no normalizer (gender, race, ethnicity) leaves it + unset; unset is not UNKNOWN, which says a normalizer ran and found no match. Condition.id: transform: generate ConnectivityEdge.directionality: @@ -103,10 +112,41 @@ derivations: coherence: undirected structural_covariance: undirected multivariate: undirected + CoordinateSet.id: + transform: derive + source_representation: ingestion_artifact + source_field: ParsedAnalysis.key + notes: >- + The `analysis#` key the coordinate parse gave the analysis this set was read from + (neuroimaging-paper-parse), the key `Analysis.source_table_analysis` names. It hashes + the set's name, space and points, so nothing here is extracted or minted. + CoordinateSet.table: + transform: derive + source_representation: ingestion_artifact + source_field: ParsedAnalysis.table_id + notes: >- + The Table whose `Table.id` is the parse analysis's `table_id`. Unset when the set was + read from the text or a figure, where `table_id` is absent. + CoordinateSet.text_excerpt: + transform: derive + source_representation: ingestion_artifact + source_field: ParsedAnalysis.text_spans + notes: >- + The paper's text over the analysis's `text_spans`, the sentences its coordinates were + read from. Unset for a set read from a table. Device.id: transform: generate Region.id: transform: generate + Group.age_unit_normalized: + transform: derive + source_representation: sibling_field + source_field: Group.age_unit + notes: >- + `pondie.normalization.age_unit` folds the source's wording in `age_unit` onto the + AgeUnit vocabulary, UNKNOWN where the wording does not say. `age_unit` keeps the + wording, and its own entry under free_text_normalizations tidies spelling for + queries; this field is the comparable one. Group.id: transform: generate notes: UUID assigned by mapper; extraction local_id stored in audit log. diff --git a/src/study_schema/linkml/neuroimaging-study-extraction.yaml b/src/study_schema/linkml/neuroimaging-study-extraction.yaml index 1cf7662..14bc544 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction.yaml @@ -11,7 +11,7 @@ name: neuroimaging-study-extraction title: Neuroimaging Study Extraction Schema description: |- Immutable, evidence-first structured output from extracting a neuroimaging publication. - This schema is generated from neuroimaging-study-storage by gen_extraction_schema.py. It has the same classes with the same slots in the same nesting; what differs is that every source-derived value is wrapped. An ExtractedValue records whether a value was found (extraction_status), the value itself when it was, how it relates to the source (value_source), why there is none when there is not (unreported_reason), and the exact supporting spans (evidence). A slot needing a scalar uses a typed subtype -- ExtractedString, ExtractedInteger, ExtractedNumber, ExtractedBoolean. Storage's vocabularies come across whole, each field keeping the range storage gives it: a closed enum takes a permissible value and nothing else, and an open one takes a permissible value or, when none fits, the source's own wording. Identifiers, type designators, and pipeline provenance use native LinkML types, because nothing in the paper supplies them. + This schema is generated from neuroimaging-study-storage by pondie.schema.generate. It has the same classes with the same slots in the same nesting; what differs is that every source-derived value is wrapped. An ExtractedValue records whether a value was found (extraction_status), the value itself when it was, how it relates to the source (value_source), why there is none when there is not (unreported_reason), and the exact supporting spans (evidence). A slot needing a scalar uses a typed subtype -- ExtractedString, ExtractedInteger, ExtractedNumber, ExtractedBoolean. Storage's vocabularies come across whole, each field keeping the range storage gives it: a closed enum takes a permissible value and nothing else, and an open one takes a permissible value or, when none fits, the source's own wording. Identifiers, type designators, and pipeline provenance use native LinkML types, because nothing in the paper supplies them. Evidence is present when one or more independently sufficient EvidenceSets were found, or not_found when a value was extracted but no span supports it. For a not_reported attribute, omit value and set evidence.status to not_applicable. Entities refer to each other by document-local local_id; ingestion resolves them and mints the storage identifiers. Do not manually correct an extraction record: create a new extraction version or a separate correction record. Rules this schema cannot state -- which papers are skipped before extraction, how a multivalued extracted fact is shaped, which invariants the structural validator enforces, and what a paper may report that no field holds -- are in extraction-readme.md. Everything this schema does that is not a projection of storage is in extraction-deviations.yaml. license: CC0-1.0 diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/acquisition.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/acquisition.yaml index a66311a..66889c6 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/acquisition.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/acquisition.yaml @@ -98,14 +98,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true acquisition_type: range: string required: true @@ -289,14 +289,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable identifier used by Acquisition foreign-key references. manufacturer: range: ExtractedString diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/analysis.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/analysis.yaml index a593987..4bac9c7 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/analysis.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/analysis.yaml @@ -34,14 +34,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true name: range: ExtractedString required: true @@ -278,14 +278,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable identifier used by Analysis foreign-key references. family: range: ExtractedMeasureFamily @@ -391,14 +391,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable identifier used by Analysis foreign-key references. description: range: ExtractedString @@ -435,14 +435,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable identifier used by Analysis foreign-key references. model_family: range: ExtractedModelFamily @@ -524,14 +524,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: >- Stable identifier, referenced by an Effect's `cells` and its `mediation.mediator`, and by `interaction_with`. @@ -668,14 +668,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable identifier used by Analysis foreign-key references. inference_level: range: ExtractedString @@ -743,14 +743,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable table identifier used by Analysis foreign-key references. table_number: range: ExtractedString @@ -784,14 +784,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: >- The key the coordinate parse gave this set, `ParsedAnalysis.key` in neuroimaging-paper-parse: `analysis#`, the same key `Analysis.source_table_analysis` names, where `h` hashes diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/analysis_enums.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/analysis_enums.yaml index 8963fea..c988f37 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/analysis_enums.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/analysis_enums.yaml @@ -614,69 +614,66 @@ enums: times. CoordinateRole: description: >- - What a set of reported coordinates is, by what it is to the study and not by where - the paper prints it. A coordinate that appears in a figure takes the role of what - it is: peaks the study found are results even when a figure shows them. The role - is judged for the coordinate-based meta-analysis these coordinates feed. When a - set could be a `result` or another role, it is a `result`: a result missed costs - the meta-analysis more than a non-result kept. Open vocabulary: write the source's - own wording when nothing here fits. + What a set of reported coordinates is, by what it is to the study and not by where the + paper prints it. A coordinate that appears in a figure takes the role of what it is: + peaks the study found are results even when a figure shows them. The role is judged + for the coordinate-based meta-analysis these coordinates feed. When a set could be a + `result` or another role, it is a `result`: a result missed costs the meta-analysis + more than a non-result kept. Open vocabulary: write the source's own wording when nothing + here fits. permissible_values: result: description: >- - A finding of this study: the peaks, clusters, local maxima or centres of - gravity of a tested effect, whether listed in a table or shown in a figure - (the "patients > controls" and "adults > children" peaks drawn on a brain). A - coordinate reported with its statistics is a result even when the paper then - uses it as a region of interest or seed, and so is a peak from the authors' - own earlier study or dataset given with its statistics. A region of interest - or seed whose coordinates are reported together with the statistics of the - effect measured in it is a result, whether this study defined it or took it - from another source, because the ROI analysis is the finding: an ROI from - Supekar et al. (2013) reported with a connectivity correlation, functional - ROIs at Neurosynth meta-analysis peaks reported with percent signal change and - ICC, a vmPFC 8 mm sphere centred at -6, 50, 1 that "shows increased coupling - ... p < 0.001". A set that mixes this study's peaks with a cited peak is a - result. The slice and crosshair positions of a figure are results, even when - no peak or statistic is stated at them, unless the paper attributes them to - another study: crosshairs marking another paper's coordinate are a - `reference`. In MEG or EEG, the peak of a tested effect in source space is a - result; fNIRS channels reported with statistics for a significant effect are - results. `result_kind` says what kind of analysis produced it. + A finding of this study: the peaks, clusters, local maxima or centres of gravity + of a tested effect, whether listed in a table or shown in a figure (the "patients + > controls" and "adults > children" peaks drawn on a brain). A coordinate reported + with its statistics is a result even when the paper then uses it as a region of + interest or seed, and so is a peak from the authors' own earlier study or dataset + given with its statistics. A region of interest or seed whose coordinates are reported + together with the statistics of the effect measured in it is a result, whether this + study defined it or took it from another source, because the ROI analysis is the + finding: an ROI from Supekar et al. (2013) reported with a connectivity correlation, + functional ROIs at Neurosynth meta-analysis peaks reported with percent signal change + and ICC, a vmPFC 8 mm sphere centred at -6, 50, 1 that "shows increased coupling + ... p < 0.001". A set that mixes this study's peaks with a cited peak is a result. + The slice and crosshair positions of a figure are results, even when no peak or + statistic is stated at them, unless the paper attributes them to another study: + crosshairs marking another paper's coordinate are a `reference`. In MEG or EEG, + the peak of a tested effect in source space is a result; fNIRS channels reported + with statistics for a significant effect are results. `result_kind` says what kind + of analysis produced it. anchor: description: >- - A location that defines an analysis rather than resulting from one: a region - of interest, a connectivity seed, a stimulation target such as an rTMS site, a - network node. `anchor_kind` says which. An ROI, seed or sphere given only as a - definition, with no statistics reported with its coordinates, is an anchor, - including one placed at a cited location or at a peak ("a 6 mm sphere centred - on the peak"). The same coordinates reported with the statistics of the effect - measured there are a `result`. + A location that defines an analysis rather than resulting from one: a region of + interest, a connectivity seed, a stimulation target such as an rTMS site, a network + node. `anchor_kind` says which. An ROI, seed or sphere given only as a definition, + with no statistics reported with its coordinates, is an anchor, including one placed + at a cited location or at a peak ("a 6 mm sphere centred on the peak"). The same + coordinates reported with the statistics of the effect measured there are a `result`. localization: description: >- - Where a sensor, electrode, fiducial, intracranial contact, dipole or - anatomical landmark is. Metadata about a measurement, not a finding. MEG and - EEG dipole or source fits are localizations, including fits made per subject: - an EEG meta-analysis could count them as results, which is why the modality - stays recorded, but this role is judged for a coordinate-based meta-analysis. - The peak of a tested effect in source space is a `result`. + Where a sensor, electrode, fiducial, intracranial contact, dipole or anatomical + landmark is. Metadata about a measurement, not a finding. MEG and EEG dipole or + source fits are localizations, including fits made per subject: an EEG meta-analysis + could count them as results, which is why the modality stays recorded, but this + role is judged for a coordinate-based meta-analysis. The peak of a tested effect + in source space is a `result`. reference: description: >- - Coordinates from other studies, quoted for comparison or marked on a figure: - crosshairs marking a dACC peak from a prior paper, the visual word form area - from Cohen et al. A peak from the authors' own earlier study or dataset is a - reference when it is given without statistics and a `result` when it is given - with them. A cited location used to place a region of interest is not a - reference: it is an `anchor` when only the definition is given, and a `result` - when the ROI is reported with the statistics of the effect measured in it. + Coordinates from other studies, quoted for comparison or marked on a figure: crosshairs + marking a dACC peak from a prior paper, the visual word form area from Cohen et + al. A peak from the authors' own earlier study or dataset is a reference when it + is given without statistics and a `result` when it is given with them. A cited location + used to place a region of interest is not a reference: it is an `anchor` when only + the definition is given, and a `result` when the ROI is reported with the statistics + of the effect measured in it. other: description: >- - A real brain coordinate in the paper that is none of the above: a simulated - source position or lesion centre, a worked-example voxel of an atlas. Numbers - that are not brain coordinates (lattice points, phantom or rodent bregma - positions, orientation vectors) are not coordinates at all and are left out of - the parse, not given this role. - + A real brain coordinate in the paper that is none of the above: a simulated source + position or lesion centre, a worked-example voxel of an atlas. Numbers that are + not brain coordinates (lattice points, phantom or rodent bregma positions, orientation + vectors) are not coordinates at all and are left out of the parse, not given this + role. ResultKind: description: >- The kind of analysis a `result` came from, coarse enough to read from a table or a sentence. diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/assessment.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/assessment.yaml index 46a2a40..bac330d 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/assessment.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/assessment.yaml @@ -26,14 +26,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable identifier. name: range: ExtractedString diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/design.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/design.yaml index 1107649..5eed918 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/design.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/design.yaml @@ -192,14 +192,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true name: range: ExtractedString required: true @@ -227,14 +227,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true name: range: ExtractedString required: true diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/group.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/group.yaml index 846c737..95edcf9 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/group.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/group.yaml @@ -48,14 +48,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true name: range: ExtractedString required: true diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/region.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/region.yaml index 901b020..812577f 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/region.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/region.yaml @@ -66,14 +66,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable identifier. name: range: ExtractedString diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/study.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/study.yaml index ff2d515..646bdd8 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/study.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/study.yaml @@ -56,14 +56,14 @@ classes: description: Provenance for this extraction record. stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: >- Stable identifier for the study record, such as a DOI-based or repository-local identifier. diff --git a/src/study_schema/linkml/neuroimaging-study-extraction/task.yaml b/src/study_schema/linkml/neuroimaging-study-extraction/task.yaml index 0d0153c..3b0b9fd 100644 --- a/src/study_schema/linkml/neuroimaging-study-extraction/task.yaml +++ b/src/study_schema/linkml/neuroimaging-study-extraction/task.yaml @@ -159,14 +159,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true name: range: ExtractedString description: >- @@ -269,14 +269,14 @@ classes: attributes: stable_id: range: string - pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ description: >- UUID minted by the store the first time this entity appears, never by an extractor, and kept across re-runs so a later extraction matches the same entity. local_id: - identifier: true range: string required: true + identifier: true description: Stable identifier. name: range: ExtractedString diff --git a/storage-parameter-priorities.yaml b/storage-parameter-priorities.yaml index cccedb7..09ec5a6 100644 --- a/storage-parameter-priorities.yaml +++ b/storage-parameter-priorities.yaml @@ -111,6 +111,7 @@ Group: age_maximum: 0 age_median: 0 age_unit: 0 + age_unit_normalized: n/a sex_distribution: 1 gender_distribution: 1 handedness_distribution: 1 @@ -119,6 +120,7 @@ Group: is_healthy: 0 medical_condition: 0 population_characteristics: 0 + other_characteristics: n/a diagnostic_system: 1 diagnostic_instrument: 1 clinical_characteristics: 1 @@ -126,9 +128,13 @@ Group: medication_status: 1 education_summary: 2 socioeconomic_status_summary: 2 + sample_source: 0 + sample_source_reference: 1 + sample_overlap_count: 1 CategoryDistribution: category: 1 + category_normalized: n/a count: 1 percentage: 1 denominator: 1 @@ -155,6 +161,7 @@ Task: paradigm_category: n/a reference_ids: n/a variant_qualifiers: 1 + references: 1 description: 1 conditions: 1 design_type: 1 @@ -171,6 +178,7 @@ Acquisition: id: n/a acquisition_type: n/a modality: 0 + brain_coverage: 1 acquisition_duration_seconds: 1 device: 2 @@ -249,6 +257,7 @@ InferenceSettings: clusterwise_threshold_value: 0 multiple_comparison_method: 0 correction_scope: 0 + correction_regions: 1 search_volume: 2 neighborhood_definition: 2 tfce_used: 1 @@ -261,6 +270,7 @@ Analysis: id: n/a name: 0 definition: 0 + outcome: 0 prespecification: 0 interpretations: 0 spatial_scope: 0 @@ -269,12 +279,14 @@ Analysis: measure: 0 groups: 0 effect: 0 + mirror_of: n/a details: 1 acquisitions: 0 tasks: 0 model_estimation: 0 inference_settings: 0 tables: 0 + source_table_analysis: 0 statistical_maps: 1 coordinate_space: 0 assessments: 1 @@ -282,6 +294,7 @@ Analysis: Effect: cells: 0 + kind: 1 mediation: 1 statistic: 0 @@ -341,7 +354,9 @@ TaskQualifier: Condition: id: n/a name: 0 + condition_kind: 1 description: 0 + stimulus_content: 1 StatisticalMap: id: n/a From 00754a992741185c2b376c352989a13547db81f8 Mon Sep 17 00:00:00 2001 From: James Kent Date: Sat, 17 Oct 2026 13:02:59 -0500 Subject: [PATCH 2/4] Name the generator in the map header as the extraction header does Co-Authored-By: Claude Opus 5.5 --- src/study_schema/linkml/extraction-to-storage.map.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/study_schema/linkml/extraction-to-storage.map.yaml b/src/study_schema/linkml/extraction-to-storage.map.yaml index d7917b0..83e1848 100644 --- a/src/study_schema/linkml/extraction-to-storage.map.yaml +++ b/src/study_schema/linkml/extraction-to-storage.map.yaml @@ -4,7 +4,7 @@ # # THE MAP IS AN IDENTITY MAP # -------------------------- -# The extraction schema is generated from the storage schema by gen_extraction_schema.py, +# The extraction schema is generated from the storage schema by pondie.schema.generate, # so the two have the same classes with the same slots in the same nesting. A storage # field is therefore populated from the extraction field of the same name on the same # class, and the mapper needs no instruction to do it. What the mapper does uniformly: From 9dd18007f0ffd1f686f79abb2e8ae0a34ca9b2f6 Mon Sep 17 00:00:00 2001 From: James Kent Date: Sat, 17 Oct 2026 13:04:36 -0500 Subject: [PATCH 3/4] Point the prose at pondie's generator and checks The docs still named gen_extraction_schema.py and the check_*.py scripts, which live in pondie as pondie.schema.generate and pondie.schema.checks.*. Drop the link to test_map_coverage.py, which exists in neither repository. Co-Authored-By: Claude Opus 5.5 --- extraction-readme.md | 10 ++++------ schema-tutorial.md | 16 ++++++++-------- .../linkml/extraction-to-storage.map.yaml | 2 +- .../neuroimaging-study-storage/subsets.yaml | 2 +- storage-schema-design-notes.md | 2 +- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/extraction-readme.md b/extraction-readme.md index 59f7fb7..1396773 100644 --- a/extraction-readme.md +++ b/extraction-readme.md @@ -110,7 +110,7 @@ whether the vocabulary is short a value. Twenty-six fields are open, including `Analysis.spatial_scope`, `Measure.family`, `Task.stimulus_modality`, and `Task.response_modality`. Which a field is, is storage's decision and not one extraction may revisit; -`check_extraction_to_storage_map.py` fails if the projection opens or closes a vocabulary. +`pondie.schema.checks.extraction_to_storage_map` fails if the projection opens or closes a vocabulary. ### Missingness has one encoding, and it is `not_reported` @@ -642,7 +642,7 @@ Extraction carries storage's own vocabularies, so no normalization step is neede arrives is already one storage accepts. Facts the storage schema needs that no extraction field supplies. These are exactly the -storage fields marked `in_subset: [deterministic]`, and `check_extraction_to_storage_map.py` +storage fields marked `in_subset: [deterministic]`, and `pondie.schema.checks.extraction_to_storage_map` asserts that correspondence in both directions, so this table cannot quietly fall behind: | Storage field | How the mapper fills it | @@ -676,11 +676,9 @@ extraction fields had nowhere to land and a section of the map called `absorbed_ recorded where each one went instead. That is gone. The extraction schema is generated from the storage schema by -`gen_extraction_schema.py`, so an extraction field with no storage counterpart is now +`pondie.schema.generate`, so an extraction field with no storage counterpart is now impossible by construction rather than merely documented, and -`check_extraction_to_storage_map.py` asserts the correspondence in both directions. -[test_map_coverage.py](test_map_coverage.py) exercises each failure mode against mutated -copies of the real schemas, because a completeness check that cannot fail is worth nothing. +`pondie.schema.checks.extraction_to_storage_map` asserts the correspondence in both directions. The shapes that used to need absorbing are listed as candidates at the end of [extraction-deviations.yaml](src/study_schema/linkml/extraction-deviations.yaml). They were not obviously wrong -- diff --git a/schema-tutorial.md b/schema-tutorial.md index f7b2a52..e77adff 100644 --- a/schema-tutorial.md +++ b/schema-tutorial.md @@ -196,7 +196,7 @@ something — see §2.4. **`unique_keys`** says a combination may not repeat wit | Hand-edited? | Never — re-running the mapper regenerates it | Never — produce a new version or a separate correction record | | Size | 52 classes, 57 vocabularies, 365 fields, across 13 modules | Generated: the same classes plus the evidence types | -The extraction schema is **generated** from storage by `gen_extraction_schema.py`, so the two cannot +The extraction schema is **generated** from storage by `pondie.schema.generate`, so the two cannot drift. The whole of the projection: - fields marked `deterministic` are dropped (code fills them, so there is nothing to read off a page); @@ -289,7 +289,7 @@ low-priority and inside it. `deterministic` is what an API, a parser, or a derivation supplies: bibliographic metadata, PubMed publication types, minted identifiers, `Table.coordinate_space`, NeuroVault maps. `model_extracted` is everything read off the page. Every field is in exactly one of the two, and -`check_field_provenance.py` fails if not; identifiers and type designators are in neither by +`pondie.schema.checks.field_provenance` fails if not; identifiers and type designators are in neither by definition. A derivation need not come from outside the record. `ConnectivityEdge.directionality` is derived from @@ -1505,7 +1505,7 @@ not change shape because the question changed; the cells do. > **A value the article does not report takes `extraction_status: not_reported`.** -No vocabulary offers an `unstated` member, and `check_schema.py` fails the build if one appears — +No vocabulary offers an `unstated` member, and `pondie.schema.checks.linkml` fails the build if one appears — silence recorded two ways is silence a query finds half of. Nothing is lost by that rule, because a `not_reported` wrapper is still **present**: everything the @@ -1534,7 +1534,7 @@ and an extractor is asked for nine. `EffectKind` is bound by no field at all (§ 37 vocabularies come to 26 + 10 + 1. Whether a field is open or closed is storage's decision, and the projection may not revisit it: -`check_extraction_to_storage_map.py` fails if extraction opens a closed vocabulary or closes an open +`pondie.schema.checks.extraction_to_storage_map` fails if extraction opens a closed vocabulary or closes an open one. The second direction is the quiet one — closing an open vocabulary works fine and costs you the signal that a value is missing. @@ -1815,10 +1815,10 @@ model, `FactorLevel.level` within its term, `(term, level)` within one `Effect`, **The checks.** ```bash -python3 check_schema.py # both schemas load; no rule names a missing slot -python3 check_field_provenance.py --strict # every field says how it gets filled -python3 check_extraction_to_storage_map.py # the projection has not drifted -python3 gen_extraction_schema.py --check # the committed extraction tree is current +python3 -m pondie.schema.checks.linkml # both schemas load; no rule names a missing slot +python3 -m pondie.schema.checks.field_provenance --strict # every field says how it gets filled +python3 -m pondie.schema.checks.extraction_to_storage_map # the projection has not drifted +python3 -m pondie.schema.generate --check # the committed extraction tree is current python3 -m pytest # everything the review layer stands on python3 review/validate_record.py --record .extraction.json --text ``` diff --git a/src/study_schema/linkml/extraction-to-storage.map.yaml b/src/study_schema/linkml/extraction-to-storage.map.yaml index 83e1848..4a0f899 100644 --- a/src/study_schema/linkml/extraction-to-storage.map.yaml +++ b/src/study_schema/linkml/extraction-to-storage.map.yaml @@ -32,7 +32,7 @@ # fact copied from the parsed paper (`copy`, naming `source_artifact` and # `source_field` in neuroimaging-paper-parse), a fact an API states, an identifier the # pipeline mints, a value computed from an ingestion artifact. They are exactly the storage fields marked -# `in_subset: [deterministic]`, and check_extraction_to_storage_map.py asserts that +# `in_subset: [deterministic]`, and pondie.schema.checks.extraction_to_storage_map asserts that # correspondence in both directions. # # free_text_normalizations diff --git a/src/study_schema/linkml/neuroimaging-study-storage/subsets.yaml b/src/study_schema/linkml/neuroimaging-study-storage/subsets.yaml index d7da71f..f9a2919 100644 --- a/src/study_schema/linkml/neuroimaging-study-storage/subsets.yaml +++ b/src/study_schema/linkml/neuroimaging-study-storage/subsets.yaml @@ -58,6 +58,6 @@ subsets: change it is to re-run the extractor. Every field is in exactly one of `deterministic` and `model_extracted`, which is what - check_field_provenance.py enforces. Neither generates a standalone schema: the two are + pondie.schema.checks.field_provenance enforces. Neither generates a standalone schema: the two are interleaved down every path from the tree root, so pruning to either alone strands the other's fields. diff --git a/storage-schema-design-notes.md b/storage-schema-design-notes.md index ea3ee71..db1a6f4 100644 --- a/storage-schema-design-notes.md +++ b/storage-schema-design-notes.md @@ -244,7 +244,7 @@ instrument-bearing groups are wrong-shaped under it. The binding belongs at grou ### The cost that was accepted -`gen_extraction_schema.py` carries plain references through unwrapped, so the link has no +`pondie.schema.generate` carries plain references through unwrapped, so the link has no `ExtractedValue`: no evidence span, no `extraction_status`. The justifying quote moves to `Assessment.description`, whose wording now asks for the purpose the source gave for administering the instrument. From 3b0ea0029996bac98ab2edde448a673caebe54fd Mon Sep 17 00:00:00 2001 From: James Kent Date: Sat, 17 Oct 2026 13:06:46 -0500 Subject: [PATCH 4/4] Point the validator references at pondie's record validator review/validate_record.py is pondie.extraction.record.validate (the Validator and its CLI) with the cross-class rules in pondie/extraction/record/rules.py. The tutorial's review/examples/ directory does not exist; point at the example record in this repository. Co-Authored-By: Claude Opus 5.5 --- extraction-readme.md | 4 ++-- schema-tutorial.md | 11 +++++------ .../linkml/neuroimaging-study-storage/analysis.yaml | 4 ++-- storage-schema-design-notes.md | 12 ++++++------ 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/extraction-readme.md b/extraction-readme.md index 1396773..da3d13c 100644 --- a/extraction-readme.md +++ b/extraction-readme.md @@ -154,7 +154,7 @@ This rule does **not** change a value that names something the source reported: ### Evidence invariants -Enforced by [review/validate_record.py](review/validate_record.py): +Enforced by `pondie.extraction.record.validate`: - `extraction_status: not_reported` ⇒ omit `value`, and `evidence.status` must be `not_applicable`. A field recorded this way therefore carries **no span**: there is no @@ -224,7 +224,7 @@ composite of subscales still names the instrument the numbers came from. What th *was* goes in `source_definition`, and there it is not optional: `FactorLevel.timepoints` names the occasions a factor *compares*, a derived column has no levels to hang them on, so this is the only place saying which occasions it spanned. A derived column with neither slot filled is -an unattributed number, and `validate_record.py` says so. +an unattributed number, and `pondie.extraction.record.validate` says so. The region's own facts go on the `Region`: `name` in the source's wording, `definition_method` for how it was delimited — `same_study_analysis` where it came from this diff --git a/schema-tutorial.md b/schema-tutorial.md index e77adff..3c17edb 100644 --- a/schema-tutorial.md +++ b/schema-tutorial.md @@ -208,7 +208,7 @@ drift. The whole of the projection: record, rather than a list of wrappers; - an inlined child is projected too; a reference stays a reference and resolves through `local_id`; - `minimum_value`, `rules` and `unique_keys` are **dropped**, because they constrain a scalar and the - wrapper is in the way. Those move into `review/validate_record.py` (chapter 12). + wrapper is in the way. Those move into `pondie.extraction.record.validate` and `pondie/extraction/record/rules.py` (chapter 12). You fill the extraction schema. You read either. Everything this tutorial says about a field is true of both, because they are the same field. @@ -1635,7 +1635,7 @@ change if a different contrast came off the same model? ### 12.3 What the validator checks -`review/validate_record.py` runs three tiers over a finished extraction record. Errors mean the record +`pondie.extraction.record.validate` (with the rules in `pondie/extraction/record/rules.py`) runs three tiers over a finished extraction record. Errors mean the record is wrong; warnings mean a reviewer has to look. **Tier 1 — conformance (errors).** Types, required slots, closed vocabularies, the evidence @@ -1820,10 +1820,9 @@ python3 -m pondie.schema.checks.field_provenance --strict # every field says how python3 -m pondie.schema.checks.extraction_to_storage_map # the projection has not drifted python3 -m pondie.schema.generate --check # the committed extraction tree is current python3 -m pytest # everything the review layer stands on -python3 review/validate_record.py --record .extraction.json --text +python3 -m pondie.extraction.record.validate --record .extraction.json --text ``` **Worked records.** [representing-models.md §5](representing-models.md#5-worked-models) has twelve, -each with the sentences from the paper it came from and a referent record. Three complete extraction -records live under `review/examples/`, each with a corrections note saying what the extractor got -wrong and why. +each with the sentences from the paper it came from and a referent record. A complete extraction +record is `examples/run/records/x7Ex4mpleA01.extraction.json`. diff --git a/src/study_schema/linkml/neuroimaging-study-storage/analysis.yaml b/src/study_schema/linkml/neuroimaging-study-storage/analysis.yaml index b22e7f5..0a36961 100644 --- a/src/study_schema/linkml/neuroimaging-study-storage/analysis.yaml +++ b/src/study_schema/linkml/neuroimaging-study-storage/analysis.yaml @@ -284,7 +284,7 @@ classes: # negative rule "whole-brain, no ROI" and "the extractor missed the ROIs" are the same JSON. # # The projection drops `rules`, so these are stated here and evaluated against extraction - # records by `review/validate_record.py::check_rules`. + # records by `pondie.extraction.record.validate.Validator.check_rules`. rules: - description: Whole-brain and searchlight analyses restrict inference to no region. preconditions: @@ -325,7 +325,7 @@ classes: # `required: true` on `cells` catches an absent key and nothing else: LinkML has no # minimum cardinality here, so `cells: []` -- an effect that compared nothing -- passes # it. The projection drops `rules`, so this is stated here and evaluated against - # extraction records by `review/validate_record.py::check_rules`, the same route the two + # extraction records by `pondie.extraction.record.validate.Validator.check_rules`, the same route the two # rules on Analysis take. A postcondition with no preconditions holds of every Effect. rules: - description: An effect that compared nothing tested nothing; cells cannot be empty. diff --git a/storage-schema-design-notes.md b/storage-schema-design-notes.md index db1a6f4..c4e081d 100644 --- a/storage-schema-design-notes.md +++ b/storage-schema-design-notes.md @@ -77,11 +77,11 @@ The slot would then need a rule that it is populated only when no term of the mo reaches a `FactorLevel` naming an arm — the arm was fixed, not varied. That cannot be a LinkML `rules` block: `slot_conditions` are within-class, and this condition reaches through `terms` → `levels` → `arms`. It would be another `check_` in -`review/validate_record.py`, which is where every cross-class invariant already lives. +`pondie/extraction/record/rules.py`, which is where every cross-class invariant already lives. ### What carries it instead -`check_arm_reachability` in [review/validate_record.py](review/validate_record.py), built +`check_arm_reachability` in pondie's `pondie/extraction/record/rules.py`, built on `check_occasion_factors`' pattern: the structure is checked, and prose is the trigger. An analysis whose `name` or `definition` names an arm — matched against that record's own `Arm.name` and `Arm.agent`, so the vocabulary is the paper's rather than a fixed word list @@ -259,7 +259,7 @@ purpose has somewhere to live, and it contradicts a bad link where a reviewer ca ### What carries it -`check_group_instruments` in [review/validate_record.py](review/validate_record.py): every +`check_group_instruments` in pondie's `pondie/extraction/record/rules.py`: every entry must resolve to an `assessments[].local_id`. Referential integrity is mechanical in a way clinical relevance is not — no validator can know that the SCID-II is beside the point for heroin dependence, but any validator can know that an id resolves to an entry @@ -317,7 +317,7 @@ use. ### What carries it -`check_analysis_inference_settings` in [review/validate_record.py](review/validate_record.py): +`check_analysis_inference_settings` in pondie's `pondie/extraction/record/rules.py`: the id must resolve to an `inference_settings[].local_id`. A dangling id leaves an analysis with no threshold, no correction and no alpha, which reads as a result reported without inference — a different claim from one whose thresholding the paper never stated. That case @@ -354,7 +354,7 @@ same error as hoisting `InferenceSettings` onto `ModelEstimation`. Both are refe ### What carries it -`check_analysis_measures` in [review/validate_record.py](review/validate_record.py), through +`check_analysis_measures` in pondie's `pondie/extraction/record/rules.py`, through the shared `check_references_resolve`. A dangling id leaves a result whose measured quantity nothing states, which is not the same claim as a paper vague about what it measured — that is a declared `Measure` holding the source's wording in `source_label` and `not_reported` @@ -456,7 +456,7 @@ class is still the fix for it. exclusivity between them is dropped.** The rule used to be "populate exactly one, never both", and a structural validator was supposed to -reject both. Neither half held. Nothing ever checked it — `validate_record.py` has no such check — +reject both. Neither half held. Nothing ever checked it — `pondie.extraction.record.validate` has no such check — and `build_record.py`'s `derive_coordinate_spaces` fills `Analysis.coordinate_space` from the table parse whenever every table behind an analysis agrees, which is 214 of 308 analyses on 2026-08-14. The exclusivity was being violated by the pipeline that was meant to hold it, in the ordinary case.