From 9112f97b5b351e23328297c37bd1432adb1a8bc4 Mon Sep 17 00:00:00 2001 From: James Kent Date: Sat, 17 Oct 2026 23:22:24 -0500 Subject: [PATCH] Write each class's JSON Schema properties in the LinkML's declared order The generated JSON Schemas listed properties alphabetically: LinkML's serialize() and tools/generate_models.py both sorted keys. Consumers that show a class's fields in schema order (neurostore's review app) got age_sd far from age_mean. render() now takes the generator's dict and sorts every key except each object's properties, which keep the induced slot order. The regenerated schemas load equal to the old ones; only property order changes. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 + .../jsonschema/coordinate-parse.schema.json | 1114 ++--- .../jsonschema/extraction-record.schema.json | 4444 ++++++++--------- .../jsonschema/parsed-paper.schema.json | 1178 ++--- .../jsonschema/storage-study.schema.json | 3008 +++++------ tests/test_declared_order.py | 40 + tools/generate_models.py | 17 +- 7 files changed, 4929 insertions(+), 4874 deletions(-) create mode 100644 tests/test_declared_order.py diff --git a/README.md b/README.md index a1a9713..9e52237 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,8 @@ parse = CoordinateParse.model_validate_json(path.read_bytes()) **Unique keys.** A class with a LinkML `unique_keys` carries `"x-unique-keys": [[slot, ...], ...]` on its definition, its own keys first and then inherited ones with each key's slots in declared order, and `study_schema.jsonschema.unique_keys(name)` returns them as a dict from class to tuples of slot names. +**Property order.** Each object's `properties` follow the LinkML's declared slot order, inherited slots included, rather than alphabetical order. + Every model forbids fields its schema does not declare, so a file written against another version fails where it is read, naming the field, instead of losing what the reader does not recognise. Each model module records the schema version it was generated from as `version`, diff --git a/src/study_schema/jsonschema/coordinate-parse.schema.json b/src/study_schema/jsonschema/coordinate-parse.schema.json index 1302a2d..4272b41 100644 --- a/src/study_schema/jsonschema/coordinate-parse.schema.json +++ b/src/study_schema/jsonschema/coordinate-parse.schema.json @@ -27,22 +27,12 @@ "additionalProperties": false, "description": "pondie's verdict on one analysis of the parse a revision revises, reached while extracting the whole record. The verdict overrides the original's proposal, grouping and role alike.\nWhat a verdict does to what is stored against the analysis -- claims, their evidence, and users' votes on them:\n- accept, relabel: nothing moves. The key is unchanged, so everything stored against\n it applies to the revision as it stands.\n- edit: everything is copied to the one analysis in `replaced_by`, whose points are\n the corrected ones.\n- split: everything is copied to each analysis in `replaced_by`. - merge: everything stored against each merged analysis is copied to the one\n analysis they merge into. Two copies of the same field with the same value become\n one claim; with different values they stay competing claims, resolved like any\n other.\n- omit: nothing is copied. - add: nothing to copy; the analysis is new.\nEvery copy records the key and parse it was copied from, so a reviewer can tell a copied claim from one made against the revision. The revised parse and everything stored against it are kept, unchanged, as history: superseded rather than deleted, and left out of projections and studysets.", "properties": { - "evidence": { - "description": "The sentences of the paper the verdict rests on.", - "items": { - "$ref": "#/$defs/TextSpan" - }, - "type": [ - "array", - "null" - ] - }, "key": { "description": "The revised parse's analysis key; for `add`, the new analysis's key in this revision.", "type": "string" }, - "reason": { - "type": "string" + "verdict": { + "$ref": "#/$defs/AuditVerdict" }, "replaced_by": { "description": "The keys in this revision that take the analysis's place: itself for accept and relabel, the one corrected analysis for edit, the replacements for split and merge, none for omit.", @@ -68,8 +58,18 @@ ], "description": "For relabel, the role the points have instead." }, - "verdict": { - "$ref": "#/$defs/AuditVerdict" + "reason": { + "type": "string" + }, + "evidence": { + "description": "The sentences of the paper the verdict rests on.", + "items": { + "$ref": "#/$defs/TextSpan" + }, + "type": [ + "array", + "null" + ] } }, "required": [ @@ -95,8 +95,8 @@ "additionalProperties": false, "description": "Every identifier known for the paper when the artifact was written. Carried whole, so no consumer has to choose between them or look one up. `doi`, `pmid` and `pmcid` name the version of record; a preprint's identifiers are kept beside them in the `preprint_` fields, so a paper and its preprint are one article.", "properties": { - "doi": { - "description": "The DOI of the version of record. Never a preprint server's DOI, which is in `preprint_dois`.", + "pmid": { + "description": "PubMed's record of the version of record. Never PubMed's record of a preprint, which is `preprint_pmid`.", "type": [ "string", "null" @@ -109,8 +109,8 @@ "null" ] }, - "pmid": { - "description": "PubMed's record of the version of record. Never PubMed's record of a preprint, which is `preprint_pmid`.", + "doi": { + "description": "The DOI of the version of record. Never a preprint server's DOI, which is in `preprint_dois`.", "type": [ "string", "null" @@ -126,15 +126,15 @@ "null" ] }, - "preprint_pmcid": { - "description": "PubMed Central's copy of the preprint.", + "preprint_pmid": { + "description": "PubMed's record of the preprint, which PubMed types `Preprint`.", "type": [ "string", "null" ] }, - "preprint_pmid": { - "description": "PubMed's record of the preprint, which PubMed types `Preprint`.", + "preprint_pmcid": { + "description": "PubMed Central's copy of the preprint.", "type": [ "string", "null" @@ -155,23 +155,23 @@ "additionalProperties": false, "description": "Who made an artifact, from what, for which paper.", "properties": { - "article_id": { - "description": "The ingestion catalog's key for the paper. Minted once, at download, and never recomputed from the identifiers below.", - "type": "string" - }, "artifact_kind": { "$ref": "#/$defs/ArtifactKind" }, - "created_at": { - "format": "date-time", - "type": [ - "string", - "null" - ] + "schema_version": { + "description": "The version of this schema the artifact validates against.", + "type": "string" + }, + "article_id": { + "description": "The ingestion catalog's key for the paper. Minted once, at download, and never recomputed from the identifiers below.", + "type": "string" }, "identifiers": { "$ref": "#/$defs/ArticleIdentifiers" }, + "producer": { + "$ref": "#/$defs/Producer" + }, "inputs": { "description": "Every artifact this one was made from, by fingerprint.", "items": { @@ -182,19 +182,19 @@ "null" ] }, + "created_at": { + "format": "date-time", + "type": [ + "string", + "null" + ] + }, "neurostore_base_study_id": { "description": "Set once the paper has been uploaded; neurostore's BaseStudy id. An upload pointer, not a bibliographic identifier, so it sits here and not in `identifiers`.", "type": [ "string", "null" ] - }, - "producer": { - "$ref": "#/$defs/Producer" - }, - "schema_version": { - "description": "The version of this schema the artifact validates against.", - "type": "string" } }, "required": [ @@ -261,24 +261,8 @@ ] }, "properties": { - "abstract": { - "description": "Absent (null) when no source gave one; never an empty string.", - "pattern": "\\S", - "type": [ - "string", - "null" - ] - }, - "abstract_provider": { - "anyOf": [ - { - "$ref": "#/$defs/MetadataSource" - }, - { - "type": "null" - } - ], - "description": "The source whose abstract `abstract` is. It may differ from `provider`. The abstract is the publisher's abstract of the version `license` describes, so its licence is `license`." + "title": { + "type": "string" }, "authors": { "description": "Author names in published order.", @@ -290,68 +274,47 @@ "null" ] }, - "corrections": { - "description": "Notices attached to the paper -- errata, retractions, expressions of concern -- as the indexer lists them. A retracted paper is still parsed and stored, marked by its `retraction` notice, and left out of studysets by default; a filter can ask for it explicitly.", - "items": { - "$ref": "#/$defs/Correction" - }, + "journal": { "type": [ - "array", + "string", "null" ] }, - "issue": { + "publication_year": { + "description": "Year in which the study was first published or made publicly available.", + "minimum": 1600, "type": [ - "string", + "integer", "null" ] }, - "journal": { + "volume": { "type": [ "string", "null" ] }, - "keywords": { - "items": { - "type": "string" - }, + "issue": { "type": [ - "array", + "string", "null" ] }, - "language": { - "description": "Languages of the article, as the E-utilities API reports them -- the three-letter codes PubMed assigns, `eng` and the rest, and not read off the paper: the text a reader is given is in one language whatever the article is indexed as.\nAt least six of the benchmark's sixteen meta-analyses state \"English language\" as an inclusion criterion. On the 1,804 records measured it excludes exactly one paper, so it buys precision rather than recall; the criterion is stated, so the field states it.", - "items": { - "type": "string" - }, + "pages": { "type": [ - "array", + "string", "null" ] }, - "license": { - "$ref": "#/$defs/License", - "description": "The paper's licence: the version of record's, or the latest preprint's while the header's `publication_status` is `preprint`. Not the licence of a copy of another version, which that copy carries." - }, - "oa_status": { - "anyOf": [ - { - "$ref": "#/$defs/OaStatus" - }, - { - "type": "null" - } - ] - }, - "pages": { + "abstract": { + "description": "Absent (null) when no source gave one; never an empty string.", + "pattern": "\\S", "type": [ "string", "null" ] }, - "provider": { + "abstract_provider": { "anyOf": [ { "$ref": "#/$defs/MetadataSource" @@ -360,7 +323,17 @@ "type": "null" } ], - "description": "The metadata service the facts came from." + "description": "The source whose abstract `abstract` is. It may differ from `provider`. The abstract is the publisher's abstract of the version `license` describes, so its licence is `license`." + }, + "language": { + "description": "Languages of the article, as the E-utilities API reports them -- the three-letter codes PubMed assigns, `eng` and the rest, and not read off the paper: the text a reader is given is in one language whatever the article is indexed as.\nAt least six of the benchmark's sixteen meta-analyses state \"English language\" as an inclusion criterion. On the 1,804 records measured it excludes exactly one paper, so it buys precision rather than recall; the criterion is stated, so the field states it.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] }, "publication_types": { "description": "PubMed publication types for this record, verbatim as the E-utilities API returns them (https://pubmed.ncbi.nlm.nih.gov/help/#publication-types). Multivalued because PubMed assigns several: every article is `Journal Article` and the design of the study motivates other labels. `StudyType` lists the known types only; any other type PubMed returns is kept verbatim.", @@ -379,22 +352,49 @@ "null" ] }, - "publication_year": { - "description": "Year in which the study was first published or made publicly available.", - "minimum": 1600, + "keywords": { + "items": { + "type": "string" + }, "type": [ - "integer", + "array", "null" ] }, - "title": { - "type": "string" + "license": { + "$ref": "#/$defs/License", + "description": "The paper's licence: the version of record's, or the latest preprint's while the header's `publication_status` is `preprint`. Not the licence of a copy of another version, which that copy carries." }, - "volume": { + "oa_status": { + "anyOf": [ + { + "$ref": "#/$defs/OaStatus" + }, + { + "type": "null" + } + ] + }, + "corrections": { + "description": "Notices attached to the paper -- errata, retractions, expressions of concern -- as the indexer lists them. A retracted paper is still parsed and stored, marked by its `retraction` notice, and left out of studysets by default; a filter can ask for it explicitly.", + "items": { + "$ref": "#/$defs/Correction" + }, "type": [ - "string", + "array", "null" ] + }, + "provider": { + "anyOf": [ + { + "$ref": "#/$defs/MetadataSource" + }, + { + "type": "null" + } + ], + "description": "The metadata service the facts came from." } }, "required": [ @@ -422,13 +422,13 @@ "additionalProperties": false, "description": "One table cell an analysis was read from, by row and column group.", "properties": { - "column_group": { - "description": "As on ParsedPoint; 0 in a table with one block of coordinate columns.", + "row": { + "description": "The TableRow's `row`.", "minimum": 0, "type": "integer" }, - "row": { - "description": "The TableRow's `row`.", + "column_group": { + "description": "As on ParsedPoint; 0 in a table with one block of coordinate columns.", "minimum": 0, "type": "integer" } @@ -444,18 +444,9 @@ "additionalProperties": false, "description": "One in-text citation and the reference entries it names. A grouped citation (\"[3-5]\", \"(Smith, 2001; Jones, 2003)\") is one Citation with several references.", "properties": { - "confidence": { - "description": "For a heuristic alignment, how sure; absent for an exact one.", - "maximum": 1, - "minimum": 0, - "type": [ - "number", - "null" - ] - }, - "method": { - "$ref": "#/$defs/CitationAlignment", - "description": "How the marker was tied to its entries -- what its reliability rests on." + "text_span": { + "$ref": "#/$defs/TextSpan", + "description": "The citation marker in the text." }, "references": { "description": "The entries it names, by `Reference.id`. Empty when it could not be aligned.", @@ -468,9 +459,18 @@ ], "x-reference": "Reference" }, - "text_span": { - "$ref": "#/$defs/TextSpan", - "description": "The citation marker in the text." + "method": { + "$ref": "#/$defs/CitationAlignment", + "description": "How the marker was tied to its entries -- what its reliability rests on." + }, + "confidence": { + "description": "For a heuristic alignment, how sure; absent for an exact one.", + "maximum": 1, + "minimum": 0, + "type": [ + "number", + "null" + ] } }, "required": [ @@ -541,31 +541,18 @@ "additionalProperties": false, "description": "Every coordinate a paper reports, grouped into analyses. An original parse is written by the ingestion workflow; a revision is written by pondie when its audit disagrees with the original.\nA parse is never edited after it is written. A reader that disagrees with it writes a revision instead, and the revision is a complete parse on its own: neurostore can store it as a new version of the paper's analyses without consulting the parse it revises.\nAnalyses that pondie accepts or relabels keep their keys, because their name, space and points are unchanged, so claims made against them carry over to the revision untouched. Only a split, a merge or an omission changes which keys exist.", "properties": { - "analyses": { - "description": "May be empty: the parse ran and found no analysis. A paper the parse never ran on has no CoordinateParse at all, so the two are never confused, and `tables` says what reading each table found.", - "items": { - "$ref": "#/$defs/ParsedAnalysis" - }, - "type": "array" - }, "header": { "$ref": "#/$defs/ArtifactHeader", "description": "`inputs` names the parsed paper by `text_sha256`, and for a revision also the parse it revises by `parse_id`." }, - "omitted_analyses": { - "description": "Text or figure analyses the producer read and left out of `analyses`, each with the reason. A table's omissions are in `tables`. Absent when nothing was left out.", - "items": { - "$ref": "#/$defs/OmittedAnalysis" - }, - "type": [ - "array", - "null" - ] - }, "parse_id": { "description": "This parse's identity, the fingerprint every later artifact cites. Unique per paper; a sha256 of the canonical JSON of `analyses` is recommended, so two producers that reach the same analyses reach the same id.", "type": "string" }, + "text_sha256": { + "description": "The parsed paper's fingerprint. Every point's cells and spans address it.", + "type": "string" + }, "revision_of": { "description": "The `parse_id` of the parse this one revises. Absent on an original parse, which is how a reader tells the two apart.", "type": [ @@ -573,6 +560,13 @@ "null" ] }, + "analyses": { + "description": "May be empty: the parse ran and found no analysis. A paper the parse never ran on has no CoordinateParse at all, so the two are never confused, and `tables` says what reading each table found.", + "items": { + "$ref": "#/$defs/ParsedAnalysis" + }, + "type": "array" + }, "tables": { "description": "One entry per table of the parsed paper: what reading it for coordinates found, or why it was not read. A table with no entry was not considered.", "items": { @@ -583,9 +577,15 @@ "null" ] }, - "text_sha256": { - "description": "The parsed paper's fingerprint. Every point's cells and spans address it.", - "type": "string" + "omitted_analyses": { + "description": "Text or figure analyses the producer read and left out of `analyses`, each with the reason. A table's omissions are in `tables`. Absent when nothing was left out.", + "items": { + "$ref": "#/$defs/OmittedAnalysis" + }, + "type": [ + "array", + "null" + ] }, "text_sweep": { "anyOf": [ @@ -657,22 +657,23 @@ ] }, "properties": { - "license": { - "$ref": "#/$defs/License", - "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." + "route": { + "$ref": "#/$defs/CopyRoute" }, - "preprint_server": { + "version": { "anyOf": [ { - "$ref": "#/$defs/PreprintServer" + "$ref": "#/$defs/CopyVersion" }, { "type": "null" } - ] + ], + "description": "Absent when no source says which version the copy is." }, - "route": { - "$ref": "#/$defs/CopyRoute" + "license": { + "$ref": "#/$defs/License", + "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." }, "url": { "type": [ @@ -680,16 +681,15 @@ "null" ] }, - "version": { + "preprint_server": { "anyOf": [ { - "$ref": "#/$defs/CopyVersion" + "$ref": "#/$defs/PreprintServer" }, { "type": "null" } - ], - "description": "Absent when no source says which version the copy is." + ] } }, "required": [ @@ -749,12 +749,6 @@ "additionalProperties": false, "description": "One notice attached to a paper.", "properties": { - "doi": { - "type": [ - "string", - "null" - ] - }, "kind": { "anyOf": [ { @@ -770,6 +764,12 @@ "string", "null" ] + }, + "doi": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -921,22 +921,23 @@ "access": { "$ref": "#/$defs/CopyAccess" }, - "license": { - "$ref": "#/$defs/License", - "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." + "route": { + "$ref": "#/$defs/CopyRoute" }, - "preprint_server": { + "version": { "anyOf": [ { - "$ref": "#/$defs/PreprintServer" + "$ref": "#/$defs/CopyVersion" }, { "type": "null" } - ] + ], + "description": "Absent when no source says which version the copy is." }, - "route": { - "$ref": "#/$defs/CopyRoute" + "license": { + "$ref": "#/$defs/License", + "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." }, "url": { "type": [ @@ -944,16 +945,15 @@ "null" ] }, - "version": { + "preprint_server": { "anyOf": [ { - "$ref": "#/$defs/CopyVersion" + "$ref": "#/$defs/PreprintServer" }, { "type": "null" } - ], - "description": "Absent when no source says which version the copy is." + ] } }, "required": [ @@ -1022,20 +1022,20 @@ "license_id": { "const": "all-rights-reserved" }, - "stated_as": { + "stated_by": { "not": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/MetadataSource" } ] } }, - "stated_by": { + "stated_as": { "not": { "anyOf": [ { - "$ref": "#/$defs/MetadataSource" + "type": "string" } ] } @@ -1053,20 +1053,12 @@ ] }, "properties": { - "determination": { - "$ref": "#/$defs/LicenseDetermination" - }, "license_id": { "$ref": "#/$defs/LicenseId", "description": "Which licence, as OpenAlex's licence slugs name them (`cc-by`, `cc-by-nc`, ...). Not an SPDX License Identifier: sources rarely state the licence's version, and SPDX identifiers require one." }, - "stated_as": { - "description": "The statement exactly as the source gave it: a licence URL, a licence name, or a copyright line. What `other` holds, and what attribution quotes. Never empty: a source that gave no statement did not state a licence.", - "pattern": "\\S", - "type": [ - "string", - "null" - ] + "determination": { + "$ref": "#/$defs/LicenseDetermination" }, "stated_by": { "anyOf": [ @@ -1078,6 +1070,14 @@ } ], "description": "The source whose statement this is." + }, + "stated_as": { + "description": "The statement exactly as the source gave it: a licence URL, a licence name, or a copyright line. What `other` holds, and what attribution quotes. Never empty: a source that gave no statement did not state a licence.", + "pattern": "\\S", + "type": [ + "string", + "null" + ] } }, "required": [ @@ -1086,17 +1086,17 @@ ], "then": { "properties": { - "stated_as": { + "stated_by": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/MetadataSource" } ] }, - "stated_by": { + "stated_as": { "anyOf": [ { - "$ref": "#/$defs/MetadataSource" + "type": "string" } ] } @@ -1264,10 +1264,6 @@ "description": "The analysis's label as the producer read it.", "type": "string" }, - "reason": { - "description": "Why it was left out, for example that it repeats a kept analysis (the same key) or that its passage is not in the text.", - "type": "string" - }, "text_spans": { "description": "The sentences it was read from, when it was located.", "items": { @@ -1277,6 +1273,10 @@ "array", "null" ] + }, + "reason": { + "description": "Why it was left out, for example that it repeats a kept analysis (the same key) or that its passage is not in the text.", + "type": "string" } }, "required": [ @@ -1290,19 +1290,9 @@ "additionalProperties": false, "description": "The points a paper reports together for one tested effect, or for one non-result purpose such as the regions of interest an analysis used.\nIn an original parse, the grouping is the ingestion workflow's proposal, made from a table and its caption. pondie confirms or overrides it with the whole paper in view. The role is required and is set in the ingestion workflow by the `roles` classifier stage, after the analyses are identified. It is also the ingestion workflow's proposal: pondie's accept confirms it, and a relabel overrides it with a reviewed correction that wins.", "properties": { - "anchor_kind": { - "anyOf": [ - { - "$ref": "#/$defs/AnchorKind" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "For an `anchor`, what it defines." + "key": { + "description": "The analysis's identity, derived from what was extracted -- its `name`, `coordinate_space` and `points` -- never from where it was read, so the same analysis read from two sources (a table and its HTML copy, a table and the sentence restating it) reaches the same key, and a re-run that extracts the same content reaches it again. A rename or a changed point is a new key. The name is part of the key because differently named analyses can print identical points.\n`analysis#`, where `h` is the first 16 hex characters of the sha256 of the UTF-8 bytes of the compact JSON array `[, , [, ...]]` (no whitespace, non-ASCII characters unescaped). `` is `name` normalized: Unicode NFKC, casefolded, every dash (Unicode Pd and U+2212 minus) as `-`, zero-width characters and all whitespace dropped, so `PO > Sil` and `PO>Sil` are one name. `` is `coordinate_space`, or `\"\"` when absent.\nEach `` is `[, , , , [[, ], ...]]`: the coordinates rounded to 0.01 mm and written with two decimals (`\"-4.00\"`); the point's own `space` when it differs from the analysis's, else `\"\"`; and each of its `values` as its `kind` and its value written to 6 significant digits (`\"5.8\"`, `\"3.2e-12\"`). Negative zero is written as zero. Each point's values, and then the points, are sorted by their own compact JSON, in code point order.\nAn analysis with no points -- a contrast a table lists as \"n.s.\" -- hashes its name and space alone. The two halves of a sign split hold different points under different names, so each has its own key. `study_schema.keys.analysis_key` computes it.\nThis key is what the extraction record's `Analysis.source_table_analysis` and `CoordinateSet.id` name, and what neurostore stores as `Analysis.source_id`. Two analyses of one parse never share a name, space and points, so never share a key.", + "type": "string" }, "cells": { "description": "For a table analysis, every cell it was read from: each point's row and column group, and the rows that name the contrast without coordinates. Required for `origin: table`; every point's row and column group is among them.", @@ -1314,44 +1304,26 @@ "null" ] }, - "coordinate_space": { - "anyOf": [ - { - "$ref": "#/$defs/ReportedSpace" - }, - { - "type": "null" - } - ], - "description": "The space of the analysis's points; absent when the paper states none. A point states its own only when it differs." - }, - "derived_from": { - "description": "On a revision, the keys of the revised parse's analyses this one was made from by a split or a merge. Absent on an analysis carried over unchanged.", + "text_spans": { + "description": "For a text or figure analysis, the sentences or legend it was read from: those holding its coordinates, or the one stating a result that has none.", "items": { - "type": "string" + "$ref": "#/$defs/TextSpan" }, "type": [ "array", "null" ] }, - "description": { - "type": [ - "string", - "null" - ] + "origin": { + "$ref": "#/$defs/CoordinateOrigin" }, - "from_prior_study": { - "description": "The coordinates come from another publication rather than from this study: a peak quoted for comparison (always true for `role: reference`), another study's peak taken as this study's ROI or seed, or a peak from the authors' own earlier publication given with its statistics, which is `role: result` with this flag true so that a meta-analysis can avoid counting the earlier study twice. Separate from the role, because a borrowed seed is still the seed this study's analysis used. False when the study defined them itself, including an ROI built from its own earlier result, meaning an earlier analysis in this same paper such as a localizer.", + "table_id": { + "description": "The ParsedTable the points were read from. Present exactly when `origin` is `table`.", "type": [ - "boolean", + "string", "null" ] }, - "key": { - "description": "The analysis's identity, derived from what was extracted -- its `name`, `coordinate_space` and `points` -- never from where it was read, so the same analysis read from two sources (a table and its HTML copy, a table and the sentence restating it) reaches the same key, and a re-run that extracts the same content reaches it again. A rename or a changed point is a new key. The name is part of the key because differently named analyses can print identical points.\n`analysis#`, where `h` is the first 16 hex characters of the sha256 of the UTF-8 bytes of the compact JSON array `[, , [, ...]]` (no whitespace, non-ASCII characters unescaped). `` is `name` normalized: Unicode NFKC, casefolded, every dash (Unicode Pd and U+2212 minus) as `-`, zero-width characters and all whitespace dropped, so `PO > Sil` and `PO>Sil` are one name. `` is `coordinate_space`, or `\"\"` when absent.\nEach `` is `[, , , , [[, ], ...]]`: the coordinates rounded to 0.01 mm and written with two decimals (`\"-4.00\"`); the point's own `space` when it differs from the analysis's, else `\"\"`; and each of its `values` as its `kind` and its value written to 6 significant digits (`\"5.8\"`, `\"3.2e-12\"`). Negative zero is written as zero. Each point's values, and then the points, are sorted by their own compact JSON, in code point order.\nAn analysis with no points -- a contrast a table lists as \"n.s.\" -- hashes its name and space alone. The two halves of a sign split hold different points under different names, so each has its own key. `study_schema.keys.analysis_key` computes it.\nThis key is what the extraction record's `Analysis.source_table_analysis` and `CoordinateSet.id` name, and what neurostore stores as `Analysis.source_id`. Two analyses of one parse never share a name, space and points, so never share a key.", - "type": "string" - }, "name": { "description": "The analysis's label, as printed where the paper prints one.", "type": "string" @@ -1363,55 +1335,22 @@ "null" ] }, - "origin": { - "$ref": "#/$defs/CoordinateOrigin" - }, - "points": { - "description": "Empty when the paper names the analysis but reports no coordinates for it -- most often because nothing survived the threshold. Whether it found an effect is the extraction record's `Analysis.outcome`; an empty list here only says that no coordinates were printed.", - "items": { - "$ref": "#/$defs/ParsedPoint" - }, - "type": [ - "array", - "null" - ] - }, - "prior_study_evidence": { - "description": "The citation or sentence showing the coordinates come from another publication.", - "items": { - "$ref": "#/$defs/TextSpan" - }, + "description": { "type": [ - "array", + "string", "null" ] }, - "role": { + "coordinate_space": { "anyOf": [ { - "$ref": "#/$defs/CoordinateRole" + "$ref": "#/$defs/ReportedSpace" }, { - "type": "string" + "type": "null" } ], - "description": "What the points are for. Only a `result` is an analysis with a statistic in the extraction record; an `anchor` -- a region of interest, a seed, a stimulation target -- becomes a CoordinateSet there, and never enters a studyset as an analysis.\nRequired. Set in the ingestion workflow by the `roles` classifier stage, after the analyses are identified: a classifier that reads each set and its context, named by `role_source`. It is the ingestion workflow's proposal, which pondie audits: accept confirms it, and relabel overrides it with a reviewed correction that wins. The extractors never propose a role, and nothing supplies a default.\nNumbers that are not brain coordinates, such as channel numbers or lattice points, get no role. They are not a coordinate set." - }, - "role_confidence": { - "description": "The classifier's probability for `role` (and `anchor_kind`).", - "maximum": 1, - "minimum": 0, - "type": [ - "number", - "null" - ] - }, - "role_source": { - "description": "The classifier that set the role, by name and version, such as `set-role-encoder@2026-10-09`.", - "type": [ - "string", - "null" - ] + "description": "The space of the analysis's points; absent when the paper states none. A point states its own only when it differs." }, "space_as_printed": { "type": [ @@ -1429,21 +1368,21 @@ } ] }, - "split": { + "role": { "anyOf": [ { - "$ref": "#/$defs/SignSplit" + "$ref": "#/$defs/CoordinateRole" }, { - "type": "null" + "type": "string" } ], - "description": "Present on both halves of an analysis split by sign, and only there. The split is declared here and nowhere else: a reader never infers it from a name." + "description": "What the points are for. Only a `result` is an analysis with a statistic in the extraction record; an `anchor` -- a region of interest, a seed, a stimulation target -- becomes a CoordinateSet there, and never enters a studyset as an analysis.\nRequired. Set in the ingestion workflow by the `roles` classifier stage, after the analyses are identified: a classifier that reads each set and its context, named by `role_source`. It is the ingestion workflow's proposal, which pondie audits: accept confirms it, and relabel overrides it with a reviewed correction that wins. The extractors never propose a role, and nothing supplies a default.\nNumbers that are not brain coordinates, such as channel numbers or lattice points, get no role. They are not a coordinate set." }, - "statistic": { + "anchor_kind": { "anyOf": [ { - "$ref": "#/$defs/StatisticKind" + "$ref": "#/$defs/AnchorKind" }, { "type": "string" @@ -1452,24 +1391,17 @@ "type": "null" } ], - "description": "The test statistic of this analysis's one test -- t, z, F, chi_square -- whatever the columns call it. One analysis is one test. Every value at a point that describes the same test belongs to that point: the statistic, its p value (uncorrected and corrected), an effect size, the cluster's p. A different test is a different analysis, even on the same rows: an omnibus F and the directional post-hoc t that follows it are two analyses, told apart by `column_group` when one table prints both." - }, - "statistic_df": { - "description": "Degrees of freedom as printed, such as \"1, 38\" or \"27\".", - "type": [ - "string", - "null" - ] + "description": "For an `anchor`, what it defines." }, - "table_id": { - "description": "The ParsedTable the points were read from. Present exactly when `origin` is `table`.", + "from_prior_study": { + "description": "The coordinates come from another publication rather than from this study: a peak quoted for comparison (always true for `role: reference`), another study's peak taken as this study's ROI or seed, or a peak from the authors' own earlier publication given with its statistics, which is `role: result` with this flag true so that a meta-analysis can avoid counting the earlier study twice. Separate from the role, because a borrowed seed is still the seed this study's analysis used. False when the study defined them itself, including an ROI built from its own earlier result, meaning an earlier analysis in this same paper such as a localizer.", "type": [ - "string", + "boolean", "null" ] }, - "text_spans": { - "description": "For a text or figure analysis, the sentences or legend it was read from: those holding its coordinates, or the one stating a result that has none.", + "prior_study_evidence": { + "description": "The citation or sentence showing the coordinates come from another publication.", "items": { "$ref": "#/$defs/TextSpan" }, @@ -1478,6 +1410,64 @@ "null" ] }, + "role_confidence": { + "description": "The classifier's probability for `role` (and `anchor_kind`).", + "maximum": 1, + "minimum": 0, + "type": [ + "number", + "null" + ] + }, + "role_source": { + "description": "The classifier that set the role, by name and version, such as `set-role-encoder@2026-10-09`.", + "type": [ + "string", + "null" + ] + }, + "split": { + "anyOf": [ + { + "$ref": "#/$defs/SignSplit" + }, + { + "type": "null" + } + ], + "description": "Present on both halves of an analysis split by sign, and only there. The split is declared here and nowhere else: a reader never infers it from a name." + }, + "derived_from": { + "description": "On a revision, the keys of the revised parse's analyses this one was made from by a split or a merge. Absent on an analysis carried over unchanged.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "statistic": { + "anyOf": [ + { + "$ref": "#/$defs/StatisticKind" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The test statistic of this analysis's one test -- t, z, F, chi_square -- whatever the columns call it. One analysis is one test. Every value at a point that describes the same test belongs to that point: the statistic, its p value (uncorrected and corrected), an effect size, the cluster's p. A different test is a different analysis, even on the same rows: an omnibus F and the directional post-hoc t that follows it are two analyses, told apart by `column_group` when one table prints both." + }, + "statistic_df": { + "description": "Degrees of freedom as printed, such as \"1, 38\" or \"27\".", + "type": [ + "string", + "null" + ] + }, "thresholds": { "description": "The thresholds the analysis's results survived, at every level the paper states: most often a voxel-level height (\"p < .001 uncorrected\") together with a cluster-level correction (\"FWE p < .05\"), which one method cannot express. Read from the caption, footer or Methods; each threshold cites where.", "items": { @@ -1487,6 +1477,16 @@ "array", "null" ] + }, + "points": { + "description": "Empty when the paper names the analysis but reports no coordinates for it -- most often because nothing survived the threshold. Whether it found an effect is the extraction record's `Analysis.outcome`; an empty list here only says that no coordinates were printed.", + "items": { + "$ref": "#/$defs/ParsedPoint" + }, + "type": [ + "array", + "null" + ] } }, "required": [ @@ -1502,46 +1502,30 @@ "additionalProperties": false, "description": "One paper as text, tables and bibliographic facts. The same shape for every download source.\nThe text is the only offset space. Every evidence span an extraction record cites, every table row's position and every text-read point addresses `text_sha256`'s characters, so a span can be checked by slicing the text, and a span made against a different text is detectably wrong.", "properties": { - "bibliography": { - "$ref": "#/$defs/Bibliography" - }, - "citations": { - "description": "Every in-text citation, where it sits in the text and which reference it points to. The link from a sentence to a work, which the reference list alone cannot give: what resolves the citations a task's description makes (storage `Task.reference_ids`) and traces a task to the paper that introduced it.", - "items": { - "$ref": "#/$defs/Citation" - }, - "type": [ - "array", - "null" - ] - }, "header": { "$ref": "#/$defs/ArtifactHeader" }, - "is_meta_analysis": { - "description": "Whether the paper is itself a meta-analysis or review, decided here once. Every consumer reads this field; none applies its own test.", - "type": "boolean" - }, - "is_meta_analysis_basis": { + "source": { "anyOf": [ { - "$ref": "#/$defs/MetaAnalysisBasis" + "$ref": "#/$defs/DocumentSource" }, { - "type": "null" + "type": "string" } ] }, - "references": { - "description": "The paper's reference list, in order. Usually fetched from an API by the paper's identifiers rather than parsed from the text (`Reference.provider` says which), so it is complete and carries DOIs even when the text's list is badly extracted.", - "items": { - "$ref": "#/$defs/Reference" - }, + "source_reason": { + "description": "Why this source's text was chosen over the others downloaded, such as \"most tables\" or \"first in download order\". Every stage reads this one text, so the choice is made once and recorded.", "type": [ - "array", + "string", "null" ] }, + "text_copy": { + "$ref": "#/$defs/HeldCopy", + "description": "The copy the text was read from. Its licence and access, not the paper's licence, decide whether the text may be passed on." + }, "reported_copies": { "description": "Copies of the paper that a source lists and that we do not hold, each with its own licence. Where an open copy is, when `bibliography.oa_status` says one exists.", "items": { @@ -1552,29 +1536,44 @@ "null" ] }, - "sections": { - "items": { - "$ref": "#/$defs/Section" - }, + "text_path": { + "description": "The text file's path, relative to the paper's bundle directory.", + "type": "string" + }, + "text_sha256": { + "description": "sha256 of the text file's UTF-8 bytes. This artifact's fingerprint.", + "type": "string" + }, + "text_length": { + "minimum": 0, "type": [ - "array", + "integer", "null" ] }, - "source": { + "bibliography": { + "$ref": "#/$defs/Bibliography" + }, + "is_meta_analysis": { + "description": "Whether the paper is itself a meta-analysis or review, decided here once. Every consumer reads this field; none applies its own test.", + "type": "boolean" + }, + "is_meta_analysis_basis": { "anyOf": [ { - "$ref": "#/$defs/DocumentSource" + "$ref": "#/$defs/MetaAnalysisBasis" }, { - "type": "string" + "type": "null" } ] }, - "source_reason": { - "description": "Why this source's text was chosen over the others downloaded, such as \"most tables\" or \"first in download order\". Every stage reads this one text, so the choice is made once and recorded.", + "sections": { + "items": { + "$ref": "#/$defs/Section" + }, "type": [ - "string", + "array", "null" ] }, @@ -1588,33 +1587,34 @@ "null" ] }, - "tables": { + "references": { + "description": "The paper's reference list, in order. Usually fetched from an API by the paper's identifiers rather than parsed from the text (`Reference.provider` says which), so it is complete and carries DOIs even when the text's list is badly extracted.", "items": { - "$ref": "#/$defs/ParsedTable" + "$ref": "#/$defs/Reference" }, "type": [ "array", "null" ] }, - "text_copy": { - "$ref": "#/$defs/HeldCopy", - "description": "The copy the text was read from. Its licence and access, not the paper's licence, decide whether the text may be passed on." - }, - "text_length": { - "minimum": 0, + "citations": { + "description": "Every in-text citation, where it sits in the text and which reference it points to. The link from a sentence to a work, which the reference list alone cannot give: what resolves the citations a task's description makes (storage `Task.reference_ids`) and traces a task to the paper that introduced it.", + "items": { + "$ref": "#/$defs/Citation" + }, "type": [ - "integer", + "array", "null" ] }, - "text_path": { - "description": "The text file's path, relative to the paper's bundle directory.", - "type": "string" - }, - "text_sha256": { - "description": "sha256 of the text file's UTF-8 bytes. This artifact's fingerprint.", - "type": "string" + "tables": { + "items": { + "$ref": "#/$defs/ParsedTable" + }, + "type": [ + "array", + "null" + ] } }, "required": [ @@ -1633,20 +1633,31 @@ "additionalProperties": false, "description": "One reported coordinate. `coordinates`, `space` and `values` are NIMADS's point fields with NIMADS's meaning, so the projection to a studyset copies them; everything else is the parse's own.", "properties": { - "cluster_measure": { + "coordinates": { + "description": "x, y, z in millimetres.", + "items": { + "type": "number" + }, + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "space": { "anyOf": [ { - "$ref": "#/$defs/ClusterMeasure" + "$ref": "#/$defs/ReportedSpace" }, { "type": "null" } - ] + ], + "description": "Only when it differs from the analysis's `coordinate_space`." }, - "cluster_size": { + "row": { + "description": "For a table point, the TableRow it was read from.", "minimum": 0, "type": [ - "number", + "integer", "null" ] }, @@ -1658,65 +1669,54 @@ "null" ] }, - "coordinates": { - "description": "x, y, z in millimetres.", + "text_span": { + "anyOf": [ + { + "$ref": "#/$defs/TextSpan" + }, + { + "type": "null" + } + ], + "description": "For a text point, the characters it was read from." + }, + "values": { + "description": "The values at the point. They are the only record of its sign (StatisticKind gives the rule): a negative value makes it negative, and a point with only p, F or chi-square values, or none, is unsigned and takes the side of its analysis's `split` half. On the inverse half the directional statistic values are negated so they read positive; p, F and chi-square stay as printed. `split` declares the inverse half, and a consumer records the inverse on the contrast's conditions (negated weights), not per point. There is no point-level deactivation flag, and the parse never writes neurostore's Point.deactivation.", "items": { - "type": "number" + "$ref": "#/$defs/PointValue" }, - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - "is_subpeak": { "type": [ - "boolean", - "null" - ] - }, - "label": { - "description": "The region name printed beside the point.", - "type": [ - "string", + "array", "null" ] }, - "row": { - "description": "For a table point, the TableRow it was read from.", + "cluster_size": { "minimum": 0, "type": [ - "integer", + "number", "null" ] }, - "space": { + "cluster_measure": { "anyOf": [ { - "$ref": "#/$defs/ReportedSpace" + "$ref": "#/$defs/ClusterMeasure" }, { "type": "null" } - ], - "description": "Only when it differs from the analysis's `coordinate_space`." + ] }, - "text_span": { - "anyOf": [ - { - "$ref": "#/$defs/TextSpan" - }, - { - "type": "null" - } - ], - "description": "For a text point, the characters it was read from." + "is_subpeak": { + "type": [ + "boolean", + "null" + ] }, - "values": { - "description": "The values at the point. They are the only record of its sign (StatisticKind gives the rule): a negative value makes it negative, and a point with only p, F or chi-square values, or none, is unsigned and takes the side of its analysis's `split` half. On the inverse half the directional statistic values are negated so they read positive; p, F and chi-square stay as printed. `split` declares the inverse half, and a consumer records the inverse on the contrast's conditions (negated weights), not per point. There is no point-level deactivation flag, and the parse never writes neurostore's Point.deactivation.", - "items": { - "$ref": "#/$defs/PointValue" - }, + "label": { + "description": "The region name printed beside the point.", "type": [ - "array", + "string", "null" ] } @@ -1731,55 +1731,48 @@ "additionalProperties": false, "description": "A table of the paper, cell by cell.", "properties": { - "caption": { + "table_id": { + "description": "The table's key, as the source's parser names it. Stable for the life of the parsed paper and used unchanged by every consumer: the coordinate parse's `table_id`s, the record's Table.id, neurostore's Table.t_id. Never renumbered from the printed label and never sanitized differently on disk.", + "type": "string" + }, + "label": { + "description": "The printed label, such as Table 2 or Supplementary Table S4.", "type": [ "string", "null" ] }, - "column_headings": { - "items": { - "type": "string" - }, + "number": { "type": [ - "array", + "string", "null" ] }, - "coordinate_space_hint": { - "anyOf": [ - { - "$ref": "#/$defs/ReportedSpace" - }, - { - "type": "null" - } - ], - "description": "The source parser's own guess at the table's space. A hint for the coordinate stages, not an answer: the space a point is stored in is the coordinate parse's." - }, - "footer": { + "caption": { "type": [ "string", "null" ] }, - "label": { - "description": "The printed label, such as Table 2 or Supplementary Table S4.", + "footer": { "type": [ "string", "null" ] }, - "number": { + "raw_path": { + "description": "The source's raw table file, relative to the bundle directory.", "type": [ "string", "null" ] }, - "raw_path": { - "description": "The source's raw table file, relative to the bundle directory.", + "column_headings": { + "items": { + "type": "string" + }, "type": [ - "string", + "array", "null" ] }, @@ -1793,10 +1786,6 @@ "null" ] }, - "table_id": { - "description": "The table's key, as the source's parser names it. Stable for the life of the parsed paper and used unchanged by every consumer: the coordinate parse's `table_id`s, the record's Table.id, neurostore's Table.t_id. Never renumbered from the printed label and never sanitized differently on disk.", - "type": "string" - }, "text_span": { "anyOf": [ { @@ -1808,6 +1797,17 @@ ], "description": "Where the table's rows are inlined in the text, when the source inlines them." }, + "coordinate_space_hint": { + "anyOf": [ + { + "$ref": "#/$defs/ReportedSpace" + }, + { + "type": "null" + } + ], + "description": "The source parser's own guess at the table's space. A hint for the coordinate stages, not an answer: the space a point is stored in is the coordinate parse's." + }, "triage": { "anyOf": [ { @@ -1839,20 +1839,6 @@ "additionalProperties": false, "description": "A value reported at a point. NIMADS's point value.", "properties": { - "correction": { - "anyOf": [ - { - "$ref": "#/$defs/CorrectionMethod" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "For a p value, the correction it is under; absent for a statistic or effect size." - }, "kind": { "anyOf": [ { @@ -1869,19 +1855,33 @@ "null" ] }, - "level": { + "value": { + "type": "number" + }, + "level": { + "anyOf": [ + { + "$ref": "#/$defs/ValueLevel" + }, + { + "type": "null" + } + ], + "description": "Whether the value describes the peak or the cluster the peak belongs to. A cluster-level p printed on the peak's row is the cluster's." + }, + "correction": { "anyOf": [ { - "$ref": "#/$defs/ValueLevel" + "$ref": "#/$defs/CorrectionMethod" + }, + { + "type": "string" }, { "type": "null" } ], - "description": "Whether the value describes the peak or the cluster the peak belongs to. A cluster-level p printed on the peak's row is the cluster's." - }, - "value": { - "type": "number" + "description": "For a p value, the correction it is under; absent for a statistic or effect size." } }, "required": [ @@ -1972,32 +1972,32 @@ "additionalProperties": false, "description": "The program and stage that wrote an artifact.", "properties": { - "model": { - "description": "The language model a stage called, if any.", - "type": [ - "string", - "null" - ] - }, "name": { "description": "ns-pond-ingestion-workflow or pondie.", "type": "string" }, - "prompt_version": { + "version": { + "type": "string" + }, + "stage": { + "description": "The stage within the producer, such as extract, space or audit.", "type": [ "string", "null" ] }, - "stage": { - "description": "The stage within the producer, such as extract, space or audit.", + "model": { + "description": "The language model a stage called, if any.", "type": [ "string", "null" ] }, - "version": { - "type": "string" + "prompt_version": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -2020,29 +2020,10 @@ "additionalProperties": false, "description": "One entry of a paper's reference list.", "properties": { - "doi": { - "type": [ - "string", - "null" - ] - }, "id": { "description": "Stable within the paper: the source's own id where it has one (a JATS `ref` id such as `B12`), else `ref`.", "type": "string" }, - "label": { - "description": "How the text cites it: the number of a numbered style (\"12\"), or the author-year key of an author-year style (\"Stroop 1935\").", - "type": [ - "string", - "null" - ] - }, - "pmid": { - "type": [ - "string", - "null" - ] - }, "position": { "description": "1-based order in the list, which a numbered citation style cites by.", "minimum": 1, @@ -2065,10 +2046,29 @@ ], "description": "Where the entry came from." }, + "label": { + "description": "How the text cites it: the number of a numbered style (\"12\"), or the author-year key of an author-year style (\"Stroop 1935\").", + "type": [ + "string", + "null" + ] + }, "text": { "description": "The entry as printed.", "type": "string" }, + "doi": { + "type": [ + "string", + "null" + ] + }, + "pmid": { + "type": [ + "string", + "null" + ] + }, "title": { "type": [ "string", @@ -2129,25 +2129,26 @@ ] }, "properties": { - "license": { - "$ref": "#/$defs/License", - "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." + "reported_by": { + "$ref": "#/$defs/MetadataSource" }, - "preprint_server": { + "route": { + "$ref": "#/$defs/CopyRoute" + }, + "version": { "anyOf": [ { - "$ref": "#/$defs/PreprintServer" + "$ref": "#/$defs/CopyVersion" }, { "type": "null" } - ] - }, - "reported_by": { - "$ref": "#/$defs/MetadataSource" + ], + "description": "Absent when no source says which version the copy is." }, - "route": { - "$ref": "#/$defs/CopyRoute" + "license": { + "$ref": "#/$defs/License", + "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." }, "url": { "type": [ @@ -2155,16 +2156,15 @@ "null" ] }, - "version": { + "preprint_server": { "anyOf": [ { - "$ref": "#/$defs/CopyVersion" + "$ref": "#/$defs/PreprintServer" }, { "type": "null" } - ], - "description": "Absent when no source says which version the copy is." + ] } }, "required": [ @@ -2229,17 +2229,6 @@ "additionalProperties": false, "description": "A span of the text and what it is.", "properties": { - "end_char": { - "minimum": 0, - "type": "integer" - }, - "heading": { - "description": "The heading as printed.", - "type": [ - "string", - "null" - ] - }, "kind": { "anyOf": [ { @@ -2250,9 +2239,20 @@ } ] }, + "heading": { + "description": "The heading as printed.", + "type": [ + "string", + "null" + ] + }, "start_char": { "minimum": 0, "type": "integer" + }, + "end_char": { + "minimum": 0, + "type": "integer" } }, "required": [ @@ -2416,23 +2416,23 @@ "additionalProperties": false, "description": "One item of supplementary material.", "properties": { - "fetched": { - "type": "boolean" - }, "label": { "type": [ "string", "null" ] }, - "path": { - "description": "Where the fetched file is, relative to the bundle directory.", + "url": { "type": [ "string", "null" ] }, - "url": { + "fetched": { + "type": "boolean" + }, + "path": { + "description": "Where the fetched file is, relative to the bundle directory.", "type": [ "string", "null" @@ -2465,6 +2465,9 @@ "additionalProperties": false, "description": "What reading one table for coordinates found.", "properties": { + "table_id": { + "type": "string" + }, "reading": { "$ref": "#/$defs/TableReadingKind" }, @@ -2474,9 +2477,6 @@ "string", "null" ] - }, - "table_id": { - "type": "string" } }, "required": [ @@ -2502,6 +2502,11 @@ "additionalProperties": false, "description": "One body row of a table.", "properties": { + "row": { + "description": "The row's index among the table's body rows, from 0. What a coordinate parse's point cites.", + "minimum": 0, + "type": "integer" + }, "cells": { "description": "The row's cells, as printed, in column order.", "items": { @@ -2512,11 +2517,6 @@ "null" ] }, - "row": { - "description": "The row's index among the table's body rows, from 0. What a coordinate parse's point cites.", - "minimum": 0, - "type": "integer" - }, "text_span": { "anyOf": [ { @@ -2539,14 +2539,16 @@ "additionalProperties": false, "description": "Whether a table is worth reading for coordinates, and why.", "properties": { - "excluded_by_hand": { - "description": "A person marked the table as not holding coordinates. Overrides `passes`, and no consumer reads coordinates from it.", + "passes": { + "type": "boolean" + }, + "score": { "type": [ - "boolean", + "number", "null" ] }, - "exclusion_reason": { + "route": { "type": [ "string", "null" @@ -2558,18 +2560,16 @@ "null" ] }, - "passes": { - "type": "boolean" - }, - "route": { + "excluded_by_hand": { + "description": "A person marked the table as not holding coordinates. Overrides `passes`, and no consumer reads coordinates from it.", "type": [ - "string", + "boolean", "null" ] }, - "score": { + "exclusion_reason": { "type": [ - "number", + "string", "null" ] } @@ -2593,12 +2593,12 @@ "additionalProperties": false, "description": "A range of the parsed paper's text, by character offset.", "properties": { - "end_char": { - "description": "Exclusive.", + "start_char": { "minimum": 0, "type": "integer" }, - "start_char": { + "end_char": { + "description": "Exclusive.", "minimum": 0, "type": "integer" }, @@ -2637,6 +2637,9 @@ "null" ] }, + "truncated": { + "type": "boolean" + }, "points_dropped_as_table_restatements": { "description": "Points found in the text but dropped because a table already reports them.", "minimum": 0, @@ -2644,9 +2647,6 @@ "integer", "null" ] - }, - "truncated": { - "type": "boolean" } }, "required": [ @@ -2659,6 +2659,20 @@ "additionalProperties": false, "description": "One threshold an analysis's results survived. Maps onto the storage schema's InferenceSettings: a `height` threshold is height_threshold_value and _type, a `cluster_extent` is cluster_extent_threshold, a `cluster_level` is clusterwise_threshold_value, `tfce` sets tfce_used; `correction` is multiple_comparison_method at that level.", "properties": { + "level": { + "$ref": "#/$defs/ThresholdLevel" + }, + "quantity": { + "$ref": "#/$defs/ThresholdQuantity", + "description": "What `value` is measured in." + }, + "value": { + "description": "Absent when the paper names the correction but not the number.", + "type": [ + "number", + "null" + ] + }, "correction": { "anyOf": [ { @@ -2672,13 +2686,6 @@ } ] }, - "level": { - "$ref": "#/$defs/ThresholdLevel" - }, - "quantity": { - "$ref": "#/$defs/ThresholdQuantity", - "description": "What `value` is measured in." - }, "scope_as_printed": { "description": "What the correction ran over, as printed, such as \"whole brain\" or \"within the amygdala mask\".", "type": [ @@ -2696,13 +2703,6 @@ } ], "description": "Where the threshold was read." - }, - "value": { - "description": "Absent when the paper names the correction but not the number.", - "type": [ - "number", - "null" - ] } }, "required": [ @@ -2763,31 +2763,18 @@ "description": "Every coordinate a paper reports, grouped into analyses. An original parse is written by the ingestion workflow; a revision is written by pondie when its audit disagrees with the original.\nA parse is never edited after it is written. A reader that disagrees with it writes a revision instead, and the revision is a complete parse on its own: neurostore can store it as a new version of the paper's analyses without consulting the parse it revises.\nAnalyses that pondie accepts or relabels keep their keys, because their name, space and points are unchanged, so claims made against them carry over to the revision untouched. Only a split, a merge or an omission changes which keys exist.", "metamodel_version": "1.12.0", "properties": { - "analyses": { - "description": "May be empty: the parse ran and found no analysis. A paper the parse never ran on has no CoordinateParse at all, so the two are never confused, and `tables` says what reading each table found.", - "items": { - "$ref": "#/$defs/ParsedAnalysis" - }, - "type": "array" - }, "header": { "$ref": "#/$defs/ArtifactHeader", "description": "`inputs` names the parsed paper by `text_sha256`, and for a revision also the parse it revises by `parse_id`." }, - "omitted_analyses": { - "description": "Text or figure analyses the producer read and left out of `analyses`, each with the reason. A table's omissions are in `tables`. Absent when nothing was left out.", - "items": { - "$ref": "#/$defs/OmittedAnalysis" - }, - "type": [ - "array", - "null" - ] - }, "parse_id": { "description": "This parse's identity, the fingerprint every later artifact cites. Unique per paper; a sha256 of the canonical JSON of `analyses` is recommended, so two producers that reach the same analyses reach the same id.", "type": "string" }, + "text_sha256": { + "description": "The parsed paper's fingerprint. Every point's cells and spans address it.", + "type": "string" + }, "revision_of": { "description": "The `parse_id` of the parse this one revises. Absent on an original parse, which is how a reader tells the two apart.", "type": [ @@ -2795,6 +2782,13 @@ "null" ] }, + "analyses": { + "description": "May be empty: the parse ran and found no analysis. A paper the parse never ran on has no CoordinateParse at all, so the two are never confused, and `tables` says what reading each table found.", + "items": { + "$ref": "#/$defs/ParsedAnalysis" + }, + "type": "array" + }, "tables": { "description": "One entry per table of the parsed paper: what reading it for coordinates found, or why it was not read. A table with no entry was not considered.", "items": { @@ -2805,9 +2799,15 @@ "null" ] }, - "text_sha256": { - "description": "The parsed paper's fingerprint. Every point's cells and spans address it.", - "type": "string" + "omitted_analyses": { + "description": "Text or figure analyses the producer read and left out of `analyses`, each with the reason. A table's omissions are in `tables`. Absent when nothing was left out.", + "items": { + "$ref": "#/$defs/OmittedAnalysis" + }, + "type": [ + "array", + "null" + ] }, "text_sweep": { "anyOf": [ diff --git a/src/study_schema/jsonschema/extraction-record.schema.json b/src/study_schema/jsonschema/extraction-record.schema.json index 19e5bdd..bcb1162 100644 --- a/src/study_schema/jsonschema/extraction-record.schema.json +++ b/src/study_schema/jsonschema/extraction-record.schema.json @@ -4,16 +4,16 @@ "additionalProperties": false, "description": "A modality-agnostic protocol used to acquire neuroimaging or associated physiological data, holding only what every modality has in common. One Acquisition carries one modality: simultaneous recordings such as EEG+fMRI are separate Acquisition records that a Task lists together. Everything modality-specific lives in the class the Modality value instantiates -- MRI, EEG, FNIRS, PET, or OtherModality.", "properties": { - "acquisition_duration_seconds": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedNumber" - }, - { - "type": "null" - } - ], - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "type": "string" }, "acquisition_type": { "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", @@ -22,16 +22,20 @@ ], "type": "string" }, - "brain_coverage": { + "modality": { + "$ref": "#/$defs/ExtractedModality", + "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + }, + "acquisition_duration_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedBrainCoverage" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." }, "device": { "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", @@ -41,20 +45,16 @@ ], "x-reference": "Device" }, - "local_id": { - "type": "string" - }, - "modality": { - "$ref": "#/$defs/ExtractedModality", - "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "brain_coverage": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedBrainCoverage" + }, + { + "type": "null" + } + ], + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -80,38 +80,65 @@ "additionalProperties": false, "description": "A distinct reported inferential map or tested effect, linking its source acquisitions, sample, paradigm context, tested effect, preprocessing, model estimation, inference settings, and source result tables.\nCreate one Analysis for each distinct tested effect. Separate Analyses are required when normalized direction, participant-group comparison, method, the pattern of `effect.cells`, seed or connectivity model, decoded variable, component identity, or spatial_scope differ, and when an omnibus effect and its directional post-hoc contrast are both reported. ROI definitions, acquisition summaries, descriptive sample tables, and masks without inferential effects are not Analyses. Nor is a map reported with no inferential test at all -- an ICA or PCA component map presented descriptively, a connectivity matrix given without a test -- there being nothing for such a map to be the effect of.", "properties": { - "acquisitions": { - "description": "Acquisition protocols supplying data to this analysis. Structural, diffusion, and other non-paradigm analyses are linked directly through this field.", - "items": { - "type": "string" - }, + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": [ - "array", + "string", "null" - ], - "x-reference": "Acquisition" + ] }, - "assessments": { - "description": "Assessments whose measurements were used in or selected the sample for this analysis.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" + "local_id": { + "type": "string" + }, + "name": { + "$ref": "#/$defs/ExtractedString", + "description": "Study-local label for the analysis or inferential result, in the source's words." + }, + "definition": { + "$ref": "#/$defs/ExtractedString", + "description": "Precise statement of the tested effect in the source's own words, minimally cleaned. Always source-grounded. For an interaction it must state the direction of the interaction as the source states it; for an ordered contrast it must keep the ordering that the cell directions cannot express." + }, + "prespecification": { + "$ref": "#/$defs/ExtractedPrespecification", + "description": "Whether this contrast was planned before the data were seen or arrived at afterwards.\nOpen vocabulary: write the source's own wording in the field when neither value fits. The two are not a spectrum and the words a source uses for them are many -- `post-hoc`, `confirmatory`, `planned`, `hypothesis-driven` -- so a closed range would discard the whole analysis over a synonym. Normalization maps the wording onto the two values." + }, + "interpretations": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedStringList" + }, + { + "type": "null" + } ], - "x-reference": "Assessment" + "description": "The source's own statement of what this analysis found and what it means, typically in Results and Discussion, verbatim or minimally cleaned. Always source-grounded, and the counterpart of `definition`: that field is the Methods-side statement of what was tested, this one is the Results/Discussion-side statement of what came of it." }, - "coordinate_space": { + "outcome": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedAnalysisOutcome" }, { "type": "null" } ], - "description": "Coordinate reference space or template the spatial results are reported in, such as MNI152 or Talairach. **The authoritative of the two places this can live**: where this and a `Table.coordinate_space` disagree, this one is the analysis's space, since a table can be parsed as one space while the analysis it reports states another.\nBoth may be filled, and usually are: the table parser fills the tables and the same parse supplies this when every table behind the analysis agrees, leaving the model to answer only where they do not. Never inferred from the model's `spatial_unit` -- an analysis on a surface reports a surface template." + "description": "Whether anything survived the threshold this analysis was tested at: a cluster, a peak, a significant region. The answer to \"did this contrast find anything\", which `Effect.cells` cannot give -- a cell's direction is the sign of the contrast that was tested, and is written the same whether the test found an effect or not. Meta-analyses exclude null effects and pool only analyses that reported foci, so a tested-and-null contrast and a reported effect must not extract to the same record. Applies only to an analysis whose coordinate set is a `result` (or has no role yet): an ROI, seed, reference or other set was not tested here, so it has no outcome." + }, + "spatial_scope": { + "$ref": "#/$defs/ExtractedSpatialScope", + "description": "Spatial domain of the analysis modelled. The spatial_scope refers to what voxels were modelled, not which voxels/brain regions had a correction applied." + }, + "regions": { + "description": "The regions the analysis ran over -- its search space, what `spatial_scope: roi` restricts it to. Empty for whole-brain and searchlight, where the emptiness asserts that inference was not restricted.\nNot the analysis's seeds: a whole-brain seed-based analysis has a seed and no search-space region, so putting one here says the opposite of what it did. Seeds are `ConnectivityDetails.seed_regions`.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Region" }, "defines_regions": { "description": "Regions this analysis produced: a localizer's clusters, or peaks of a contrast later used as seeds or ROIs. Populated on the analysis a region came *from*, not on those that use it, so a region whose `definition_method` is `same_study_analysis` and which another analysis lists in `regions` or `seed_regions` is a circularity the record states rather than asserts.", @@ -124,9 +151,21 @@ ], "x-reference": "Region" }, - "definition": { - "$ref": "#/$defs/ExtractedString", - "description": "Precise statement of the tested effect in the source's own words, minimally cleaned. Always source-grounded. For an interaction it must state the direction of the interaction as the source states it; for an ordered contrast it must keep the ordering that the cell directions cannot express." + "measure": { + "description": "The scientific quantity of the map, by identifier, independent of the statistic used to test it. Analyses of one quantity reference one record; an analysis of a different quantity takes its own. Required: a paper vague about what it measured still takes a record, with `source_label` as given and `extraction_status: not_reported` elsewhere.", + "type": "string", + "x-reference": "Measure" + }, + "groups": { + "description": "The participant cohorts the analysis ran on, and how many participants each contributed. Membership only: this slot says which people were analysed, never what was compared. A cohort comparison is a pair of directional `Cell`s on the model term whose levels name the cohorts, exactly as a condition or occasion comparison is.", + "items": { + "$ref": "#/$defs/AnalysisGroup" + }, + "type": "array" + }, + "effect": { + "$ref": "#/$defs/Effect", + "description": "What was tested about the map. Universal across every method." }, "details": { "anyOf": [ @@ -157,58 +196,53 @@ ], "description": "The method that produced the map and its method-specific payload. The populated subclass identifies the method. A voxelwise, vertexwise, or ROI-wise GLM, mixed-effects model, ANOVA, ANCOVA, or robust regression takes MassUnivariateDetails; a method outside the first-class families takes OtherAnalysisDetails and names itself in method_label." }, - "effect": { - "$ref": "#/$defs/Effect", - "description": "What was tested about the map. Universal across every method." - }, - "groups": { - "description": "The participant cohorts the analysis ran on, and how many participants each contributed. Membership only: this slot says which people were analysed, never what was compared. A cohort comparison is a pair of directional `Cell`s on the model term whose levels name the cohorts, exactly as a condition or occasion comparison is.", + "acquisitions": { + "description": "Acquisition protocols supplying data to this analysis. Structural, diffusion, and other non-paradigm analyses are linked directly through this field.", "items": { - "$ref": "#/$defs/AnalysisGroup" + "type": "string" }, - "type": "array" - }, - "inference_settings": { - "description": "The thresholding and correction this analysis was reported under, by identifier. Analyses sharing one scheme reference one record; an analysis thresholded or corrected differently takes its own. Not the model's -- one fit thresholded two ways is two records here and one `model_estimation`. A contrast reported at two search volumes is two analyses sharing one `model_estimation`, not one analysis with the stricter threshold.", "type": [ - "string", + "array", "null" ], - "x-reference": "InferenceSettings" + "x-reference": "Acquisition" }, - "interpretations": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedStringList" - }, - { - "type": "null" - } + "tasks": { + "description": "The participant paradigms that contextualize this analysis. An analysis can span paradigms, as a cross-task conjunction or a localizer-plus-main-task analysis does.\nA resting-state analysis names its Task here like any other: the resting-state protocol is a paradigm, and a Task record exists for it. What leaves this empty is an analysis with no paradigm at all -- morphometry, diffusion, a static PET scan -- which is linked through `acquisitions` instead.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" ], - "description": "The source's own statement of what this analysis found and what it means, typically in Results and Discussion, verbatim or minimally cleaned. Always source-grounded, and the counterpart of `definition`: that field is the Methods-side statement of what was tested, this one is the Results/Discussion-side statement of what came of it." - }, - "local_id": { - "type": "string" + "x-reference": "Task" }, - "measure": { - "description": "The scientific quantity of the map, by identifier, independent of the statistic used to test it. Analyses of one quantity reference one record; an analysis of a different quantity takes its own. Required: a paper vague about what it measured still takes a record, with `source_label` as given and `extraction_status: not_reported` elsewhere.", + "model_estimation": { + "description": "The model this analysis's contrast was taken from: its family, estimator, stage, software, and the terms it was specified with. What was selected out of those terms is `effect`. Required: a paper that describes a contrast and never names an estimator still takes a record, with the terms it does state and `extraction_status: not_reported` elsewhere. Analyses sharing one design reference one record; an analysis specified or estimated differently takes its own.", "type": "string", - "x-reference": "Measure" + "x-reference": "ModelEstimation" }, - "mirror_of": { - "description": "Present only on an analysis the builder derived by reversing another one, and naming the analysis it reverses.\nA results table with both signs in one column reports one contrast read two ways. The paper's prose describes one direction, so only that half is extracted and the other is rebuilt arithmetically -- same cells, directions flipped. This slot is what says so. Without it the two halves look like independent findings, and a meta-analysis that counts both counts one sample twice.", + "inference_settings": { + "description": "The thresholding and correction this analysis was reported under, by identifier. Analyses sharing one scheme reference one record; an analysis thresholded or corrected differently takes its own. Not the model's -- one fit thresholded two ways is two records here and one `model_estimation`. A contrast reported at two search volumes is two analyses sharing one `model_estimation`, not one analysis with the stricter threshold.", "type": [ "string", "null" ], - "x-reference": "Analysis" + "x-reference": "InferenceSettings" }, - "model_estimation": { - "description": "The model this analysis's contrast was taken from: its family, estimator, stage, software, and the terms it was specified with. What was selected out of those terms is `effect`. Required: a paper that describes a contrast and never names an estimator still takes a record, with the terms it does state and `extraction_status: not_reported` elsewhere. Analyses sharing one design reference one record; an analysis specified or estimated differently takes its own.", - "type": "string", - "x-reference": "ModelEstimation" + "tables": { + "description": "The source tables reporting results for this analysis.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Table" }, - "model_representation_notes": { + "source_table_analysis": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -217,29 +251,21 @@ "type": "null" } ], - "description": "Model components that could not be represented or were represented only approximately, for an analysis whose method is otherwise first-class. Examples include random slopes, latent variables, dynamic connectivity, and dependencies between terms. An analysis whose method has no stable structured decomposition at all takes NotStructurableDetails instead." - }, - "name": { - "$ref": "#/$defs/ExtractedString", - "description": "Study-local label for the analysis or inferential result, in the source's words." + "description": "The key of the coordinate-parse analysis this analysis's results were read from: `ParsedAnalysis.key` in neuroimaging-paper-parse, `analysis#`, where `h` hashes the parse analysis's name, space and points. Derived from what was extracted, not where it was read, so a re-parse that extracts the same analysis reaches the same key. When pondie revised the parse, the key is the revision's.\nThis schema stores no coordinates, so reaching an analysis's foci means joining through the parse, and `tables` alone cannot do it: a table usually reports several contrasts and several analyses usually cite the same table. This key is what makes the join exact.\nCopy it from the bracketed key the stage-1 listing prints beside each parsed analysis. One key and not a list, because one analysis is read from one row group; an analysis whose results span two tables cites both in `tables` and names here the row group that defined it." }, - "outcome": { + "coordinate_space": { "anyOf": [ { - "$ref": "#/$defs/ExtractedAnalysisOutcome" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Whether anything survived the threshold this analysis was tested at: a cluster, a peak, a significant region. The answer to \"did this contrast find anything\", which `Effect.cells` cannot give -- a cell's direction is the sign of the contrast that was tested, and is written the same whether the test found an effect or not. Meta-analyses exclude null effects and pool only analyses that reported foci, so a tested-and-null contrast and a reported effect must not extract to the same record. Applies only to an analysis whose coordinate set is a `result` (or has no role yet): an ROI, seed, reference or other set was not tested here, so it has no outcome." - }, - "prespecification": { - "$ref": "#/$defs/ExtractedPrespecification", - "description": "Whether this contrast was planned before the data were seen or arrived at afterwards.\nOpen vocabulary: write the source's own wording in the field when neither value fits. The two are not a spectrum and the words a source uses for them are many -- `post-hoc`, `confirmatory`, `planned`, `hypothesis-driven` -- so a closed range would discard the whole analysis over a synonym. Normalization maps the wording onto the two values." + "description": "Coordinate reference space or template the spatial results are reported in, such as MNI152 or Talairach. **The authoritative of the two places this can live**: where this and a `Table.coordinate_space` disagree, this one is the analysis's space, since a table can be parsed as one space while the analysis it reports states another.\nBoth may be filled, and usually are: the table parser fills the tables and the same parse supplies this when every table behind the analysis agrees, leaving the model to answer only where they do not. Never inferred from the model's `spatial_unit` -- an analysis on a surface reports a surface template." }, - "regions": { - "description": "The regions the analysis ran over -- its search space, what `spatial_scope: roi` restricts it to. Empty for whole-brain and searchlight, where the emptiness asserts that inference was not restricted.\nNot the analysis's seeds: a whole-brain seed-based analysis has a seed and no search-space region, so putting one here says the opposite of what it did. Seeds are `ConnectivityDetails.seed_regions`.", + "assessments": { + "description": "Assessments whose measurements were used in or selected the sample for this analysis.", "items": { "type": "string" }, @@ -247,9 +273,9 @@ "array", "null" ], - "x-reference": "Region" + "x-reference": "Assessment" }, - "source_table_analysis": { + "model_representation_notes": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -258,41 +284,15 @@ "type": "null" } ], - "description": "The key of the coordinate-parse analysis this analysis's results were read from: `ParsedAnalysis.key` in neuroimaging-paper-parse, `analysis#`, where `h` hashes the parse analysis's name, space and points. Derived from what was extracted, not where it was read, so a re-parse that extracts the same analysis reaches the same key. When pondie revised the parse, the key is the revision's.\nThis schema stores no coordinates, so reaching an analysis's foci means joining through the parse, and `tables` alone cannot do it: a table usually reports several contrasts and several analyses usually cite the same table. This key is what makes the join exact.\nCopy it from the bracketed key the stage-1 listing prints beside each parsed analysis. One key and not a list, because one analysis is read from one row group; an analysis whose results span two tables cites both in `tables` and names here the row group that defined it." - }, - "spatial_scope": { - "$ref": "#/$defs/ExtractedSpatialScope", - "description": "Spatial domain of the analysis modelled. The spatial_scope refers to what voxels were modelled, not which voxels/brain regions had a correction applied." + "description": "Model components that could not be represented or were represented only approximately, for an analysis whose method is otherwise first-class. Examples include random slopes, latent variables, dynamic connectivity, and dependencies between terms. An analysis whose method has no stable structured decomposition at all takes NotStructurableDetails instead." }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "mirror_of": { + "description": "Present only on an analysis the builder derived by reversing another one, and naming the analysis it reverses.\nA results table with both signs in one column reports one contrast read two ways. The paper's prose describes one direction, so only that half is extracted and the other is rebuilt arithmetically -- same cells, directions flipped. This slot is what says so. Without it the two halves look like independent findings, and a meta-analysis that counts both counts one sample twice.", "type": [ "string", "null" - ] - }, - "tables": { - "description": "The source tables reporting results for this analysis.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Table" - }, - "tasks": { - "description": "The participant paradigms that contextualize this analysis. An analysis can span paradigms, as a cross-task conjunction or a localizer-plus-main-task analysis does.\nA resting-state analysis names its Task here like any other: the resting-state protocol is a paradigm, and a Task record exists for it. What leaves this empty is an analysis with no paradigm at all -- morphometry, diffusion, a static PET scan -- which is linked through `acquisitions` instead.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" ], - "x-reference": "Task" + "x-reference": "Analysis" } }, "required": [ @@ -393,7 +393,22 @@ "additionalProperties": false, "description": "One thing participants were assigned to receive, or to receive in place of it.", "properties": { - "agent": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "type": "string" + }, + "name": { + "$ref": "#/$defs/ExtractedString", + "description": "Short arm label as the source states it, for example methylphenidate, sham tDCS, waitlist." + }, + "description": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -402,13 +417,13 @@ "type": "null" } ], - "description": "The drug, agent, or stimulation target this arm received, as the source names it. The name only; dose, unit, route, and schedule are not recorded." + "description": "Complete definition of what this arm received." }, "arm_kind": { "$ref": "#/$defs/ExtractedArmKind", "description": "What class of thing this arm received. Required, so an arm whose kind the source leaves unclear still carries the slot, as `extraction_status: not_reported`." }, - "description": { + "agent": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -417,22 +432,7 @@ "type": "null" } ], - "description": "Complete definition of what this arm received." - }, - "local_id": { - "type": "string" - }, - "name": { - "$ref": "#/$defs/ExtractedString", - "description": "Short arm label as the source states it, for example methylphenidate, sham tDCS, waitlist." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "The drug, agent, or stimulation target this arm received, as the source names it. The name only; dose, unit, route, and schedule are not recorded." } }, "required": [ @@ -462,7 +462,23 @@ "additionalProperties": false, "description": "A behavioral, cognitive, biological, clinical, or questionnaire-based instrument or procedure that produces participant-level measurements independently of the neuroimaging acquisition or experimental conditions.", "properties": { - "assessment_type": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "description": "Stable identifier.", + "type": "string" + }, + "name": { + "$ref": "#/$defs/ExtractedString", + "description": "Name of the assessment, such as Digit Span, MMSE, or BDI." + }, + "description": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -471,9 +487,9 @@ "type": "null" } ], - "description": "Broad type, such as cognitive test, clinical scale, diagnostic interview, questionnaire, behavioral task, physiological measure, or a biospecimen assay such as salivary cortisol or a plasma marker. Free text rather than a vocabulary: create a category if none of these fit." + "description": "Description of the assessment and the measurements it produced, including the purpose the source gives for administering it -- establishing a group's diagnosis, screening a comorbidity, grading severity, characterizing everyone. That purpose is what decides whether a group may name this assessment as its `diagnostic_instrument`, and it is often the only sentence distinguishing the two." }, - "description": { + "assessment_type": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -482,23 +498,7 @@ "type": "null" } ], - "description": "Description of the assessment and the measurements it produced, including the purpose the source gives for administering it -- establishing a group's diagnosis, screening a comorbidity, grading severity, characterizing everyone. That purpose is what decides whether a group may name this assessment as its `diagnostic_instrument`, and it is often the only sentence distinguishing the two." - }, - "local_id": { - "description": "Stable identifier.", - "type": "string" - }, - "name": { - "$ref": "#/$defs/ExtractedString", - "description": "Name of the assessment, such as Digit Span, MMSE, or BDI." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "Broad type, such as cognitive test, clinical scale, diagnostic interview, questionnaire, behavioral task, physiological measure, or a biospecimen assay such as salivary cortisol or a plasma marker. Free text rather than a vocabulary: create a category if none of these fit." } }, "required": [ @@ -549,27 +549,27 @@ "$ref": "#/$defs/ExtractedString", "description": "The distribution bucket counted by this entry, labelled as the study writes it and not normalized to any external scheme: `female`, `left-handed`, `Hispanic or Latino`." }, - "category_normalized": { + "count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "`category` folded onto the answer set for the distribution it belongs to, by `pondie.normalization.sex_distribution` or `.handedness_distribution`. Unset where no normalizer covers the distribution -- unset is not UNKNOWN, which says a normalizer read the wording and could not place it." + "description": "Number of participants assigned to this category." }, - "count": { + "percentage": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Number of participants assigned to this category." + "description": "Percentage of the relevant group in this category." }, "denominator": { "anyOf": [ @@ -582,18 +582,18 @@ ], "description": "Number of participants used as the denominator for the percentage." }, - "percentage": { + "reporting_framework": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Percentage of the relevant group in this category." + "description": "The classification system the study used for these categories, in its own words, such as the OMB or NIH race and ethnicity categories, a named handedness inventory, or a self-report question's answer set." }, - "reporting_framework": { + "category_normalized": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -602,7 +602,7 @@ "type": "null" } ], - "description": "The classification system the study used for these categories, in its own words, such as the OMB or NIH race and ethnicity categories, a named handedness inventory, or a self-report question's answer set." + "description": "`category` folded onto the answer set for the distribution it belongs to, by `pondie.normalization.sex_distribution` or `.handedness_distribution`. Unset where no normalizer covers the distribution -- unset is not UNKNOWN, which says a normalizer read the wording and could not place it." } }, "required": [ @@ -615,11 +615,12 @@ "additionalProperties": false, "description": "One side of the tested comparison: a level of a model term, and which way that level entered. The unit a contrast is built from.\nA cell points at a `ModelTerm`. The term's `FactorLevel`s record which cohort, condition, occasion, or arm carries each level. Joining through the model recovers the contrast's entities, including its conditions and cohorts. The `term` is the comparison axis: cells sharing a term compare within a factor, cells on different terms compare across them.\nA cell over a term that declares no levels -- a slope, or a product column -- leaves `level` unset and carries its `direction` alone.", "properties": { - "direction": { - "$ref": "#/$defs/ExtractedDirection", - "description": "How this cell entered the contrast: the weight's sign for a categorical level, the fitted coefficient's sign for a continuous term, or the interaction coefficient's sign for a product column -- read as the named level's slope relative to the others when the cell names one (`level`).\nSigns, never numeric weights. For an ordered comparison mark the extremes `positive` and `negative`, leave the levels in between out of `cells` -- absence is the zero weight -- and keep the ordering on `FactorLevel.order`." + "term": { + "description": "The model term this cell belongs to, by identifier. It must be one of the terms of the ModelEstimation the containing Analysis references, or of a stage that model reaches through `inputs_from` -- a group contrast of a first-level condition, or of a seed's time series, is a cell on that stage's term rather than on a copy of it.", + "type": "string", + "x-reference": "ModelTerm" }, - "label": { + "level": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -628,9 +629,13 @@ "type": "null" } ], - "description": "The source's wording for this level in this comparison, when it differs from the level's own label on the model." + "description": "The level represented by this cell, matching a `FactorLevel.level` on the referenced ModelTerm. The model states the level vocabulary; this field only identifies the level placed on this side of the comparison.\nRequired exactly when the referenced term declares levels. A slope declares none, and its cell names none. A product column declares none either, but a signed cell on one crossing a factor with a continuous term may name a level of the factor: the cohort or condition whose slope has the cell's sign, relative to the factor's other levels. \"Maltreatment was negatively associated with rACC activation in the PTSD group, but not in controls\" is `direction: negative` with `level: PTSD group`. Without the level the sign has no reference, since which level the difference is taken from is a coding choice." }, - "level": { + "direction": { + "$ref": "#/$defs/ExtractedDirection", + "description": "How this cell entered the contrast: the weight's sign for a categorical level, the fitted coefficient's sign for a continuous term, or the interaction coefficient's sign for a product column -- read as the named level's slope relative to the others when the cell names one (`level`).\nSigns, never numeric weights. For an ordered comparison mark the extremes `positive` and `negative`, leave the levels in between out of `cells` -- absence is the zero weight -- and keep the ordering on `FactorLevel.order`." + }, + "label": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -639,12 +644,7 @@ "type": "null" } ], - "description": "The level represented by this cell, matching a `FactorLevel.level` on the referenced ModelTerm. The model states the level vocabulary; this field only identifies the level placed on this side of the comparison.\nRequired exactly when the referenced term declares levels. A slope declares none, and its cell names none. A product column declares none either, but a signed cell on one crossing a factor with a continuous term may name a level of the factor: the cohort or condition whose slope has the cell's sign, relative to the factor's other levels. \"Maltreatment was negatively associated with rACC activation in the PTSD group, but not in controls\" is `direction: negative` with `level: PTSD group`. Without the level the sign has no reference, since which level the difference is taken from is a coding choice." - }, - "term": { - "description": "The model term this cell belongs to, by identifier. It must be one of the terms of the ModelEstimation the containing Analysis references, or of a stage that model reaches through `inputs_from` -- a group contrast of a first-level condition, or of a seed's time series, is a cell on that stage's term rather than on a copy of it.", - "type": "string", - "x-reference": "ModelTerm" + "description": "The source's wording for this level in this comparison, when it differs from the level's own label on the model." } }, "required": [ @@ -658,54 +658,54 @@ "additionalProperties": false, "description": "A named condition of a task: a modelled state, event type, or baseline that an analysis can contrast against another. Only what happened inside the paradigm -- the occasion the task was run on and the arm it was run under are Timepoints and Arms, and enter an analysis as levels of their own model terms, compared by `Cell`s on the Effect.", "properties": { - "condition_kind": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "description": "Stable identifier.", + "type": "string" + }, + "name": { + "$ref": "#/$defs/ExtractedString", + "description": "Short condition label that distinguishes the condition from other task periods or modeled effects." + }, + "stimulus_content": { "anyOf": [ { - "$ref": "#/$defs/ExtractedConditionKind" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "What kind of state this was: an active task state, a rest or fixation period, or a state the source calls a control or neutral condition. A field of its own rather than a phrase in `description`, which a consumer cannot reliably read it out of." + "description": "What this condition's stimuli were about -- their category, not the channel (`Task.stimulus_modality`) or the materials (`Task.stimuli`): a cigarette photograph is `cigarettes`. Faces, scenes, words, tones, music, food, money -- examples, not a vocabulary.\nLeave unset where the stimulus does not distinguish this condition from the others: every n-back load level shows the same letters." }, - "description": { + "condition_kind": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedConditionKind" }, { "type": "null" } ], - "description": "Complete definition of the condition, including whether it put a demand on the participant. Whether it was a rest, fixation, or control state belongs in `condition_kind`.\nA rest or fixation period inside another paradigm is a Condition of that paradigm's Task. A standalone resting-state run is not: it is its own Task, with one condition or none. The distinction is which paradigm the participant was in, not what they were doing -- a fixation baseline between working-memory blocks belongs to the working-memory Task.\nWhat a tested effect was measured against is not recorded here: a contrast against this condition gives it a `negative` cell, and a contrast against the unmodelled implicit baseline has no second cell." - }, - "local_id": { - "description": "Stable identifier.", - "type": "string" - }, - "name": { - "$ref": "#/$defs/ExtractedString", - "description": "Short condition label that distinguishes the condition from other task periods or modeled effects." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "What kind of state this was: an active task state, a rest or fixation period, or a state the source calls a control or neutral condition. A field of its own rather than a phrase in `description`, which a consumer cannot reliably read it out of." }, - "stimulus_content": { + "description": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "What this condition's stimuli were about -- their category, not the channel (`Task.stimulus_modality`) or the materials (`Task.stimuli`): a cigarette photograph is `cigarettes`. Faces, scenes, words, tones, music, food, money -- examples, not a vocabulary.\nLeave unset where the stimulus does not distinguish this condition from the others: every n-back load level shows the same letters." + "description": "Complete definition of the condition, including whether it put a demand on the participant. Whether it was a rest, fixation, or control state belongs in `condition_kind`.\nA rest or fixation period inside another paradigm is a Condition of that paradigm's Task. A standalone resting-state run is not: it is its own Task, with one condition or none. The distinction is which paradigm the participant was in, not what they were doing -- a fixation baseline between working-memory blocks belongs to the working-memory Task.\nWhat a tested effect was measured against is not recorded here: a contrast against this condition gives it a `negative` cell, and a contrast against the unmodelled implicit baseline has no second cell." } }, "required": [ @@ -777,34 +777,34 @@ "null" ] }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "ConjunctionDetails" - ], - "type": "string" - }, - "implementation": { + "null_hypothesis": { "anyOf": [ { - "$ref": "#/$defs/ExtractedConjunctionImplementation" + "$ref": "#/$defs/ExtractedNullHypothesis" }, { "type": "null" } ], - "description": "Procedure that computed the conjunction, recorded separately from the hypothesis it tested." + "description": "Which null hypothesis was tested. Only the conjunction null licenses the strong reading." }, - "null_hypothesis": { + "implementation": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNullHypothesis" + "$ref": "#/$defs/ExtractedConjunctionImplementation" }, { "type": "null" } ], - "description": "Which null hypothesis was tested. Only the conjunction null licenses the strong reading." + "description": "Procedure that computed the conjunction, recorded separately from the hypothesis it tested." + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "ConjunctionDetails" + ], + "type": "string" } }, "required": [ @@ -831,12 +831,27 @@ "$ref": "#/$defs/ExtractedConnectivityMethod", "description": "Method used to estimate connectivity." }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "ConnectivityDetails" + "seed_regions": { + "description": "Seed or source regions, by identifier. What each one is and how it was delimited is on the `Region`; naming it here says only that this analysis seeded from it.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" ], - "type": "string" + "x-reference": "Region" + }, + "target_regions": { + "description": "Target regions, by identifier. Empty for a seed-to-whole-brain analysis.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Region" }, "edges": { "description": "Named region pairs, when the source reports specific edges.", @@ -848,49 +863,34 @@ "null" ] }, - "inference_target": { + "modulatory_input": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInferenceTarget" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "What the statistical test was about: the strength of a connection, which of several models fits better, or a network taken as a whole. A model comparison and a parameter test answer different questions and are not poolable." + "description": "Modulatory input of the model, for DCM and related methods." }, - "modulatory_input": { + "inference_target": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedInferenceTarget" }, { "type": "null" } ], - "description": "Modulatory input of the model, for DCM and related methods." - }, - "seed_regions": { - "description": "Seed or source regions, by identifier. What each one is and how it was delimited is on the `Region`; naming it here says only that this analysis seeded from it.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Region" + "description": "What the statistical test was about: the strength of a connection, which of several models fits better, or a network taken as a whole. A model comparison and a parameter test answer different questions and are not poolable." }, - "target_regions": { - "description": "Target regions, by identifier. Empty for a seed-to-whole-brain analysis.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "ConnectivityDetails" ], - "x-reference": "Region" + "type": "string" } }, "required": [ @@ -955,39 +955,58 @@ "additionalProperties": false, "description": "A group of coordinates reported together -- a row group of a table, or a run of sentences in the text -- and what it is for. The coordinates stay in the parse, as for tables; this records their role and links them to what they belong to: the analysis a result reports, the regions an anchor locates.\nA set has one role: its dominant one. The groups come from the table parse, which splits a table by what was tested rather than by what each row is for, so a set can mix roles -- a connectivity results table that lists its own seed as a row. Such a set keeps the role of most of its points, and the odd point takes that role with it. This is a known error, about 1.3% of sets, accepted rather than splitting the parse's groups.", "properties": { - "analysis": { - "description": "For a `result`, the analysis whose findings these are. The same join `Analysis.source_table_analysis` makes from the other side, here for sets read from the text as well.", + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": [ "string", "null" + ] + }, + "local_id": { + "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 the set's name, space and points, so the key survives a re-parse that extracts the same set.", + "type": "string" + }, + "role": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedCoordinateRole" + }, + { + "type": "null" + } ], - "x-reference": "Analysis" + "description": "What these coordinates are for." }, - "anchor_kind": { + "result_kind": { "anyOf": [ { - "$ref": "#/$defs/ExtractedAnchorKind" + "$ref": "#/$defs/ExtractedResultKind" }, { "type": "null" } ], - "description": "For an `anchor`, what it defines. Absent for every other role." + "description": "For a `result`, the kind of analysis that produced it. Absent for every other role." }, - "description": { + "anchor_kind": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedAnchorKind" }, { "type": "null" } ], - "description": "What the set is, in the source's words, when the role alone does not say." + "description": "For an `anchor`, what it defines. Absent for every other role." }, - "local_id": { - "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 the set's name, space and points, so the key survives a re-parse that extracts the same set.", - "type": "string" + "analysis": { + "description": "For a `result`, the analysis whose findings these are. The same join `Analysis.source_table_analysis` makes from the other side, here for sets read from the text as well.", + "type": [ + "string", + "null" + ], + "x-reference": "Analysis" }, "regions": { "description": "For an `anchor`, the regions these coordinates locate.", @@ -1000,35 +1019,16 @@ ], "x-reference": "Region" }, - "result_kind": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedResultKind" - }, - { - "type": "null" - } - ], - "description": "For a `result`, the kind of analysis that produced it. Absent for every other role." - }, - "role": { + "description": { "anyOf": [ { - "$ref": "#/$defs/ExtractedCoordinateRole" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "What these coordinates are for." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "What the set is, in the source's words, when the role alone does not say." } }, "required": [ @@ -1041,6 +1041,10 @@ "additionalProperties": false, "description": "One class discriminated by a decoding analysis.", "properties": { + "label": { + "$ref": "#/$defs/ExtractedString", + "description": "The class label as the source names it, such as `faces` or `left hand`." + }, "condition": { "description": "The experimental condition this class corresponds to, when it is one.", "type": [ @@ -1048,10 +1052,6 @@ "null" ], "x-reference": "Condition" - }, - "label": { - "$ref": "#/$defs/ExtractedString", - "description": "The class label as the source names it, such as `faces` or `left hand`." } }, "required": [ @@ -1064,6 +1064,10 @@ "additionalProperties": false, "description": "Payload for a classification or pattern-discriminability analysis. Above-chance decoding is one signed cell with nothing crossed, and the reference it beat is `PerformanceMetric.reference_value`, which is per metric because accuracy against chance and AUC against 0.5 are two references. A between-cohort accuracy comparison is a crossed cohort term; accuracy regressed on a behavioural score is a cell on a continuous term. Spatial scope is recorded on the Analysis, including for searchlight analyses.\n\n\"One signed cell\" means exactly one. The class the reported accuracy is *for* takes the positive cell and the other class takes no cell at all, because absence is the zero weight. Signing both would say the paper reported which way the classifier erred, which a single accuracy does not. Where the test is undirected -- accuracy against chance, with no class named as the target -- every cell is `undirected` instead.\n`classes` is then the only field saying what was told apart, so leaving it empty asserts that the paper never named the classes it decoded, which is rare.", "properties": { + "decoded_variable": { + "$ref": "#/$defs/ExtractedString", + "description": "The variable the classifier or discriminability test predicted." + }, "classes": { "description": "The classes discriminated. These are non-directional: direction, where it exists, is carried by Effect.", "items": { @@ -1074,18 +1078,14 @@ "null" ] }, - "decoded_variable": { - "$ref": "#/$defs/ExtractedString", - "description": "The variable the classifier or discriminability test predicted." - }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "DecodingDetails" - ], - "type": "string" + "performance_metrics": { + "description": "The performance measures reported for this map, one entry each. Papers routinely give several of the same analysis -- accuracy with sensitivity and specificity, or accuracy with AUC -- and each carries its own value and its own reference. Record every one the source reports.", + "items": { + "$ref": "#/$defs/PerformanceMetric" + }, + "type": "array" }, - "generalization": { + "validation_scheme": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -1094,16 +1094,9 @@ "type": "null" } ], - "description": "Train/test split for cross-decoding, when the analysis generalized across sets." - }, - "performance_metrics": { - "description": "The performance measures reported for this map, one entry each. Papers routinely give several of the same analysis -- accuracy with sensitivity and specificity, or accuracy with AUC -- and each carries its own value and its own reference. Record every one the source reports.", - "items": { - "$ref": "#/$defs/PerformanceMetric" - }, - "type": "array" + "description": "Cross-validation or resampling scheme, such as leave-one-run-out." }, - "validation_scheme": { + "generalization": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -1112,7 +1105,14 @@ "type": "null" } ], - "description": "Cross-validation or resampling scheme, such as leave-one-run-out." + "description": "Train/test split for cross-decoding, when the analysis generalized across sets." + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "DecodingDetails" + ], + "type": "string" } }, "required": [ @@ -1157,6 +1157,14 @@ "additionalProperties": false, "description": "The scanner, amplifier, or recording system an acquisition was made on, split into the two parts a source reliably reports. Either part can be missing: papers name a model with no manufacturer as often as the reverse.\nOne record is one physical machine, referenced by every acquisition made on it: a study that collected functional, structural and diffusion scans in one session has three Acquisitions and one Device. Two records mean the study says it used two machines -- a second site, or a rescan on different hardware -- which is a claim about the data and not a way of recording that two sequences differed.", "properties": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, "local_id": { "description": "Stable identifier used by Acquisition foreign-key references.", "type": "string" @@ -1182,14 +1190,6 @@ } ], "description": "Model or product name of the device, as reported, such as Prisma, Signa, Achieva, or actiCHamp." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] } }, "required": [ @@ -1213,7 +1213,7 @@ "additionalProperties": false, "description": "A scalp EEG acquisition. Its own attributes are the ones that materially distinguish recordings used in source-localized coordinate or image-based meta-analyses.", "properties": { - "acquisition_duration_seconds": { + "sampling_frequency_hz": { "anyOf": [ { "$ref": "#/$defs/ExtractedNumber" @@ -1222,46 +1222,31 @@ "type": "null" } ], - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." - }, - "acquisition_type": { - "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", - "enum": [ - "EEG" - ], - "type": "string" + "description": "Sampling frequency of the recording in hertz." }, - "brain_coverage": { + "eeg_reference": { "anyOf": [ { - "$ref": "#/$defs/ExtractedBrainCoverage" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", - "type": [ - "string", - "null" - ], - "x-reference": "Device" + "description": "Reference scheme and location used for the raw recording." }, - "eeg_channel_count": { + "eeg_placement_scheme": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Number of simultaneously recorded EEG channels." + "description": "Electrode placement system, such as the 10-20 or 10-10 system." }, - "eeg_placement_scheme": { + "recording_type": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -1270,55 +1255,70 @@ "type": "null" } ], - "description": "Electrode placement system, such as the 10-20 or 10-10 system." + "description": "Recording organization, such as continuous, discontinuous, or epoched." }, - "eeg_reference": { + "eeg_channel_count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Reference scheme and location used for the raw recording." + "description": "Number of simultaneously recorded EEG channels." + }, + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] }, "local_id": { "type": "string" }, + "acquisition_type": { + "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", + "enum": [ + "EEG" + ], + "type": "string" + }, "modality": { "$ref": "#/$defs/ExtractedModality", "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." }, - "recording_type": { + "acquisition_duration_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Recording organization, such as continuous, discontinuous, or epoched." + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." }, - "sampling_frequency_hz": { + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "type": [ + "string", + "null" + ], + "x-reference": "Device" + }, + "brain_coverage": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedBrainCoverage" }, { "type": "null" } ], - "description": "Sampling frequency of the recording in hertz." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -1401,6 +1401,9 @@ "additionalProperties": false, "description": "Evidence associated with an extracted value.", "properties": { + "status": { + "$ref": "#/$defs/EvidenceStatus" + }, "sets": { "items": { "$ref": "#/$defs/EvidenceSet" @@ -1410,9 +1413,6 @@ "null" ] }, - "status": { - "$ref": "#/$defs/EvidenceStatus" - }, "unlocated_quotes": { "description": "How many quotes were offered for this value that no locator could place in the source text. Set by `build` and omitted where none were, so its presence is the claim.\nIt exists because `status: not_found` collapsed two opposite faults. A value with no quote at all is a recall failure -- the pass never proposed support. A value whose quote was rejected is a fidelity failure -- support was proposed and `spans.resolve` could not match it character-for-character. The two have opposite fixes, and over 1,817 records 32,500 fields carried `value_source: reported` with `not_found` and no way to tell which of the two had happened, so the warning that fires on 98.6% of papers could not be acted on.\nThe same argument `EvidenceSource` was added for, one level up: a distinction the pipeline already knows and the record did not carry.", "minimum": 1, @@ -1472,16 +1472,16 @@ "additionalProperties": false, "description": "An exact source-text span used as evidence for an extracted value.", "properties": { - "end_char": { - "minimum": 0, - "type": "integer" + "text": { + "type": "string" }, "start_char": { "minimum": 0, "type": "integer" }, - "text": { - "type": "string" + "end_char": { + "minimum": 0, + "type": "integer" } }, "required": [ @@ -1536,23 +1536,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is Allocation, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -1576,6 +1562,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -1589,43 +1589,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is AnalysisOutcome.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/AnalysisOutcome" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/AnalysisOutcome" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -1639,23 +1639,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is AnalysisType, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -1679,24 +1665,6 @@ "type": "null" } ] - } - }, - "required": [ - "extraction_status", - "evidence" - ], - "title": "ExtractedAnalysisType", - "type": "object" - }, - "ExtractedAnchorKind": { - "additionalProperties": false, - "description": "An ExtractedValue whose value is AnchorKind, or the source's own wording when the vocabulary does not cover it.", - "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, - "extraction_status": { - "$ref": "#/$defs/ExtractionStatus" }, "unreported_reason": { "anyOf": [ @@ -1709,6 +1677,24 @@ ], "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." }, + "evidence": { + "$ref": "#/$defs/Evidence" + } + }, + "required": [ + "extraction_status", + "evidence" + ], + "title": "ExtractedAnalysisType", + "type": "object" + }, + "ExtractedAnchorKind": { + "additionalProperties": false, + "description": "An ExtractedValue whose value is AnchorKind, or the source's own wording when the vocabulary does not cover it.", + "properties": { + "extraction_status": { + "$ref": "#/$defs/ExtractionStatus" + }, "value": { "anyOf": [ { @@ -1732,6 +1718,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -1745,23 +1745,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is ArmKind, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -1785,6 +1771,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -1798,23 +1798,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is AssignmentStructure, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -1838,6 +1824,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -1851,23 +1851,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is Blinding, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -1891,6 +1877,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -1904,23 +1904,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is true or false.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "type": [ @@ -1937,6 +1923,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -1950,23 +1950,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is a list of booleans, under one evidence record.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "items": { @@ -1986,6 +1972,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -1999,43 +1999,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is BrainCoverage.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/BrainCoverage" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/BrainCoverage" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2049,23 +2049,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is ConditionKind, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2089,6 +2075,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2102,23 +2102,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is ConjunctionImplementation, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2142,6 +2128,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2155,22 +2155,8 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is ConnectivityMethod, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, - "extraction_status": { - "$ref": "#/$defs/ExtractionStatus" - }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "extraction_status": { + "$ref": "#/$defs/ExtractionStatus" }, "value": { "anyOf": [ @@ -2195,6 +2181,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2208,23 +2208,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is CoordinateRole, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2248,6 +2234,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2261,23 +2261,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is DecompositionMethod, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2301,6 +2287,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2314,23 +2314,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is DesignType, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2354,6 +2340,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2367,43 +2367,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is Direction.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/Direction" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/Direction" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2417,43 +2417,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is EffectKind.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/EffectKind" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/EffectKind" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2467,43 +2467,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is EffectPath.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/EffectPath" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/EffectPath" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2517,23 +2517,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is FunctionalForm, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2557,6 +2543,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2570,23 +2570,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is InferenceTarget, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2610,6 +2596,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2623,23 +2623,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is a whole number.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "type": [ @@ -2647,15 +2633,29 @@ "null" ] }, - "value_source": { + "value_source": { + "anyOf": [ + { + "$ref": "#/$defs/ValueSource" + }, + { + "type": "null" + } + ] + }, + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2669,23 +2669,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is a list of whole numbers, under one evidence record.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "items": { @@ -2705,6 +2691,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2718,23 +2718,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is MapQuantity, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2758,6 +2744,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2771,23 +2771,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is MeasureFamily, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2811,6 +2797,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2824,23 +2824,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is MeasureType, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2864,6 +2850,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2877,23 +2877,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is Modality, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2917,6 +2903,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2930,23 +2930,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is ModelFamily, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -2970,6 +2956,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -2983,23 +2983,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is NotStructurableReason, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3023,6 +3009,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3036,43 +3036,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is NullHypothesis.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/NullHypothesis" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/NullHypothesis" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3086,23 +3086,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is numeric, including fractional values.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "type": [ @@ -3110,15 +3096,29 @@ "null" ] }, - "value_source": { + "value_source": { + "anyOf": [ + { + "$ref": "#/$defs/ValueSource" + }, + { + "type": "null" + } + ] + }, + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3132,23 +3132,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is a list of numbers, under one evidence record.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "items": { @@ -3168,6 +3154,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3181,43 +3181,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is PerformanceRelation.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/PerformanceRelation" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/PerformanceRelation" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3231,43 +3231,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is PolaritySemantics.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/PolaritySemantics" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/PolaritySemantics" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3281,23 +3281,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is Prespecification, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3321,6 +3307,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3334,23 +3334,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is RegionDefinition, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3374,6 +3360,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3387,23 +3387,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is RegionType, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3427,6 +3413,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3440,23 +3440,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is a list of ResponseModality, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "items": { @@ -3483,6 +3469,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3496,23 +3496,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is ResultKind, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3536,6 +3522,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3549,43 +3549,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is ResultReporting.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/ResultReporting" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/ResultReporting" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3599,43 +3599,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is SampleSource.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/SampleSource" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/SampleSource" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3649,23 +3649,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is a list of SecondBlock, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "items": { @@ -3692,6 +3678,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3705,23 +3705,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is SpatialScope, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3745,6 +3731,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3758,23 +3758,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is SpatialUnit, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3798,6 +3784,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3811,23 +3811,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is Species, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3851,6 +3837,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3864,23 +3864,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is StatisticFamily, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -3904,6 +3890,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3917,23 +3917,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is a list of StimulusModality, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "items": { @@ -3960,6 +3946,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -3973,23 +3973,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is text.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "type": [ @@ -4006,6 +3992,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -4019,23 +4019,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is a list of strings, under one evidence record.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported.", "items": { @@ -4055,6 +4041,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -4067,24 +4067,10 @@ "ExtractedTablePurpose": { "additionalProperties": false, "description": "An ExtractedValue whose value is TablePurpose, or the source's own wording when the vocabulary does not cover it.", - "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, + "properties": { "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -4108,6 +4094,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -4121,23 +4121,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is TaskVariantAxis, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -4161,6 +4147,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -4174,43 +4174,43 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is TermType.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/TermType" }, { "type": "null" } ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." }, - "value": { + "value_source": { "anyOf": [ { - "$ref": "#/$defs/TermType" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -4224,23 +4224,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is TimepointRelation, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -4264,6 +4250,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -4277,35 +4277,35 @@ "additionalProperties": false, "description": "A schema attribute explicitly examined during extraction, including its value, source, and supporting evidence.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { + "value": { + "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + }, + "value_source": { "anyOf": [ { - "$ref": "#/$defs/UnreportedReason" + "$ref": "#/$defs/ValueSource" }, { "type": "null" } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, - "value": { - "description": "Extracted or generated value. Populate when extraction_status is extracted; omit when it is not_reported." + ] }, - "value_source": { + "unreported_reason": { "anyOf": [ { - "$ref": "#/$defs/ValueSource" + "$ref": "#/$defs/UnreportedReason" }, { "type": "null" } - ] + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -4319,23 +4319,9 @@ "additionalProperties": false, "description": "An ExtractedValue whose value is VariationLevel, or the source's own wording when the vocabulary does not cover it.", "properties": { - "evidence": { - "$ref": "#/$defs/Evidence" - }, "extraction_status": { "$ref": "#/$defs/ExtractionStatus" }, - "unreported_reason": { - "anyOf": [ - { - "$ref": "#/$defs/UnreportedReason" - }, - { - "type": "null" - } - ], - "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." - }, "value": { "anyOf": [ { @@ -4359,6 +4345,20 @@ "type": "null" } ] + }, + "unreported_reason": { + "anyOf": [ + { + "$ref": "#/$defs/UnreportedReason" + }, + { + "type": "null" + } + ], + "description": "Why there is no value, where the reason is not the plain one. Populate when extraction_status is not_reported and one of the four applies; omit when it is extracted, where there is nothing to explain. Absent is the ordinary case and claims nothing beyond the status: the attribute was examined and the source carries no value. Reading absence as \"no pass looked\" would contradict what `not_reported` already asserts, so nothing should." + }, + "evidence": { + "$ref": "#/$defs/Evidence" } }, "required": [ @@ -4372,14 +4372,6 @@ "additionalProperties": false, "description": "Pipeline provenance stamped onto the extraction record at generation time.", "properties": { - "extraction_date": { - "description": "ISO-8601 date of extraction.", - "format": "date", - "type": [ - "string", - "null" - ] - }, "extractor_model": { "description": "Model identifier used for extraction, e.g. claude-sonnet-4-6.", "type": "string" @@ -4388,13 +4380,18 @@ "description": "Version string of the extraction prompt or pipeline.", "type": "string" }, - "paper_sections": { - "description": "Complete deterministic index of all paper sections and subsections, including character spans.", - "items": { - "$ref": "#/$defs/PaperSection" - }, + "source_text_hash": { + "description": "SHA-256 of the paper text processed, for reproducibility checking.", "type": [ - "array", + "string", + "null" + ] + }, + "extraction_date": { + "description": "ISO-8601 date of extraction.", + "format": "date", + "type": [ + "string", "null" ] }, @@ -4405,10 +4402,13 @@ "null" ] }, - "source_text_hash": { - "description": "SHA-256 of the paper text processed, for reproducibility checking.", + "paper_sections": { + "description": "Complete deterministic index of all paper sections and subsections, including character spans.", + "items": { + "$ref": "#/$defs/PaperSection" + }, "type": [ - "string", + "array", "null" ] } @@ -4429,75 +4429,20 @@ "title": "ExtractionStatus", "type": "string" }, - "FNIRS": { - "additionalProperties": false, - "description": "An fNIRS acquisition. Its own attributes are the ones affecting cortical sensitivity, spatial sampling, or comparability, and so can support study filtering or moderator analyses.", - "properties": { - "acquisition_duration_seconds": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedNumber" - }, - { - "type": "null" - } - ], - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." - }, - "acquisition_type": { - "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", - "enum": [ - "FNIRS" - ], - "type": "string" - }, - "brain_coverage": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedBrainCoverage" - }, - { - "type": "null" - } - ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", - "type": [ - "string", - "null" - ], - "x-reference": "Device" - }, - "local_id": { - "type": "string" - }, - "modality": { - "$ref": "#/$defs/ExtractedModality", - "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." - }, - "nirs_channel_count": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedInteger" - }, - { - "type": "null" - } - ], - "description": "Total number of NIRS channels, including short-separation channels." - }, - "nirs_placement_scheme": { + "FNIRS": { + "additionalProperties": false, + "description": "An fNIRS acquisition. Its own attributes are the ones affecting cortical sensitivity, spatial sampling, or comparability, and so can support study filtering or moderator analyses.", + "properties": { + "sampling_frequency_hz": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Standard placement system or listed optode positions, such as 10-20, Cz, or Pz." + "description": "Sampling frequency of the fNIRS data in hertz." }, "nirs_system_type": { "anyOf": [ @@ -4510,27 +4455,27 @@ ], "description": "Instrument principle, such as continuous-wave, frequency-domain, or time-domain NIRS." }, - "recorded_signal_types": { + "nirs_channel_count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Raw or derived channel signal types, such as continuous-wave amplitude, optical density, oxygenated hemoglobin, or deoxygenated hemoglobin." + "description": "Total number of NIRS channels, including short-separation channels." }, - "sampling_frequency_hz": { + "wavelengths_nm": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedNumberList" }, { "type": "null" } ], - "description": "Sampling frequency of the fNIRS data in hertz." + "description": "Nominal emitted wavelengths in nanometers." }, "short_channel_count": { "anyOf": [ @@ -4554,6 +4499,28 @@ ], "description": "Reported source-detector separations in millimeters." }, + "nirs_placement_scheme": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedStringList" + }, + { + "type": "null" + } + ], + "description": "Standard placement system or listed optode positions, such as 10-20, Cz, or Pz." + }, + "recorded_signal_types": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedStringList" + }, + { + "type": "null" + } + ], + "description": "Raw or derived channel signal types, such as continuous-wave amplitude, optical density, oxygenated hemoglobin, or deoxygenated hemoglobin." + }, "stable_id": { "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.", "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", @@ -4562,16 +4529,49 @@ "null" ] }, - "wavelengths_nm": { + "local_id": { + "type": "string" + }, + "acquisition_type": { + "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", + "enum": [ + "FNIRS" + ], + "type": "string" + }, + "modality": { + "$ref": "#/$defs/ExtractedModality", + "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + }, + "acquisition_duration_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumberList" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Nominal emitted wavelengths in nanometers." + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." + }, + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "type": [ + "string", + "null" + ], + "x-reference": "Device" + }, + "brain_coverage": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedBrainCoverage" + }, + { + "type": "null" + } + ], + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -4586,16 +4586,20 @@ "additionalProperties": false, "description": "One level of a categorical model term and the entities that carry it. The ModelTerm records which design cell a cohort or condition occupies because that placement belongs to the design, not to a contrast drawn from it.\nThe five entity slots are what make the entity view of a contrast recoverable: an Effect's cells name levels, and joining those levels here says which cohorts, conditions, occasions, arms or regions were compared. A factor normally ranges over one of the five -- conditions for a task factor, cohorts for a participant factor, occasions for a time factor, arms for a treatment factor, regions for one comparing places in the brain -- so the others stay empty; a level realized by a crossing, such as a drug given at follow-up, fills more than one.\nA factor whose levels are not entities of the study at all -- hemispheres, tasks, frequency bands, acquisition sessions -- leaves all five empty and is carried by `level` alone. Such a factor is complete, and takes `Cell`s like any other.", "properties": { - "arms": { - "description": "The arms carrying this level, by identifier. A factor whose levels are drug and placebo, or active and sham, ranges over arms. In a parallel trial each level is both an arm and a cohort and both slots are filled; in a crossover one cohort receives every arm, so `arms` alone distinguishes the levels and filling only `groups` loses the allocation.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" + "level": { + "$ref": "#/$defs/ExtractedString", + "description": "The level's label, as the source words it. Unique within its term: a level appearing twice is one level recorded twice." + }, + "order": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedInteger" + }, + { + "type": "null" + } ], - "x-reference": "Arm" + "description": "Position of this level in the factor's sequence, counting from 1, for a factor whose levels are ordered. It carries an ordering `Effect.cells` cannot: a contrast signs only its extremes and omits what lies between, so without this 1-back, 2-back and 3-back are an unordered set.\nPopulate it whenever the levels have an order the source states or plainly implies -- a dose series, a load or difficulty series, an ordinal severity grading. Leave it unset for a nominal factor, where imposing an order would assert a structure the design does not have. Timepoint.order is the same device for occasions." }, "conditions": { "description": "The conditions carrying this level, by identifier.", @@ -4619,23 +4623,19 @@ ], "x-reference": "Group" }, - "level": { - "$ref": "#/$defs/ExtractedString", - "description": "The level's label, as the source words it. Unique within its term: a level appearing twice is one level recorded twice." - }, - "order": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedInteger" - }, - { - "type": "null" - } + "timepoints": { + "description": "The occasions carrying this level, by identifier. A factor whose levels are baseline, week 6 or follow-up ranges over occasions and fills this: left empty, the level stands on its label alone and no query can tell which scan preceded the intervention.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" ], - "description": "Position of this level in the factor's sequence, counting from 1, for a factor whose levels are ordered. It carries an ordering `Effect.cells` cannot: a contrast signs only its extremes and omits what lies between, so without this 1-back, 2-back and 3-back are an unordered set.\nPopulate it whenever the levels have an order the source states or plainly implies -- a dose series, a load or difficulty series, an ordinal severity grading. Leave it unset for a nominal factor, where imposing an order would assert a structure the design does not have. Timepoint.order is the same device for occasions." + "x-reference": "Timepoint" }, - "regions": { - "description": "The brain regions carrying this level, by identifier, for a factor comparing places. A three-region factor is one `ModelTerm` with three levels, one region on each.", + "arms": { + "description": "The arms carrying this level, by identifier. A factor whose levels are drug and placebo, or active and sham, ranges over arms. In a parallel trial each level is both an arm and a cohort and both slots are filled; in a crossover one cohort receives every arm, so `arms` alone distinguishes the levels and filling only `groups` loses the allocation.", "items": { "type": "string" }, @@ -4643,10 +4643,10 @@ "array", "null" ], - "x-reference": "Region" + "x-reference": "Arm" }, - "timepoints": { - "description": "The occasions carrying this level, by identifier. A factor whose levels are baseline, week 6 or follow-up ranges over occasions and fills this: left empty, the level stands on its label alone and no query can tell which scan preceded the intervention.", + "regions": { + "description": "The brain regions carrying this level, by identifier, for a factor comparing places. A three-region factor is one `ModelTerm` with three levels, one region on each.", "items": { "type": "string" }, @@ -4654,7 +4654,7 @@ "array", "null" ], - "x-reference": "Timepoint" + "x-reference": "Region" } }, "required": [ @@ -4678,114 +4678,122 @@ "additionalProperties": false, "description": "An aggregate participant group and its reported demographics.", "properties": { - "acquired_count": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "type": "string" + }, + "name": { + "$ref": "#/$defs/ExtractedString", + "description": "Study-local group label, for example healthy controls or patients with Parkinson disease." + }, + "description": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Number for whom data were acquired or who were scanned." + "description": "Narrative definition of the group and its role in the study." }, - "age_maximum": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedNumber" - }, - { - "type": "null" - } + "species": { + "$ref": "#/$defs/ExtractedSpecies", + "description": "The species of this cohort." + }, + "arm": { + "description": "The arm this cohort was allocated to. Set it whenever a cohort was allocated to one, which is the parallel-group case: every participant of this cohort received this arm and no other. A crossover study allocates nobody to one arm, so its cohorts leave this empty and an arm reaches an analysis only as a level of a model term, through `FactorLevel.arms`. Distinct from `medication_status`, which is the on/off state during acquisition rather than the allocation.", + "type": [ + "string", + "null" ], - "description": "Oldest age reported for the group." + "x-reference": "Arm" }, - "age_mean": { + "approached_count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Mean participant age in the unit given by age_unit." + "description": "Number of prospective participants approached for this group." }, - "age_median": { + "consented_count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Median participant age for the group." + "description": "Number who consented or assented to participate." }, - "age_minimum": { + "enrolled_count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Youngest age reported for the group." + "description": "Number enrolled after screening and before acquisition." }, - "age_standard_deviation": { + "acquired_count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Standard deviation of participant age in the unit given by age_unit." + "description": "Number for whom data were acquired or who were scanned." }, - "age_unit": { + "excluded_count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Unit used for age summaries, normally years but potentially months, weeks, or gestational weeks." + "description": "Number excluded after consent, enrollment, acquisition, and quality control." }, - "age_unit_normalized": { + "inclusion_criteria": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "`age_unit` folded onto years / months / weeks / gestational_weeks / UNKNOWN by `pondie.normalization.age_unit`, never extracted. The source's own wording stays in `age_unit`." + "description": "Eligibility requirements that a participant had to satisfy for membership in this group." }, - "approached_count": { + "exclusion_criteria": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "Number of prospective participants approached for this group." - }, - "arm": { - "description": "The arm this cohort was allocated to. Set it whenever a cohort was allocated to one, which is the parallel-group case: every participant of this cohort received this arm and no other. A crossover study allocates nobody to one arm, so its cohorts leave this empty and an arm reaches an analysis only as a level of a model term, through `FactorLevel.arms`. Distinct from `medication_status`, which is the on/off state during acquisition rather than the allocation.", - "type": [ - "string", - "null" - ], - "x-reference": "Arm" + "description": "Screening, imaging-safety, clinical, behavioral, or quality-control criteria causing exclusion." }, - "clinical_characteristics": { + "recruitment_method": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -4794,105 +4802,94 @@ "type": "null" } ], - "description": "Disease duration, severity, treatment status, or other clinically relevant summaries." + "description": "How, where, and from what setting participants were recruited." }, - "consented_count": { + "recruitment_dates": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Number who consented or assented to participate." + "description": "Free-text recruitment period when the source does not support a precise normalized date range; examples include January 2021 to June 2022, 2018-2020, or autumn 2023." }, - "description": { + "age_mean": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, - { - "type": "null" - } - ], - "description": "Narrative definition of the group and its role in the study." - }, - "diagnostic_instrument": { - "description": "The study assessment that established this group's defining condition -- the `medical_condition` entries, or for a healthy group its confirmed absence. Naming one here claims it classified this cohort, which is narrower than having been administered to it: an interview screening a comorbidity the group is not defined by, or grading the severity of a diagnosis made elsewhere, is recorded in `assessments` and pointed at from nowhere. Groups defined by enrollment or history rather than by an assessment -- a treatment programme's patients, self-reported users -- name nothing, as do sources reporting a diagnostic system but never the instrument applying it. Every entry must also appear in the study's `assessments`.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" + { + "type": "null" + } ], - "x-reference": "Assessment" + "description": "Mean participant age in the unit given by age_unit." }, - "diagnostic_system": { + "age_standard_deviation": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Classification system and edition under which this group's `medical_condition` was diagnosed, such as DSM-5 or ICD-10. An edition that appears only inside an instrument's title does not establish it: \"SCID for DSM-IV Axis II Disorders\", administered to screen comorbidities, says nothing about the system used to diagnose the condition that defines the group." + "description": "Standard deviation of participant age in the unit given by age_unit." }, - "education_summary": { + "age_minimum": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Aggregate education level and the measurement or coding system used." + "description": "Youngest age reported for the group." }, - "enrolled_count": { + "age_maximum": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Number enrolled after screening and before acquisition." - }, - "ethnicity_distribution": { - "description": "Aggregate ethnic distribution using the study-reported category labels.", - "items": { - "$ref": "#/$defs/CategoryDistribution" - }, - "type": [ - "array", - "null" - ] + "description": "Oldest age reported for the group." }, - "excluded_count": { + "age_median": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Number excluded after consent, enrollment, acquisition, and quality control." + "description": "Median participant age for the group." }, - "exclusion_criteria": { + "age_unit": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Screening, imaging-safety, clinical, behavioral, or quality-control criteria causing exclusion." + "description": "Unit used for age summaries, normally years but potentially months, weeks, or gestational weeks." + }, + "sex_distribution": { + "description": "Aggregate phenotypic sex distribution. Use one entry per reported category, with the study's category label and a count and/or percentage. A cohort described wholly as one sex reports its distribution without ever printing a breakdown: \"22 male smokers\", \"young women (n = 8)\" and \"ten male patients\" are each one entry whose count is the whole group, plus a second entry for the absent sex with a count of zero. Read a demographics table row (\"N female | 5\") exactly as you would read the same fact in a sentence.", + "items": { + "$ref": "#/$defs/CategoryDistribution" + }, + "type": [ + "array", + "null" + ] }, "gender_distribution": { "description": "Aggregate self-described gender distribution, kept separate from phenotypic sex. Use one entry per reported category.", @@ -4914,7 +4911,27 @@ "null" ] }, - "inclusion_criteria": { + "race_distribution": { + "description": "Aggregate racial distribution using the study-reported category labels.", + "items": { + "$ref": "#/$defs/CategoryDistribution" + }, + "type": [ + "array", + "null" + ] + }, + "ethnicity_distribution": { + "description": "Aggregate ethnic distribution using the study-reported category labels.", + "items": { + "$ref": "#/$defs/CategoryDistribution" + }, + "type": [ + "array", + "null" + ] + }, + "population_characteristics": { "anyOf": [ { "$ref": "#/$defs/ExtractedStringList" @@ -4923,34 +4940,42 @@ "type": "null" } ], - "description": "Eligibility requirements that a participant had to satisfy for membership in this group." + "description": "How this cohort **deviates from the typical**, where no other field covers it: habitual exposure (heavy caffeine use, social drinkers, smoking status), training or expertise (athletes, musicians, bilinguals, gamers), occupation or social role (veterans, undergraduates, shift workers), lifestyle (sedentary, vegetarian, meditation practitioners), and body habitus where it is atypical (obese, overweight, underweight).\nDeviation is the test, and it is what makes this field worth querying. A study selects its cohort for something; that something is what belongs here. The unremarkable does not: \"normal weight\", \"healthy weight\", \"typically developing\", \"right-handed\", \"no hearing loss\" and an ordinary age range describe a cohort that was not selected for anything, and recording them fills the field with the absence of information. If every plausible cohort could carry the value, leave it out.\n**Every other Group slot takes priority.** Nothing here that belongs in `age_mean`, `age_minimum`, `age_maximum`, `age_median`, `sex_distribution`, `gender_distribution`, `handedness_distribution`, `race_distribution`, `ethnicity_distribution`, `education_summary`, `socioeconomic_status_summary`, `medications`, `medication_status`, `recruitment_method`, `species`, `inclusion_criteria`, `exclusion_criteria` or `medical_condition`. \"All Caucasian\" is `race_distribution`; \"children aged 8-11\" is `age_minimum` and `age_maximum`; \"five participants were female\" is `sex_distribution`.\nNot a diagnosis either. Where a trait is also the cohort's condition -- alcohol dependence, obesity as a diagnosed disease -- `medical_condition` takes it alone, which is where a clinician would look.\nWhat was screened out is not a characteristic. \"Excluding caffeine\" in an exclusion list says nothing about caffeine use in the cohort." }, - "is_healthy": { + "medical_condition": { "anyOf": [ { - "$ref": "#/$defs/ExtractedBoolean" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "Whether this cohort is free of medical, neurological and psychiatric conditions. Derived from `medical_condition` by `pondie.normalization.is_healthy`, never extracted, so it cannot disagree with the field it summarises. Unset where `medical_condition` was never read: that is an unread cohort, not a healthy one." - }, - "local_id": { - "type": "string" + "description": "Medical, neurological, psychiatric, developmental, or physiological condition(s) defining or characterizing the group. List the primary diagnosis and all comorbidities." }, - "medical_condition": { + "diagnostic_system": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Medical, neurological, psychiatric, developmental, or physiological condition(s) defining or characterizing the group. List the primary diagnosis and all comorbidities." + "description": "Classification system and edition under which this group's `medical_condition` was diagnosed, such as DSM-5 or ICD-10. An edition that appears only inside an instrument's title does not establish it: \"SCID for DSM-IV Axis II Disorders\", administered to screen comorbidities, says nothing about the system used to diagnose the condition that defines the group." }, - "medication_status": { + "diagnostic_instrument": { + "description": "The study assessment that established this group's defining condition -- the `medical_condition` entries, or for a healthy group its confirmed absence. Naming one here claims it classified this cohort, which is narrower than having been administered to it: an interview screening a comorbidity the group is not defined by, or grading the severity of a diagnosis made elsewhere, is recorded in `assessments` and pointed at from nowhere. Groups defined by enrollment or history rather than by an assessment -- a treatment programme's patients, self-reported users -- name nothing, as do sources reporting a diagnostic system but never the instrument applying it. Every entry must also appear in the study's `assessments`.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Assessment" + }, + "clinical_characteristics": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -4959,7 +4984,7 @@ "type": "null" } ], - "description": "Whether the cohort was medicated, unmedicated, drug-naive, or withdrawn, and the on/off medication state during acquisition or testing. The state, not the agents: which drugs they were taking is `medications`." + "description": "Disease duration, severity, treatment status, or other clinically relevant summaries." }, "medications": { "anyOf": [ @@ -4972,43 +4997,29 @@ ], "description": "The drugs or other agents this cohort was taking, as the source names them, one agent per entry. Names only." }, - "name": { - "$ref": "#/$defs/ExtractedString", - "description": "Study-local group label, for example healthy controls or patients with Parkinson disease." - }, - "other_characteristics": { + "medication_status": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Cohort traits moved out of `population_characteristics` because every plausible cohort could carry them. Written by `pondie.normalization.population_characteristics`, never extracted." + "description": "Whether the cohort was medicated, unmedicated, drug-naive, or withdrawn, and the on/off medication state during acquisition or testing. The state, not the agents: which drugs they were taking is `medications`." }, - "population_characteristics": { + "education_summary": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "How this cohort **deviates from the typical**, where no other field covers it: habitual exposure (heavy caffeine use, social drinkers, smoking status), training or expertise (athletes, musicians, bilinguals, gamers), occupation or social role (veterans, undergraduates, shift workers), lifestyle (sedentary, vegetarian, meditation practitioners), and body habitus where it is atypical (obese, overweight, underweight).\nDeviation is the test, and it is what makes this field worth querying. A study selects its cohort for something; that something is what belongs here. The unremarkable does not: \"normal weight\", \"healthy weight\", \"typically developing\", \"right-handed\", \"no hearing loss\" and an ordinary age range describe a cohort that was not selected for anything, and recording them fills the field with the absence of information. If every plausible cohort could carry the value, leave it out.\n**Every other Group slot takes priority.** Nothing here that belongs in `age_mean`, `age_minimum`, `age_maximum`, `age_median`, `sex_distribution`, `gender_distribution`, `handedness_distribution`, `race_distribution`, `ethnicity_distribution`, `education_summary`, `socioeconomic_status_summary`, `medications`, `medication_status`, `recruitment_method`, `species`, `inclusion_criteria`, `exclusion_criteria` or `medical_condition`. \"All Caucasian\" is `race_distribution`; \"children aged 8-11\" is `age_minimum` and `age_maximum`; \"five participants were female\" is `sex_distribution`.\nNot a diagnosis either. Where a trait is also the cohort's condition -- alcohol dependence, obesity as a diagnosed disease -- `medical_condition` takes it alone, which is where a clinician would look.\nWhat was screened out is not a characteristic. \"Excluding caffeine\" in an exclusion list says nothing about caffeine use in the cohort." - }, - "race_distribution": { - "description": "Aggregate racial distribution using the study-reported category labels.", - "items": { - "$ref": "#/$defs/CategoryDistribution" - }, - "type": [ - "array", - "null" - ] + "description": "Aggregate education level and the measurement or coding system used." }, - "recruitment_dates": { + "socioeconomic_status_summary": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -5017,9 +5028,20 @@ "type": "null" } ], - "description": "Free-text recruitment period when the source does not support a precise normalized date range; examples include January 2021 to June 2022, 2018-2020, or autumn 2023." + "description": "Aggregate socioeconomic status and the instrument or proxy used to measure it." }, - "recruitment_method": { + "sample_source": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedSampleSource" + }, + { + "type": "null" + } + ], + "description": "Where this group's participants came from. `recruited` when they were enrolled for this study and no other source is stated; `public_dataset` when they were drawn from a named public or consortium dataset; `previously_reported` when the same or an overlapping sample was already reported in another publication.\nOn the group and not the study, because overlap is routinely partial: a study may scan a fresh patient group against controls taken from an existing dataset, and a study-level flag cannot say which. `Study.external_datasets` remains the place for a named dataset the study as a whole used.\nA protocol, preprocessing pipeline or analysis \"described previously\" is a shared METHOD and leaves this `recruited`; so does comparing findings to earlier literature in the discussion. Only a statement that these same people were scanned, studied or reported before counts, and a source that means it normally says so in one sentence.\n`recruited` is an assertion and not the absence of one. A meta-analysis excluding overlapping samples has to distinguish a study that recruited freshly from one nobody checked, and an unfilled field cannot." + }, + "sample_source_reference": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -5028,7 +5050,7 @@ "type": "null" } ], - "description": "How, where, and from what setting participants were recruited." + "description": "The dataset name, or the prior publication as the source cites it: an author-year citation, a bracketed reference number, or a trial registration number, copied as written. Without it the overlap is known but not actionable -- deciding whether two papers are one independent sample needs the identity of the other one." }, "sample_overlap_count": { "anyOf": [ @@ -5041,18 +5063,18 @@ ], "description": "How many of this group were in that prior report, when the source says so. It is the difference between two papers being one sample and their sharing part of one, which is the difference between dropping a study and down-weighting it." }, - "sample_source": { + "is_healthy": { "anyOf": [ { - "$ref": "#/$defs/ExtractedSampleSource" + "$ref": "#/$defs/ExtractedBoolean" }, { "type": "null" } ], - "description": "Where this group's participants came from. `recruited` when they were enrolled for this study and no other source is stated; `public_dataset` when they were drawn from a named public or consortium dataset; `previously_reported` when the same or an overlapping sample was already reported in another publication.\nOn the group and not the study, because overlap is routinely partial: a study may scan a fresh patient group against controls taken from an existing dataset, and a study-level flag cannot say which. `Study.external_datasets` remains the place for a named dataset the study as a whole used.\nA protocol, preprocessing pipeline or analysis \"described previously\" is a shared METHOD and leaves this `recruited`; so does comparing findings to earlier literature in the discussion. Only a statement that these same people were scanned, studied or reported before counts, and a source that means it normally says so in one sentence.\n`recruited` is an assertion and not the absence of one. A meta-analysis excluding overlapping samples has to distinguish a study that recruited freshly from one nobody checked, and an unfilled field cannot." + "description": "Whether this cohort is free of medical, neurological and psychiatric conditions. Derived from `medical_condition` by `pondie.normalization.is_healthy`, never extracted, so it cannot disagree with the field it summarises. Unset where `medical_condition` was never read: that is an unread cohort, not a healthy one." }, - "sample_source_reference": { + "age_unit_normalized": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -5061,40 +5083,18 @@ "type": "null" } ], - "description": "The dataset name, or the prior publication as the source cites it: an author-year citation, a bracketed reference number, or a trial registration number, copied as written. Without it the overlap is known but not actionable -- deciding whether two papers are one independent sample needs the identity of the other one." - }, - "sex_distribution": { - "description": "Aggregate phenotypic sex distribution. Use one entry per reported category, with the study's category label and a count and/or percentage. A cohort described wholly as one sex reports its distribution without ever printing a breakdown: \"22 male smokers\", \"young women (n = 8)\" and \"ten male patients\" are each one entry whose count is the whole group, plus a second entry for the absent sex with a count of zero. Read a demographics table row (\"N female | 5\") exactly as you would read the same fact in a sentence.", - "items": { - "$ref": "#/$defs/CategoryDistribution" - }, - "type": [ - "array", - "null" - ] + "description": "`age_unit` folded onto years / months / weeks / gestational_weeks / UNKNOWN by `pondie.normalization.age_unit`, never extracted. The source's own wording stays in `age_unit`." }, - "socioeconomic_status_summary": { + "other_characteristics": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "Aggregate socioeconomic status and the instrument or proxy used to measure it." - }, - "species": { - "$ref": "#/$defs/ExtractedSpecies", - "description": "The species of this cohort." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "Cohort traits moved out of `population_characteristics` because every plausible cohort could carry them. Written by `pondie.normalization.population_characteristics`, never extracted." } }, "required": [ @@ -5109,99 +5109,107 @@ "additionalProperties": false, "description": "One thresholding and correction procedure. This is the canonical storage home for thresholding, correction, and cluster-inference metadata. The test statistic itself belongs to the tested effect and lives in Effect.statistic.\nOne record may be referenced by several Analyses, so every field must hold for all of them; an analysis thresholded or corrected differently takes its own record. The test is mechanical: two analyses share a record when the threshold, its type, the correction method, the scope corrected over and the alpha are all the same. A paper that states one correction scheme in Methods and applies it throughout has one record.\nInference attaches to the analysis, not to the fit: one model estimated once is often thresholded several ways -- an ROI test Bonferroni-corrected over four regions and a searchlight over the same fit corrected by FDR. See storage-schema-design-notes.md for the reasoning behind this shape.", "properties": { - "alpha_level": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "description": "Stable identifier used by Analysis foreign-key references.", + "type": "string" + }, + "inference_level": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Family-wise, false-discovery, or nominal alpha threshold when reported." + "description": "Level at which inference is made, such as voxel, vertex, cluster, region, network or other." }, - "cluster_extent_threshold": { + "height_threshold_value": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Minimum surviving cluster extent in voxels, vertices, or the unit stated in the source." + "description": "The threshold applied to each element's statistic, before correction and before any cluster-level test. Papers name it for whichever of its two jobs they are describing -- the *voxelwise* (or vertexwise) threshold when elements are tested individually, the *cluster-forming* threshold when surviving elements are grouped into clusters -- and it is one number either way, applied once to the map. Record it once here, whichever word the source uses.\nNot the cluster-level threshold: `clusterwise_threshold_value` is the p or q a whole cluster had to beat, and `cluster_extent_threshold` the size it had to reach. A paper reporting \"p < 0.001 uncorrected, cluster-level p < 0.05 FWE\" states this field and `clusterwise_threshold_value`, not this field twice." }, - "clusterwise_threshold_value": { + "height_threshold_type": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Numeric cluster-level threshold for retaining clusters." - }, - "correction_regions": { - "description": "The regions correction was restricted to -- what `correction_scope: roi` names, as `Analysis.regions` names what `spatial_scope: roi` restricts the search space to. Empty for a whole-brain correction.\nNot the analysis's search space. Naming the correction mask on the analysis instead says the model was fitted within it, which is a different claim about what the paper did.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Region" + "description": "Meaning of height_threshold_value, such as p, q, t, z, or F." }, - "correction_scope": { + "cluster_extent_threshold": { "anyOf": [ { - "$ref": "#/$defs/ExtractedSpatialScope" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Whether the multiple-comparison correction searched the whole acquired volume or was restricted to regions. The correction's own domain, not the analysis's: a whole-brain voxelwise model whose significance was corrected inside an anatomical mask has `Analysis.spatial_scope: whole_brain` and `correction_scope: roi`, and the two differing is the ordinary case rather than a contradiction.\nA domain the enum cannot name is written here as free text. An inference level such as \"cluster level\" is not a scope and belongs in `inference_level`." + "description": "Minimum surviving cluster extent in voxels, vertices, or the unit stated in the source." }, - "height_threshold_type": { + "clusterwise_threshold_value": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Meaning of height_threshold_value, such as p, q, t, z, or F." + "description": "Numeric cluster-level threshold for retaining clusters." }, - "height_threshold_value": { + "multiple_comparison_method": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "The threshold applied to each element's statistic, before correction and before any cluster-level test. Papers name it for whichever of its two jobs they are describing -- the *voxelwise* (or vertexwise) threshold when elements are tested individually, the *cluster-forming* threshold when surviving elements are grouped into clusters -- and it is one number either way, applied once to the map. Record it once here, whichever word the source uses.\nNot the cluster-level threshold: `clusterwise_threshold_value` is the p or q a whole cluster had to beat, and `cluster_extent_threshold` the size it had to reach. A paper reporting \"p < 0.001 uncorrected, cluster-level p < 0.05 FWE\" states this field and `clusterwise_threshold_value`, not this field twice." + "description": "Correction method, such as FWE, FDR, Bonferroni, permutation, Monte Carlo, or none." }, - "inference_level": { + "correction_scope": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedSpatialScope" }, { "type": "null" } ], - "description": "Level at which inference is made, such as voxel, vertex, cluster, region, network or other." + "description": "Whether the multiple-comparison correction searched the whole acquired volume or was restricted to regions. The correction's own domain, not the analysis's: a whole-brain voxelwise model whose significance was corrected inside an anatomical mask has `Analysis.spatial_scope: whole_brain` and `correction_scope: roi`, and the two differing is the ordinary case rather than a contradiction.\nA domain the enum cannot name is written here as free text. An inference level such as \"cluster level\" is not a scope and belongs in `inference_level`." }, - "local_id": { - "description": "Stable identifier used by Analysis foreign-key references.", - "type": "string" + "correction_regions": { + "description": "The regions correction was restricted to -- what `correction_scope: roi` names, as `Analysis.regions` names what `spatial_scope: roi` restricts the search space to. Empty for a whole-brain correction.\nNot the analysis's search space. Naming the correction mask on the analysis instead says the model was fitted within it, which is a different claim about what the paper did.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Region" }, - "multiple_comparison_method": { + "search_volume": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -5210,7 +5218,7 @@ "type": "null" } ], - "description": "Correction method, such as FWE, FDR, Bonferroni, permutation, Monte Carlo, or none." + "description": "Reported search volume, mask size, or number of vertices or voxels used for inference." }, "neighborhood_definition": { "anyOf": [ @@ -5223,68 +5231,60 @@ ], "description": "Voxel or vertex adjacency or connectivity rule used for cluster formation." }, - "number_of_tests": { + "tfce_used": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedBoolean" }, { "type": "null" } ], - "description": "Number of statistical tests considered by the multiple-comparison procedure." + "description": "Whether threshold-free cluster enhancement was used." }, - "permutation_count": { + "tfce_parameters": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Number of permutations, randomizations, or simulations used for inference." + "description": "TFCE parameters such as height and extent exponents when reported." }, - "search_volume": { + "permutation_count": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Reported search volume, mask size, or number of vertices or voxels used for inference." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "Number of permutations, randomizations, or simulations used for inference." }, - "tfce_parameters": { + "alpha_level": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "TFCE parameters such as height and extent exponents when reported." + "description": "Family-wise, false-discovery, or nominal alpha threshold when reported." }, - "tfce_used": { + "number_of_tests": { "anyOf": [ { - "$ref": "#/$defs/ExtractedBoolean" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Whether threshold-free cluster enhancement was used." + "description": "Number of statistical tests considered by the multiple-comparison procedure." } }, "required": [ @@ -5307,6 +5307,36 @@ "additionalProperties": false, "description": "Payload for an analysis whose map is one component or latent variable of a decomposition -- ICA, PCA, NMF, PLS, CCA, and the multimodal fusion methods. Covers a component whose map or loading was *tested*: compared between cohorts, correlated with behaviour, or otherwise given an inferential result. A component map presented descriptively with no test is not an Analysis, so it takes no record here. Being representable here does not imply eligibility for coordinate-based meta-analysis.\nOne analysis reports one component: a second latent variable or component from the same decomposition is a second Analysis.\nOne invariant LinkML does not express, so the structural validator carries it: `crossblock_covariance_percent` is meaningless when `second_block` is `[none]`, there being no second block for the covariance to cross. `variance_explained_percent` is permitted either way, since a two-block decomposition may still report how much of the brain block its component captures.", "properties": { + "method": { + "$ref": "#/$defs/ExtractedDecompositionMethod", + "description": "The algorithm that produced the components, which decides what their values and signs mean." + }, + "second_block": { + "$ref": "#/$defs/ExtractedSecondBlockList", + "description": "What the brain block was decomposed against, one value per additional block, and the primary filter on this payload. `[none]` for a decomposition of the brain data alone, which is a positive assertion rather than an empty slot.\nMultivalued because papers cross more than two blocks: a three-modality mCCA+jICA carries `other_imaging_modality` twice, and a design-plus-behaviour PLS carries `[design, behavioral]`. Name the blocks, never their number." + }, + "second_block_assessments": { + "description": "The measures forming a behavioural or clinical block, by identifier, when they are `Assessment` records of this study. Empty when the second block is the design, another imaging modality, or anything else that is not an assessment -- those are named in `second_block_detail`.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Assessment" + }, + "second_block_detail": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedStringList" + }, + { + "type": "null" + } + ], + "description": "What each non-assessment block held, in the source's words: the seed regions of a seed decomposition, the modalities of a fusion (\"ALFF, FA, grey matter\"), the genotype array of a parallel ICA (\"367 SNPs\"). Free text because the range of things that form a second block is open, and a SNP array is neither an `Assessment` nor an `Acquisition` of this study." + }, "component_id": { "$ref": "#/$defs/ExtractedString", "description": "Identifier of the component or latent variable within the decomposition, as reported -- commonly an index such as `IC12` or `LV1`. Required because without it two analyses from one decomposition are indistinguishable." @@ -5322,23 +5352,16 @@ ], "description": "The source's name for the component, such as a network label, or for what a latent variable captures." }, - "crossblock_covariance_percent": { + "total_components": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedInteger" }, { "type": "null" } ], - "description": "Percentage of crossblock covariance this latent variable accounts for, as reported. The nearest thing a two-block decomposition has to an effect size, and what a paper uses to justify reporting one latent variable and not another. Unset when `second_block` is `[none]`, there being no second block to cross." - }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "LatentDecompositionDetails" - ], - "type": "string" + "description": "Number of components or latent variables the decomposition produced." }, "dimensionality_selection_method": { "anyOf": [ @@ -5351,70 +5374,51 @@ ], "description": "How the number of components was chosen, as reported -- minimum description length, a variance-retained cutoff, a scree criterion, permutation on the singular values, or a fixed a priori number." }, - "map_quantity": { + "selection_criterion": { "anyOf": [ { - "$ref": "#/$defs/ExtractedMapQuantity" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "What the values of the reported map represent -- a loading, a weight, a PLS salience, a standardized score." - }, - "method": { - "$ref": "#/$defs/ExtractedDecompositionMethod", - "description": "The algorithm that produced the components, which decides what their values and signs mean." + "description": "How this component was selected for reporting." }, - "polarity_semantics": { + "variance_explained_percent": { "anyOf": [ { - "$ref": "#/$defs/ExtractedPolaritySemantics" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Whether the sign of the reported map carries meaning. A latent variable's sign is arbitrary up to a flip, and so is an independent component's, so a paper must say what positive means before a direction can be pooled." + "description": "Percentage of the brain block's variance this component accounts for, as reported. Not interchangeable with `crossblock_covariance_percent`: one is variance within a block and the other covariance between blocks, and averaging across the two would combine incomparable quantities." }, - "salience_threshold": { + "crossblock_covariance_percent": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "The criterion deciding which elements count as reliable contributors, as reported -- conventionally a bootstrap ratio cutoff such as \"|BSR| > 3.0\", or a standardized cutoff such as \"|Z| > 3.5\". Not `InferenceSettings`: a bootstrap ratio is a stability measure and not a test statistic, so reading it as a voxelwise threshold would misstate the inference." - }, - "second_block": { - "$ref": "#/$defs/ExtractedSecondBlockList", - "description": "What the brain block was decomposed against, one value per additional block, and the primary filter on this payload. `[none]` for a decomposition of the brain data alone, which is a positive assertion rather than an empty slot.\nMultivalued because papers cross more than two blocks: a three-modality mCCA+jICA carries `other_imaging_modality` twice, and a design-plus-behaviour PLS carries `[design, behavioral]`. Name the blocks, never their number." - }, - "second_block_assessments": { - "description": "The measures forming a behavioural or clinical block, by identifier, when they are `Assessment` records of this study. Empty when the second block is the design, another imaging modality, or anything else that is not an assessment -- those are named in `second_block_detail`.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Assessment" + "description": "Percentage of crossblock covariance this latent variable accounts for, as reported. The nearest thing a two-block decomposition has to an effect size, and what a paper uses to justify reporting one latent variable and not another. Unset when `second_block` is `[none]`, there being no second block to cross." }, - "second_block_detail": { + "map_quantity": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedMapQuantity" }, { "type": "null" } ], - "description": "What each non-assessment block held, in the source's words: the seed regions of a seed decomposition, the modalities of a fusion (\"ALFF, FA, grey matter\"), the genotype array of a parallel ICA (\"367 SNPs\"). Free text because the range of things that form a second block is open, and a SNP array is neither an `Assessment` nor an `Acquisition` of this study." + "description": "What the values of the reported map represent -- a loading, a weight, a PLS salience, a standardized score." }, - "selection_criterion": { + "salience_threshold": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -5423,29 +5427,25 @@ "type": "null" } ], - "description": "How this component was selected for reporting." + "description": "The criterion deciding which elements count as reliable contributors, as reported -- conventionally a bootstrap ratio cutoff such as \"|BSR| > 3.0\", or a standardized cutoff such as \"|Z| > 3.5\". Not `InferenceSettings`: a bootstrap ratio is a stability measure and not a test statistic, so reading it as a voxelwise threshold would misstate the inference." }, - "total_components": { + "polarity_semantics": { "anyOf": [ { - "$ref": "#/$defs/ExtractedInteger" + "$ref": "#/$defs/ExtractedPolaritySemantics" }, { "type": "null" } ], - "description": "Number of components or latent variables the decomposition produced." + "description": "Whether the sign of the reported map carries meaning. A latent variable's sign is arbitrary up to a flip, and so is an independent component's, so a paper must say what positive means before a direction can be pooled." }, - "variance_explained_percent": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedNumber" - }, - { - "type": "null" - } + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "LatentDecompositionDetails" ], - "description": "Percentage of the brain block's variance this component accounts for, as reported. Not interchangeable with `crossblock_covariance_percent`: one is variance within a block and the other covariance between blocks, and averaging across the two would combine incomparable quantities." + "type": "string" } }, "required": [ @@ -5461,7 +5461,7 @@ "additionalProperties": false, "description": "An MRI acquisition of any kind -- functional, structural, or diffusion, as modality records. Its own attributes are the ones commonly useful for study selection, subgrouping, or sensitivity analyses in coordinate- or image-based meta-analysis.", "properties": { - "acquisition_duration_seconds": { + "magnetic_field_strength_tesla": { "anyOf": [ { "$ref": "#/$defs/ExtractedNumber" @@ -5470,84 +5470,62 @@ "type": "null" } ], - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." - }, - "acquisition_type": { - "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", - "enum": [ - "MRI" - ], - "type": "string" + "description": "Nominal static magnetic field strength in tesla." }, - "acquisition_voxel_size_mm": { + "pulse_sequence_type": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumberList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Original acquisition voxel size [x, y, z] in millimeters, before interpolation or resampling." + "description": "General pulse sequence family, such as EPI, MPRAGE, SPGR, or spin echo." }, - "brain_coverage": { + "mr_acquisition_type": { "anyOf": [ { - "$ref": "#/$defs/ExtractedBrainCoverage" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", - "type": [ - "string", - "null" - ], - "x-reference": "Device" + "description": "Acquisition dimensionality, such as 2D or 3D; useful for distinguishing protocols with materially different spatial sampling." }, - "echo_time_seconds": { + "repetition_time_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumberList" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Echo time in seconds, as a list of one value per echo. A multi-echo sequence has several; a single-echo one has a list of length one." - }, - "local_id": { - "type": "string" + "description": "Time between the start of successive acquired volumes, in seconds." }, - "magnetic_field_strength_tesla": { + "echo_time_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedNumberList" }, { "type": "null" } ], - "description": "Nominal static magnetic field strength in tesla." - }, - "modality": { - "$ref": "#/$defs/ExtractedModality", - "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + "description": "Echo time in seconds, as a list of one value per echo. A multi-echo sequence has several; a single-echo one has a list of length one." }, - "mr_acquisition_type": { + "acquisition_voxel_size_mm": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumberList" }, { "type": "null" } ], - "description": "Acquisition dimensionality, such as 2D or 3D; useful for distinguishing protocols with materially different spatial sampling." + "description": "Original acquisition voxel size [x, y, z] in millimeters, before interpolation or resampling." }, "number_of_volumes": { "anyOf": [ @@ -5560,35 +5538,57 @@ ], "description": "Number of acquired volumes contributing to the recording, when reported; a proxy for scan length and temporal sampling in functional acquisitions." }, - "pulse_sequence_type": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "type": "string" + }, + "acquisition_type": { + "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", + "enum": [ + "MRI" + ], + "type": "string" + }, + "modality": { + "$ref": "#/$defs/ExtractedModality", + "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + }, + "acquisition_duration_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "General pulse sequence family, such as EPI, MPRAGE, SPGR, or spin echo." + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." + }, + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "type": [ + "string", + "null" + ], + "x-reference": "Device" }, - "repetition_time_seconds": { + "brain_coverage": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedBrainCoverage" }, { "type": "null" } ], - "description": "Time between the start of successive acquired volumes, in seconds." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -5633,14 +5633,26 @@ "additionalProperties": false, "description": "The scientific quantity of the map, independent of the statistic used to test it. When a paper alternates terms for the same map -- grey matter concentration in Methods and volume in Results -- take the Methods term for type and keep both wordings in source_label.\nOne record may be referenced by several Analyses, so every field must hold for all of them; an analysis of a different quantity takes its own record. The test is mechanical: two analyses share a record when `family`, `type`, `specific_metric` and `unit` all match. A study measures one or two quantities and tests them many ways, so sharing is the common case -- eight analyses of one connectivity measure are one record, not eight.\nNot a property of the thresholding scheme: one correction scheme routinely covers several measures, and one measure is often tested under several schemes. See storage-schema-design-notes.md for the reasoning behind this shape.", "properties": { - "family": { - "$ref": "#/$defs/ExtractedMeasureFamily", - "description": "Broad imaging or signal family, for coarse filtering." + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] }, "local_id": { "description": "Stable identifier used by Analysis foreign-key references.", "type": "string" }, + "family": { + "$ref": "#/$defs/ExtractedMeasureFamily", + "description": "Broad imaging or signal family, for coarse filtering." + }, + "type": { + "$ref": "#/$defs/ExtractedMeasureType", + "description": "Normalized quantity, for cross-study queries." + }, "source_label": { "anyOf": [ { @@ -5663,18 +5675,6 @@ ], "description": "Named metric when type is intentionally broad, such as fractional anisotropy, ALFF, fALFF, or a specific tracer binding potential." }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] - }, - "type": { - "$ref": "#/$defs/ExtractedMeasureType", - "description": "Normalized quantity, for cross-study queries." - }, "unit": { "anyOf": [ { @@ -5739,14 +5739,14 @@ "additionalProperties": false, "description": "The mediation model an effect's tested quantity is a path through: which path, and the intervening term it runs through. Present only when there is one; the ordinary case leaves `Effect.mediation` unset. Both slots are required, so a path always names its mediator.", "properties": { + "path": { + "$ref": "#/$defs/ExtractedEffectPath", + "description": "The path through the mediation model represented by this effect's map. An effect that is not a mediated path has no Mediation, so there is no `not_applicable` case." + }, "mediator": { "description": "The intervening term, by identifier. It must be a term of the ModelEstimation the containing Analysis names, or of a stage that model reaches through `inputs_from`.", "type": "string", "x-reference": "ModelTerm" - }, - "path": { - "$ref": "#/$defs/ExtractedEffectPath", - "description": "The path through the mediation model represented by this effect's map. An effect that is not a mediated path has no Mediation, so there is no `not_applicable` case." } }, "required": [ @@ -5777,7 +5777,27 @@ "additionalProperties": false, "description": "How a model was specified in software and estimated: its terms, the family, the source's wording for it, the estimator, the stage, the software, the pipeline that produced the data it was fitted to, and the spatial element it was fitted over. One record may be referenced by several Analyses, so every field must hold for all of them; an analysis specified or estimated differently takes its own record. **The data a model was fitted to is part of its specification**.\nOne record is one estimation stage. Neuroimaging splits a model across stages for tractability, and `inputs_from` is what puts it back together: a stage's terms are its own plus those of the stages it consumed, so the split costs the record nothing while each stage keeps its own family, estimator, software and basis.\n\nThe test for whether two analyses share a record is mechanical: list the columns each was fitted with. A different dependent variable, a different covariate set, or a different subset of participants means a different design matrix and therefore a separate record, even when family, estimator and software are identical. Two BPM regressions of the same measure on the same regressor, one over the patients and one over patients and controls pooled, are two records.\nSharing one record across matrices corrupts the derived adjustment set. The adjustment set is the model's terms minus the terms named by its cells. If several matrices share one record, they claim adjustments that their original models never made, and the record cannot expose the error.", "properties": { - "estimator": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "description": "Stable identifier used by Analysis foreign-key references.", + "type": "string" + }, + "model_family": { + "$ref": "#/$defs/ExtractedModelFamily", + "description": "Normalized statistical model family, which is the queryable form. Required, so a paper that never says still carries the slot, as `extraction_status: not_reported`. A method the vocabulary cannot name -- ICA, representational similarity, a decoder -- is written here as free text; the Analysis.details subclass also identifies the method." + }, + "model_type": { + "$ref": "#/$defs/ExtractedString", + "description": "The source's own wording for the model, minimally cleaned, such as mass- univariate GLM, meta-analysis, multivariate, predictive model, ICA, or representational similarity analysis." + }, + "hrf_model": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -5786,9 +5806,9 @@ "type": "null" } ], - "description": "Estimation method, such as OLS, WLS, maximum likelihood, restricted maximum likelihood, permutation, or Bayesian estimation." + "description": "The haemodynamic response basis the design matrix was built with: a canonical HRF, a canonical plus temporal or dispersion derivatives, an FIR or gamma basis, or a named alternative. Unset for a measure that models no timeseries -- morphometry, diffusion, static PET." }, - "hrf_model": { + "model_settings": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -5797,28 +5817,13 @@ "type": "null" } ], - "description": "The haemodynamic response basis the design matrix was built with: a canonical HRF, a canonical plus temporal or dispersion derivatives, an FIR or gamma basis, or a named alternative. Unset for a measure that models no timeseries -- morphometry, diffusion, static PET." - }, - "inputs_from": { - "description": "The models whose fitted output this model was estimated on: the first-level GLM whose contrast images a group model takes as its data, the subject-level fit an ROI ANOVA summarises. The stage graph, recorded only where the source states it.\n**A model's terms are its own plus, transitively, those of the models named here.** That combined list is what `terms` would be had the analysis been fitted in one step.\nLeave it empty when the source describes one design matrix, and when it describes the lower stage too thinly to constitute a model. Empty is not a claim that no lower stage existed; it is the absence of a statement about one, exactly as an unrecorded term is.\nMust be acyclic, no model may reach itself.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "ModelEstimation" - }, - "local_id": { - "description": "Stable identifier used by Analysis foreign-key references.", - "type": "string" + "description": "Essential estimator, variance, autocorrelation, covariance, regularization, and software-specific settings." }, - "model_family": { - "$ref": "#/$defs/ExtractedModelFamily", - "description": "Normalized statistical model family, which is the queryable form. Required, so a paper that never says still carries the slot, as `extraction_status: not_reported`. A method the vocabulary cannot name -- ICA, representational similarity, a decoder -- is written here as free text; the Analysis.details subclass also identifies the method." + "stage": { + "$ref": "#/$defs/ExtractedString", + "description": "Stage of analysis, in the source's terms: run, session, subject and group are the usual words, and any other the source uses is storable as written. Deliberately left open -- no vocabulary fits every design, and nothing derives anything from it.\nA label, not an ordering. Only `inputs_from` records which stage fed another: two records labelled `group` say nothing about their relationship, and these strings never imply a chain." }, - "model_settings": { + "estimator": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -5827,22 +5832,7 @@ "type": "null" } ], - "description": "Essential estimator, variance, autocorrelation, covariance, regularization, and software-specific settings." - }, - "model_type": { - "$ref": "#/$defs/ExtractedString", - "description": "The source's own wording for the model, minimally cleaned, such as mass- univariate GLM, meta-analysis, multivariate, predictive model, ICA, or representational similarity analysis." - }, - "preprocessing": { - "description": "The pipelines that produced the data this model was fitted to. Every Analysis referencing this record came through them, so a model fitted to differently prepared data -- unsmoothed images for a classifier, smoothed ones for a mass-univariate fit -- takes its own record rather than sharing one.\nMore than one entry when the fit consumed more than one kind of image: a multimodal regression of perfusion on grey-matter volume was fitted to the output of both pipelines, and naming either alone says the other's smoothing and normalisation did not touch the map.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Preprocessing" + "description": "Estimation method, such as OLS, WLS, maximum likelihood, restricted maximum likelihood, permutation, or Bayesian estimation." }, "software": { "anyOf": [ @@ -5866,17 +5856,27 @@ ], "description": "The element this model was fitted over. Distinct from the Analysis's `spatial_scope`, which gives the extent rather than the element: a whole-brain model can be voxelwise or vertexwise, and a model over an ROI can average the region or test its parcels. Record it rather than inferring it from `coordinate_space`.\nA model fitted over a different element is a different model, so a voxelwise GLM and an ROI-mean ANOVA of the same design take two records: no contrast of one can be read off the other's fit." }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "preprocessing": { + "description": "The pipelines that produced the data this model was fitted to. Every Analysis referencing this record came through them, so a model fitted to differently prepared data -- unsmoothed images for a classifier, smoothed ones for a mass-univariate fit -- takes its own record rather than sharing one.\nMore than one entry when the fit consumed more than one kind of image: a multimodal regression of perfusion on grey-matter volume was fitted to the output of both pipelines, and naming either alone says the other's smoothing and normalisation did not touch the map.", + "items": { + "type": "string" + }, "type": [ - "string", + "array", "null" - ] + ], + "x-reference": "Preprocessing" }, - "stage": { - "$ref": "#/$defs/ExtractedString", - "description": "Stage of analysis, in the source's terms: run, session, subject and group are the usual words, and any other the source uses is storable as written. Deliberately left open -- no vocabulary fits every design, and nothing derives anything from it.\nA label, not an ordering. Only `inputs_from` records which stage fed another: two records labelled `group` say nothing about their relationship, and these strings never imply a chain." + "inputs_from": { + "description": "The models whose fitted output this model was estimated on: the first-level GLM whose contrast images a group model takes as its data, the subject-level fit an ROI ANOVA summarises. The stage graph, recorded only where the source states it.\n**A model's terms are its own plus, transitively, those of the models named here.** That combined list is what `terms` would be had the analysis been fitted in one step.\nLeave it empty when the source describes one design matrix, and when it describes the lower stage too thinly to constitute a model. Empty is not a claim that no lower stage existed; it is the absence of a statement about one, exactly as an unrecorded term is.\nMust be acyclic, no model may reach itself.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "ModelEstimation" }, "terms": { "description": "The terms **this stage** was specified with -- the columns of its design matrix, in the general sense that covers a GLM's regressors and the predictors of any other family. Stated once here however many contrasts are taken from them, and never restated on a stage above: the terms of a lower stage are reached through `inputs_from`.\nRecord every term the source states, including ones no reported contrast tests, and including ones mentioned only in passing: \"controlling for age and sex\" is a statement about the design matrix. This list, together with the lists reached through `inputs_from`, is **the adjustment set of every contrast taken from this model** -- each contrast's is that combined list minus the terms its cells name -- so a term omitted from every stage reads as one the model did not adjust for. Where the list is incomplete, the slot's extraction_status is what says so.\nMay be empty, and legitimately is for a stage that fits only an intercept: a one-sample group test over first-level contrast images has no column of its own. Do not invent an intercept term to fill it.\nAnalyses whose design matrices differ take their own records rather than sharing one.", @@ -5914,35 +5914,25 @@ "additionalProperties": false, "description": "One term of the model: a column of the design matrix, or the equivalent predictor in a family with no design matrix. A categorical term names its levels and the entities carrying them; a continuous term carries the facts that make a slope interpretable. Nothing here is a fact about any one contrast -- what a contrast did with this term is a `Cell` on that contrast's Effect. A term with no cell on some contrast is a term that contrast was adjusted for rather than tested.\n\nA term is something the measure was regressed *on*. The quantity being modelled is `Analysis.measure` and never a term of the model that models it. A table laid out with one row-block per measured parameter -- four diffusion parameters, three frequency bands -- is reporting one analysis per parameter over one measure each, not one analysis over a factor whose levels are the parameters.", "properties": { - "assessment": { - "description": "The assessment whose measurements supply a continuous term, **including when the column is derived from them rather than being one of them**: a change score, a percent change, a composite of subscales, a residualized score. The instrument is still where the values came from, and one administration or several makes no difference to that -- `region` says the same by example, an ROI mean and a PPI regressor both naming their region.\nOne instrument, one record, however many columns it supplies: two subscale scores of one questionnaire each name it, and which subscale a column carries is the term's `name`.\nUnset only when the values come from somewhere the study does not record as an Assessment -- age, dose, a motion parameter, cardiac phase, scanner drift -- in which case `source_definition` names the origin. A term with neither is a column whose origin the record does not state.", + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": [ "string", "null" - ], - "x-reference": "Assessment" + ] }, - "functional_form": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedFunctionalForm" - }, - { - "type": "null" - } - ], - "description": "Shape of the term's relationship to the measure. A squared term with a negative coefficient is an inverted-U relationship, not a negative association, so non-linear and transformed terms must not be recorded as linear. A model fitting both a linear and a quadratic form of the same measurement has two terms, not one.\nNot where a product is recorded: a non-empty `interaction_with` is what makes a column a product." + "local_id": { + "description": "Stable identifier, referenced by an Effect's `cells` and its `mediation.mediator`, and by `interaction_with`.", + "type": "string" }, - "interaction_with": { - "description": "The other terms this term is a product of -- what makes this column an interaction column. Populate it when the source names an interaction term in one model; leave it empty when the source describes a regression on a contrast.\nThe terms it may name are those of this model and of the stages it reaches through `inputs_from`, which is how a group stage crosses a cohort factor with a first-level condition: the product column is declared where it was fitted and names the lower stage's column directly.\nA crossing of two factors is normally read off the cells, since a factor compared against itself carries a positive cell and a negative one. A continuous term has no levels and so cannot be crossed that way, which makes this slot the only record of a moderation such as `age x group`; the derived kind reads it before `variation_level`.\nA product of terms that are all categorical and all crossed is legal but decides nothing, the crossed cells already saying what it says, and is flagged for review.\nA product column declares no `levels` of its own; a signed cell on one with a continuous component may name a level of the factor, saying whose slope the sign describes. Such a column is not a covariate of a contrast whose cells name all of its components.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "ModelTerm" + "name": { + "$ref": "#/$defs/ExtractedString", + "description": "Term label, unique within the model: a name appearing twice is one term recorded twice. For a categorical term this is the factor's identity.\nOften a label the source never writes. A paper reporting 1-back, 2-back and 3-back conditions states the levels and states that they vary together, but frequently names no axis; `load` is then a name supplied for a grouping the source does make. That is permitted, within two limits: the levels must be source-stated, and the name must not assert a comparison or a construct the paper does not." + }, + "type": { + "$ref": "#/$defs/ExtractedTermType", + "description": "Whether the term is categorical or continuous. Categorical when the column distinguishes named cells of the design -- occasions, cohorts, conditions, arms, regions -- whether or not the paper names the axis; `levels` is then required. Continuous when the column carries a measured value and the model fits a slope.\nSmall as it is, this field settles four others: whether `levels` exists, whether a `Cell` here takes a `level`, whether the term can be crossed, and which branch of the derived kind fires. A term recorded continuous is one no contrast can be built across.\n**A term whose name states a comparison -- `pre > post`, `A versus B` -- is almost always a categorical factor written down from its contrast's side.** The design matrix distinguished two occasions, cohorts or conditions; the paper labelled the difference and left the axis unnamed. Record the factor with a level per side and let the cells carry the direction (`representing-models.md` §5.6). Collapsed into one continuous column, nothing says what was compared and the effect reads as a slope.\nThe exception is a column genuinely holding one number per participant -- a difference score, a percent change entered as a covariate. That is continuous, and `source_definition` records the subtraction it came from." }, "levels": { "description": "The levels of a categorical term, and for each the entities carrying it. This is the whole of what a factor is: its level vocabulary, and which cohort, condition, occasion, or arm occupies each cell. Record a level the source names even when no reported contrast uses it -- a three-level factor contrasted at two levels is still a three-level factor. Empty for a continuous term.", @@ -5954,13 +5944,13 @@ "null" ] }, - "local_id": { - "description": "Stable identifier, referenced by an Effect's `cells` and its `mediation.mediator`, and by `interaction_with`.", - "type": "string" - }, - "name": { - "$ref": "#/$defs/ExtractedString", - "description": "Term label, unique within the model: a name appearing twice is one term recorded twice. For a categorical term this is the factor's identity.\nOften a label the source never writes. A paper reporting 1-back, 2-back and 3-back conditions states the levels and states that they vary together, but frequently names no axis; `load` is then a name supplied for a grouping the source does make. That is permitted, within two limits: the levels must be source-stated, and the name must not assert a comparison or a construct the paper does not." + "assessment": { + "description": "The assessment whose measurements supply a continuous term, **including when the column is derived from them rather than being one of them**: a change score, a percent change, a composite of subscales, a residualized score. The instrument is still where the values came from, and one administration or several makes no difference to that -- `region` says the same by example, an ROI mean and a PPI regressor both naming their region.\nOne instrument, one record, however many columns it supplies: two subscale scores of one questionnaire each name it, and which subscale a column carries is the term's `name`.\nUnset only when the values come from somewhere the study does not record as an Assessment -- age, dose, a motion parameter, cardiac phase, scanner drift -- in which case `source_definition` names the origin. A term with neither is a column whose origin the record does not state.", + "type": [ + "string", + "null" + ], + "x-reference": "Assessment" }, "region": { "description": "The region whose signal supplies this term: a seed timecourse, an ROI mean covariate, a PPI physiological regressor. The counterpart of `assessment` for a column drawn from a place in the brain rather than an instrument.\nSingular, so it does not serve a factor that *compares* regions: that is one term with several levels, and the regions hang off `FactorLevel.regions`.\n\nThe test is whether the column's numbers came out of that place. A region that only bounds where inference ran -- an explicit mask, a search volume, an atlas the analysis was restricted to -- is `Analysis.regions`, and no term draws anything from it. Naming it here says the column *was* that region's signal, which for a voxelwise fit inside a mask is false, and it says so while `Analysis.regions` goes on saying the true thing, so the two never visibly disagree.\nLeave it empty on a categorical term. Regions are carried by a factor's levels, on `FactorLevel.regions`, and a factor comparing places has at least two: one level named for a mask is a search space written as a comparison the study never made.", @@ -5970,28 +5960,38 @@ ], "x-reference": "Region" }, - "source_definition": { + "variation_level": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedVariationLevel" }, { "type": "null" } ], - "description": "The source's own definition of the term, and the origin of a continuous term that no Assessment supplies, such as age, dose, a motion parameter, cardiac phase, or scanner drift.\n**Required for a derived column, whatever its `assessment` says.** A change score names its instrument in `assessment` and its derivation here, in the source's own words and including the occasions it spans -- \"percent reduction in BDI, (post-pre)/pre, from baseline to post-treatment\". Nothing else can carry those occasions: `FactorLevel.timepoints` names the occasions a factor *compares*, and a derived column has no levels to hang them on, so where a study has several post-intervention occasions this is the only thing separating a change to the endpoint from a change to a later follow-up.\nNot the shape of the fitted relationship, which is `functional_form`: a percent change entered linearly is a linear term computed from a difference, so recording the construction as the form would lose whether the fit was linear or quadratic. A transform *of the measure* the vocabulary cannot name still goes in functional_form." + "description": "Whether this term moves within a participant or only across the sample. Populate it for **any** term: a repeated-measures factor is within-subject and a diagnosis factor is between-subject in exactly the sense a trial-wise modulator and an age covariate are.\nFor a continuous term it is what separates a parametric modulation from a cross-subject regression, and the derived kind of any effect testing it reads this on that basis. For a categorical factor it feeds no derivation, so setting it changes no other value; it is the factor's scope, and two studies that crossed a factor within participants and between them did not estimate the same thing.\nA categorical factor's levels usually settle it, and the two should agree: levels naming `groups` are between-subject, since a participant belongs to one cohort; levels naming `timepoints` are within-subject, since an occasion is when the same people were measured again; levels naming `conditions` are within-subject in the ordinary repeated-measures case; levels naming `arms` follow `StudyDesign.assignment_structure`, crossover being within and parallel between.\nA level the vocabulary cannot name is written here as free text; the derived kind then cannot choose between a modulation and a regression, so it is undetermined for that record rather than wrong." }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "functional_form": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedFunctionalForm" + }, + { + "type": "null" + } + ], + "description": "Shape of the term's relationship to the measure. A squared term with a negative coefficient is an inverted-U relationship, not a negative association, so non-linear and transformed terms must not be recorded as linear. A model fitting both a linear and a quadratic form of the same measurement has two terms, not one.\nNot where a product is recorded: a non-empty `interaction_with` is what makes a column a product." + }, + "interaction_with": { + "description": "The other terms this term is a product of -- what makes this column an interaction column. Populate it when the source names an interaction term in one model; leave it empty when the source describes a regression on a contrast.\nThe terms it may name are those of this model and of the stages it reaches through `inputs_from`, which is how a group stage crosses a cohort factor with a first-level condition: the product column is declared where it was fitted and names the lower stage's column directly.\nA crossing of two factors is normally read off the cells, since a factor compared against itself carries a positive cell and a negative one. A continuous term has no levels and so cannot be crossed that way, which makes this slot the only record of a moderation such as `age x group`; the derived kind reads it before `variation_level`.\nA product of terms that are all categorical and all crossed is legal but decides nothing, the crossed cells already saying what it says, and is flagged for review.\nA product column declares no `levels` of its own; a signed cell on one with a continuous component may name a level of the factor, saying whose slope the sign describes. Such a column is not a covariate of a contrast whose cells name all of its components.", + "items": { + "type": "string" + }, "type": [ - "string", + "array", "null" - ] - }, - "type": { - "$ref": "#/$defs/ExtractedTermType", - "description": "Whether the term is categorical or continuous. Categorical when the column distinguishes named cells of the design -- occasions, cohorts, conditions, arms, regions -- whether or not the paper names the axis; `levels` is then required. Continuous when the column carries a measured value and the model fits a slope.\nSmall as it is, this field settles four others: whether `levels` exists, whether a `Cell` here takes a `level`, whether the term can be crossed, and which branch of the derived kind fires. A term recorded continuous is one no contrast can be built across.\n**A term whose name states a comparison -- `pre > post`, `A versus B` -- is almost always a categorical factor written down from its contrast's side.** The design matrix distinguished two occasions, cohorts or conditions; the paper labelled the difference and left the axis unnamed. Record the factor with a level per side and let the cells carry the direction (`representing-models.md` §5.6). Collapsed into one continuous column, nothing says what was compared and the effect reads as a slope.\nThe exception is a column genuinely holding one number per participant -- a difference score, a percent change entered as a covariate. That is continuous, and `source_definition` records the subtraction it came from." + ], + "x-reference": "ModelTerm" }, "unit": { "anyOf": [ @@ -6004,16 +6004,16 @@ ], "description": "The unit a continuous term is measured in, when the source gives one -- years for age, mg for a dose. It is what makes a reported slope interpretable." }, - "variation_level": { + "source_definition": { "anyOf": [ { - "$ref": "#/$defs/ExtractedVariationLevel" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Whether this term moves within a participant or only across the sample. Populate it for **any** term: a repeated-measures factor is within-subject and a diagnosis factor is between-subject in exactly the sense a trial-wise modulator and an age covariate are.\nFor a continuous term it is what separates a parametric modulation from a cross-subject regression, and the derived kind of any effect testing it reads this on that basis. For a categorical factor it feeds no derivation, so setting it changes no other value; it is the factor's scope, and two studies that crossed a factor within participants and between them did not estimate the same thing.\nA categorical factor's levels usually settle it, and the two should agree: levels naming `groups` are between-subject, since a participant belongs to one cohort; levels naming `timepoints` are within-subject, since an occasion is when the same people were measured again; levels naming `conditions` are within-subject in the ordinary repeated-measures case; levels naming `arms` follow `StudyDesign.assignment_structure`, crossover being within and parallel between.\nA level the vocabulary cannot name is written here as free text; the derived kind then cannot choose between a modulation and a regression, so it is undetermined for that record rather than wrong." + "description": "The source's own definition of the term, and the origin of a continuous term that no Assessment supplies, such as age, dose, a motion parameter, cardiac phase, or scanner drift.\n**Required for a derived column, whatever its `assessment` says.** A change score names its instrument in `assessment` and its derivation here, in the source's own words and including the occasions it spans -- \"percent reduction in BDI, (post-pre)/pre, from baseline to post-treatment\". Nothing else can carry those occasions: `FactorLevel.timepoints` names the occasions a factor *compares*, and a derived column has no levels to hang them on, so where a study has several post-intervention occasions this is the only thing separating a change to the endpoint from a change to a later follow-up.\nNot the shape of the fitted relationship, which is `functional_form`: a percent change entered linearly is a linear term computed from a difference, so recording the construction as the form would lose whether the fit was linear or quadratic. A transform *of the measure* the vocabulary cannot name still goes in functional_form." } }, "required": [ @@ -6028,20 +6028,20 @@ "additionalProperties": false, "description": "Payload for an effect that is grounded in the source but has no stable structured decomposition. Analysis.definition remains required, so the meaning survives even though the structure does not.", "properties": { + "reason": { + "$ref": "#/$defs/ExtractedNotStructurableReason", + "description": "The kind of obstacle that prevented structuring, selected from a short vocabulary. Pairs with `explanation`, which says what specifically was lost." + }, + "explanation": { + "$ref": "#/$defs/ExtractedString", + "description": "What specifically could not be represented, in the source's own terms. The reason gives the category; this gives the instance, so a reader can judge whether the effect is usable." + }, "details_type": { "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", "enum": [ "NotStructurableDetails" ], "type": "string" - }, - "explanation": { - "$ref": "#/$defs/ExtractedString", - "description": "What specifically could not be represented, in the source's own terms. The reason gives the category; this gives the instance, so a reader can judge whether the effect is usable." - }, - "reason": { - "$ref": "#/$defs/ExtractedNotStructurableReason", - "description": "The kind of obstacle that prevented structuring, selected from a short vocabulary. Pairs with `explanation`, which says what specifically was lost." } }, "required": [ @@ -6075,21 +6075,10 @@ "additionalProperties": false, "description": "Payload for a method outside the first-class families. Available for any analysis, so an unusual design never has to delete true terms in order to validate.", "properties": { - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "OtherAnalysisDetails" - ], - "type": "string" - }, "method_label": { "$ref": "#/$defs/ExtractedString", "description": "The method's name as the source gives it. This is the only record of what the method was, so write what the paper calls it rather than a paraphrase." }, - "reason_first_class_type_not_used": { - "$ref": "#/$defs/ExtractedString", - "description": "Why no first-class details subclass fits this method." - }, "structured_details": { "anyOf": [ { @@ -6100,6 +6089,17 @@ } ], "description": "JSON-encoded method-specific payload, when the method has structure worth keeping." + }, + "reason_first_class_type_not_used": { + "$ref": "#/$defs/ExtractedString", + "description": "Why no first-class details subclass fits this method." + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "OtherAnalysisDetails" + ], + "type": "string" } }, "required": [ @@ -6114,16 +6114,31 @@ "additionalProperties": false, "description": "An acquisition whose modality has no class of its own: MEG, SPECT, and `other`. Nothing here is normalized, so this is where a modality the schema does not name keeps its parameters. A parameter that MRI, EEG, FNIRS, or PET has no field for belongs on that sibling class instead.", "properties": { - "acquisition_duration_seconds": { + "modality_label": { + "$ref": "#/$defs/ExtractedString", + "description": "The source's own name for the modality or recording technique, verbatim. Always populated, since the Modality vocabulary is coarser than what a source reports." + }, + "reason_no_modality_class_used": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." + "description": "Why this is not an MRI, EEG, FNIRS, or PET record." + }, + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "type": "string" }, "acquisition_type": { "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", @@ -6132,16 +6147,20 @@ ], "type": "string" }, - "brain_coverage": { + "modality": { + "$ref": "#/$defs/ExtractedModality", + "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + }, + "acquisition_duration_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedBrainCoverage" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." }, "device": { "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", @@ -6151,35 +6170,16 @@ ], "x-reference": "Device" }, - "local_id": { - "type": "string" - }, - "modality": { - "$ref": "#/$defs/ExtractedModality", - "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." - }, - "modality_label": { - "$ref": "#/$defs/ExtractedString", - "description": "The source's own name for the modality or recording technique, verbatim. Always populated, since the Modality vocabulary is coarser than what a source reports." - }, - "reason_no_modality_class_used": { + "brain_coverage": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedBrainCoverage" }, { "type": "null" } ], - "description": "Why this is not an MRI, EEG, FNIRS, or PET record." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -6195,44 +6195,29 @@ "additionalProperties": false, "description": "A PET acquisition. Its own attributes are the ones defining the biological signal or spatial comparability, and so are likely to be useful for study selection, subgrouping, or image harmonization.", "properties": { - "acquisition_duration_seconds": { + "image_units": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." - }, - "acquisition_type": { - "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", - "enum": [ - "PET" - ], - "type": "string" + "description": "Units encoded in the PET image, such as Bq/mL or SUV." }, - "brain_coverage": { + "tracer_name": { "anyOf": [ { - "$ref": "#/$defs/ExtractedBrainCoverage" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", - "type": [ - "string", - "null" - ], - "x-reference": "Device" + "description": "Name of the injected tracer compound." }, - "image_units": { + "tracer_radionuclide": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -6241,14 +6226,7 @@ "type": "null" } ], - "description": "Units encoded in the PET image, such as Bq/mL or SUV." - }, - "local_id": { - "type": "string" - }, - "modality": { - "$ref": "#/$defs/ExtractedModality", - "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + "description": "Radioisotope labeling the tracer, such as C11, F18, or O15." }, "mode_of_administration": { "anyOf": [ @@ -6261,27 +6239,38 @@ ], "description": "Tracer administration mode, such as bolus, infusion, or bolus plus infusion." }, - "reconstructed_voxel_size_mm": { + "scan_type": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumberList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Reconstructed PET voxel size [x, y, z] in millimeters." + "description": "Whether the analyzed PET data were acquired or summarized as a static or dynamic scan." }, - "scan_type": { + "uptake_time_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Whether the analyzed PET data were acquired or summarized as a static or dynamic scan." + "description": "Interval from tracer administration to the start of the analyzed scan window, in seconds." + }, + "reconstructed_voxel_size_mm": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedNumberList" + }, + { + "type": "null" + } + ], + "description": "Reconstructed PET voxel size [x, y, z] in millimeters." }, "spatial_resolution_mm": { "anyOf": [ @@ -6302,38 +6291,49 @@ "null" ] }, - "tracer_name": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedString" - }, - { - "type": "null" - } + "local_id": { + "type": "string" + }, + "acquisition_type": { + "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", + "enum": [ + "PET" ], - "description": "Name of the injected tracer compound." + "type": "string" }, - "tracer_radionuclide": { + "modality": { + "$ref": "#/$defs/ExtractedModality", + "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + }, + "acquisition_duration_seconds": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Radioisotope labeling the tracer, such as C11, F18, or O15." + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings." }, - "uptake_time_seconds": { + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "type": [ + "string", + "null" + ], + "x-reference": "Device" + }, + "brain_coverage": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedBrainCoverage" }, { "type": "null" } ], - "description": "Interval from tracer administration to the start of the analyzed scan window, in seconds." + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -6348,21 +6348,20 @@ "additionalProperties": false, "description": "Deterministic document index entry for every section and subsection in the processed paper text. Character offsets use the same normalized source text and half-open interval convention [start_char, end_char).", "properties": { - "end_char": { - "description": "Exclusive character offset immediately before the next same-or-higher-level heading or EOF.", + "ordinal": { + "description": "Zero-based document order of the section heading.", "minimum": 0, "type": "integer" }, + "title": { + "description": "Section or subsection heading text after deterministic normalization.", + "type": "string" + }, "level": { "description": "Heading depth; 0 is the document-level title, 1 is a top-level section.", "minimum": 0, "type": "integer" }, - "ordinal": { - "description": "Zero-based document order of the section heading.", - "minimum": 0, - "type": "integer" - }, "parent_section": { "description": "title of the immediately containing section, when applicable.", "type": [ @@ -6375,9 +6374,10 @@ "minimum": 0, "type": "integer" }, - "title": { - "description": "Section or subsection heading text after deterministic normalization.", - "type": "string" + "end_char": { + "description": "Exclusive character offset immediately before the next same-or-higher-level heading or EOF.", + "minimum": 0, + "type": "integer" } }, "required": [ @@ -6398,7 +6398,7 @@ "$ref": "#/$defs/ExtractedString", "description": "The measure as the source names it, such as accuracy, balanced accuracy, AUC, d-prime, sensitivity, specificity, or a correlation between predicted and observed scores." }, - "reference_value": { + "value": { "anyOf": [ { "$ref": "#/$defs/ExtractedNumber" @@ -6407,29 +6407,29 @@ "type": "null" } ], - "description": "Numeric reference this measure was compared against, when reported -- chance for an accuracy, zero for a correlation. Each measure carries its own: accuracy against chance and AUC against 0.5 are two references, not one. `relation` says which side of it the measure fell on." + "description": "The value as reported, in the source's own scale: a percentage and a proportion are both recorded as written rather than converted, since the scale is not always stated and converting would invent precision." }, - "relation": { + "reference_value": { "anyOf": [ { - "$ref": "#/$defs/ExtractedPerformanceRelation" + "$ref": "#/$defs/ExtractedNumber" }, { "type": "null" } ], - "description": "Direction of this measure relative to its own reference_value." + "description": "Numeric reference this measure was compared against, when reported -- chance for an accuracy, zero for a correlation. Each measure carries its own: accuracy against chance and AUC against 0.5 are two references, not one. `relation` says which side of it the measure fell on." }, - "value": { + "relation": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumber" + "$ref": "#/$defs/ExtractedPerformanceRelation" }, { "type": "null" } ], - "description": "The value as reported, in the source's own scale: a percentage and a proportion are both recorded as written rather than converted, since the scale is not always stated and converting would invent precision." + "description": "Direction of this measure relative to its own reference_value." } }, "required": [ @@ -6462,6 +6462,18 @@ "additionalProperties": false, "description": "An ordered preprocessing pipeline applied to acquired data before or as part of statistical modeling. This can be expanded with COBIDAS.", "properties": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "description": "Stable identifier used by Analysis foreign-key references.", + "type": "string" + }, "description": { "anyOf": [ { @@ -6473,22 +6485,18 @@ ], "description": "Narrative overview of the pipeline, its inputs, outputs, and intended purpose." }, - "local_id": { - "description": "Stable identifier used by Analysis foreign-key references.", - "type": "string" - }, - "smoothing_fwhm_mm": { + "software": { "anyOf": [ { - "$ref": "#/$defs/ExtractedNumberList" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "Full width at half maximum of the applied spatial smoothing kernel, in millimetres: a list of one value for an isotropic kernel and of three for an anisotropic [x, y, z] one. Record 0 when the source states that no smoothing was applied." + "description": "Software packages or methods used, including version and revision identifiers." }, - "software": { + "steps": { "anyOf": [ { "$ref": "#/$defs/ExtractedStringList" @@ -6497,26 +6505,18 @@ "type": "null" } ], - "description": "Software packages or methods used, including version and revision identifiers." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "Ordered free-text preprocessing operation names, listed in execution order; examples include slice-timing correction, motion correction, distortion correction, spatial normalization, and smoothing. The smoothing kernel itself is `smoothing_fwhm_mm` rather than a phrase in this list, because it is the one preprocessing parameter a synthesis filters on." }, - "steps": { + "smoothing_fwhm_mm": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedNumberList" }, { "type": "null" } ], - "description": "Ordered free-text preprocessing operation names, listed in execution order; examples include slice-timing correction, motion correction, distortion correction, spatial normalization, and smoothing. The smoothing kernel itself is `smoothing_fwhm_mm` rather than a phrase in this list, because it is the one preprocessing parameter a synthesis filters on." + "description": "Full width at half maximum of the applied spatial smoothing kernel, in millimetres: a list of one value for an isotropic kernel and of three for an anisotropic [x, y, z] one. Record 0 when the source states that no smoothing was applied." } }, "required": [ @@ -6539,22 +6539,38 @@ "additionalProperties": false, "description": "A brain region the study delimited and used: a connectivity seed or target, the search space an analysis was restricted to, a level of a factor comparing regions, or the signal a model term carries. Declared once and referenced from each, because the same seed written separately in each place is three spellings nothing can join.", "properties": { - "atlas": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "description": "Stable identifier.", + "type": "string" + }, + "name": { + "$ref": "#/$defs/ExtractedString", + "description": "The region as the source names it -- aSCC, left dlPFC parcel, salience network. Its own words: normalization to an anatomical vocabulary happens downstream and needs the original to map from." + }, + "definition_method": { + "$ref": "#/$defs/ExtractedRegionDefinition", + "description": "How the region was delimited, which is a statement about independence: one drawn from the study's own prior contrast is not independent of an effect tested inside it. Per region, since one analysis may use an atlas mask and a functional ROI at once. Required, so a source that does not say still carries the slot, as `extraction_status: not_reported`. Open vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range discards the whole region rather than the one unrecognised word.\n\nTwo values are routinely confused and the difference is the entire point of the field. `functional_localizer` means a *separate* acquisition or run whose only purpose was to locate the region, independent of the effect later tested in it. A region that is a peak, a cluster, or a component of one of *this paper's own* analyses is `same_study_analysis` however functionally it was defined -- and that value is what makes the circularity visible, which is why `Analysis.defines_regions` exists to name the analysis it came from. Reaching for `functional_localizer` because the region is functional, rather than because a localizer was run, is the mistake to avoid." + }, + "region_type": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedRegionType" }, { "type": "null" } ], - "description": "The atlas, parcellation or template it came from, as named -- AAL, WFU_Pickatlas, Schaefer 400 -- with version or parcel index when given. `definition_method: atlas` records that one was used, not which, which is not enough to pool two studies." - }, - "definition_method": { - "$ref": "#/$defs/ExtractedRegionDefinition", - "description": "How the region was delimited, which is a statement about independence: one drawn from the study's own prior contrast is not independent of an effect tested inside it. Per region, since one analysis may use an atlas mask and a functional ROI at once. Required, so a source that does not say still carries the slot, as `extraction_status: not_reported`. Open vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range discards the whole region rather than the one unrecognised word.\n\nTwo values are routinely confused and the difference is the entire point of the field. `functional_localizer` means a *separate* acquisition or run whose only purpose was to locate the region, independent of the effect later tested in it. A region that is a peak, a cluster, or a component of one of *this paper's own* analyses is `same_study_analysis` however functionally it was defined -- and that value is what makes the circularity visible, which is why `Analysis.defines_regions` exists to name the analysis it came from. Reaching for `functional_localizer` because the region is functional, rather than because a localizer was run, is the mistake to avoid." + "description": "What kind of thing the region is, which `definition_method` does not settle: an atlas parcel and an anatomical structure can both come from an atlas, and a network is neither." }, - "description": { + "atlas": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -6563,34 +6579,18 @@ "type": "null" } ], - "description": "How the source defines the region, including any geometry it states: \"8 mm spheres centered at [0 36 -6]\". Definitional coordinates have no structured slot and belong here, where the evidence span makes them checkable; result coordinates reach the record through the table parser instead." - }, - "local_id": { - "description": "Stable identifier.", - "type": "string" - }, - "name": { - "$ref": "#/$defs/ExtractedString", - "description": "The region as the source names it -- aSCC, left dlPFC parcel, salience network. Its own words: normalization to an anatomical vocabulary happens downstream and needs the original to map from." + "description": "The atlas, parcellation or template it came from, as named -- AAL, WFU_Pickatlas, Schaefer 400 -- with version or parcel index when given. `definition_method: atlas` records that one was used, not which, which is not enough to pool two studies." }, - "region_type": { + "description": { "anyOf": [ { - "$ref": "#/$defs/ExtractedRegionType" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "What kind of thing the region is, which `definition_method` does not settle: an atlas parcel and an anatomical structure can both come from an atlas, and a network is neither." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "How the source defines the region, including any geometry it states: \"8 mm spheres centered at [0 36 -6]\". Definitional coordinates have no structured slot and belong here, where the evidence span makes them checkable; result coordinates reach the record through the table parser instead." } }, "required": [ @@ -6690,25 +6690,18 @@ "additionalProperties": false, "description": "Payload for a representational-similarity analysis. The direction of a model-brain association is a continuous ModelTerm on the analysis's ModelEstimation, with the model RDM as the term, tested by the Effect through a `Cell`. Before choosing this family, check that the association is at the RDM level: a similarity regressor entered into a univariate GLM is a parametric modulation, not RSA.", "properties": { - "competing_models": { + "model": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Other models the reported model was compared against." - }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "SimilarityDetails" - ], - "type": "string" + "description": "The model RDM tested. Unset for a genuinely model-free test, such as within- versus between-category pattern similarity, or dissimilarity against zero -- a single signed cell rather than a comparison." }, - "model": { + "neural_representation": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -6717,9 +6710,9 @@ "type": "null" } ], - "description": "The model RDM tested. Unset for a genuinely model-free test, such as within- versus between-category pattern similarity, or dissimilarity against zero -- a single signed cell rather than a comparison." + "description": "How the neural pattern was defined, such as the voxel set or time window used." }, - "neural_representation": { + "similarity_metric": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -6728,18 +6721,25 @@ "type": "null" } ], - "description": "How the neural pattern was defined, such as the voxel set or time window used." + "description": "Similarity or distance metric. The RDM distance metric and the model-brain association method are different facts, and crossnobis values are not comparable with correlation-distance values." }, - "similarity_metric": { + "competing_models": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "Similarity or distance metric. The RDM distance metric and the model-brain association method are different facts, and crossnobis values are not comparable with correlation-distance values." + "description": "Other models the reported model was compared against." + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "SimilarityDetails" + ], + "type": "string" } }, "required": [ @@ -6786,7 +6786,11 @@ "additionalProperties": false, "description": "A test statistic and its degrees of freedom. Statistic values -- peak magnitudes, p-values, cluster extents -- belong to the reported coordinates.", "properties": { - "degrees_of_freedom_denominator": { + "family": { + "$ref": "#/$defs/ExtractedStatisticFamily", + "description": "The statistic used, independent of the thresholding procedure. A t-test corrected by permutation is still a `t`." + }, + "degrees_of_freedom_numerator": { "anyOf": [ { "$ref": "#/$defs/ExtractedNumber" @@ -6795,9 +6799,9 @@ "type": "null" } ], - "description": "Denominator degrees of freedom, used alone for t, correlation, and chi_square. Both are unset for z. Float, so corrected degrees of freedom such as a Greenhouse-Geisser adjustment can be recorded as reported." + "description": "Numerator degrees of freedom, normally populated only for f and likelihood_ratio. A single-degree-of-freedom statistic leaves this unset, since t(151) is F(1,151)." }, - "degrees_of_freedom_numerator": { + "degrees_of_freedom_denominator": { "anyOf": [ { "$ref": "#/$defs/ExtractedNumber" @@ -6806,11 +6810,7 @@ "type": "null" } ], - "description": "Numerator degrees of freedom, normally populated only for f and likelihood_ratio. A single-degree-of-freedom statistic leaves this unset, since t(151) is F(1,151)." - }, - "family": { - "$ref": "#/$defs/ExtractedStatisticFamily", - "description": "The statistic used, independent of the thresholding procedure. A t-test corrected by permutation is still a `t`." + "description": "Denominator degrees of freedom, used alone for t, correlation, and chi_square. Both are unset for z. Float, so corrected degrees of freedom such as a Greenhouse-Geisser adjustment can be recorded as reported." } }, "required": [ @@ -6851,61 +6851,21 @@ "additionalProperties": false, "description": "The publication-level record that contains the groups, tasks, design, acquisitions, preprocessing pipelines, model estimations, analyses, and reported result tables belonging to one study. Conditions belong to the task they are part of, not to the study.", "properties": { - "acquisitions": { - "description": "Image or signal acquisition protocols used in the study.", - "items": { - "anyOf": [ - { - "$ref": "#/$defs/MRI" - }, - { - "$ref": "#/$defs/EEG" - }, - { - "$ref": "#/$defs/FNIRS" - }, - { - "$ref": "#/$defs/PET" - }, - { - "$ref": "#/$defs/OtherModality" - } - ] - }, - "type": [ - "array", - "null" - ] - }, - "analyses": { - "description": "Distinct analyses or inferential tests reported by the study, including those that found nothing (`Analysis.outcome: no_significant_effect`) and those reported without coordinates. Whether an empty list means the paper reports none is `result_reporting`'s to say, not the list's.", - "items": { - "$ref": "#/$defs/Analysis" - }, - "type": [ - "array", - "null" - ] + "extraction_metadata": { + "$ref": "#/$defs/ExtractionMetadata", + "description": "Provenance for this extraction record." }, - "assessments": { - "description": "Measures of participants that were taken independently of an acquisition.", - "items": { - "$ref": "#/$defs/Assessment" - }, + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": [ - "array", + "string", "null" ] }, - "coordinate_sets": { - "description": "Every group of coordinates the paper reports, in its tables or its text, with what each is for.", - "items": { - "$ref": "#/$defs/CoordinateSet" - }, - "type": [ - "array", - "null" - ] + "local_id": { + "description": "Stable identifier for the study record, such as a DOI-based or repository-local identifier.", + "type": "string" }, "description": { "anyOf": [ @@ -6918,6 +6878,17 @@ ], "description": "Brief free-text summary of the study aims and scope." }, + "hypothesis": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedStringList" + }, + { + "type": "null" + } + ], + "description": "The study's own statement of what it expected to find, quoted from the paper -- usually the closing paragraph of the Introduction, in the form \"we hypothesized that\" or \"we predicted that\". Verbatim but for citation markers, one entry per prediction, so a study stating several gets several.\nA prediction, not a purpose. \"The aim of this study was to examine whether connectivity differs between patients and controls\" says what was tested and takes no value here; \"we expected connectivity to be lower in patients\" says what was expected and does. Recording an aim as a hypothesis asserts a direction the study never committed to, which is precisely what a reader consults this field to check.\nAbsent when the paper states no prediction, which is common: never paraphrase an aim, an abstract sentence, or a finding into one. Where the only statement is in the Discussion -- \"consistent with our hypothesis that connectivity would be lower\" -- quote that clause, which is still the study's account of what it predicted rather than `Analysis.interpretations`, the source's account of what it found." + }, "design": { "anyOf": [ { @@ -6929,99 +6900,126 @@ ], "description": "The study's design outside its tasks: how participants were assigned, the arms they were assigned to, and the occasions data were collected on. The narrative design description is this record's own `description`." }, - "devices": { - "description": "The scanners, amplifiers, or recording systems the study collected data on. Declared here once and referenced by identifier.", + "groups": { + "description": "Participant or demographic groups.", "items": { - "$ref": "#/$defs/Device" + "$ref": "#/$defs/Group" }, "type": [ "array", "null" ] }, - "external_datasets": { - "description": "Named external datasets used, cited, or made available by the study.", + "tasks": { + "description": "Task, resting-state, stimulation, passive-viewing, or other meaningful participant paradigms associated with study acquisitions. Each holds the conditions it was made of.", "items": { - "$ref": "#/$defs/ExternalDataset" + "$ref": "#/$defs/Task" }, "type": [ "array", "null" ] }, - "extraction_metadata": { - "$ref": "#/$defs/ExtractionMetadata", - "description": "Provenance for this extraction record." + "assessments": { + "description": "Measures of participants that were taken independently of an acquisition.", + "items": { + "$ref": "#/$defs/Assessment" + }, + "type": [ + "array", + "null" + ] }, - "groups": { - "description": "Participant or demographic groups.", + "regions": { + "description": "Brain regions the study delimited and then used -- as connectivity seeds or targets, as the search space an analysis was restricted to, as the levels of a factor comparing regions, or as the signal a model term carries. Declared here once and referenced by identifier wherever used.", "items": { - "$ref": "#/$defs/Group" + "$ref": "#/$defs/Region" }, "type": [ "array", "null" ] }, - "hypothesis": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedStringList" - }, - { - "type": "null" - } - ], - "description": "The study's own statement of what it expected to find, quoted from the paper -- usually the closing paragraph of the Introduction, in the form \"we hypothesized that\" or \"we predicted that\". Verbatim but for citation markers, one entry per prediction, so a study stating several gets several.\nA prediction, not a purpose. \"The aim of this study was to examine whether connectivity differs between patients and controls\" says what was tested and takes no value here; \"we expected connectivity to be lower in patients\" says what was expected and does. Recording an aim as a hypothesis asserts a direction the study never committed to, which is precisely what a reader consults this field to check.\nAbsent when the paper states no prediction, which is common: never paraphrase an aim, an abstract sentence, or a finding into one. Where the only statement is in the Discussion -- \"consistent with our hypothesis that connectivity would be lower\" -- quote that clause, which is still the study's account of what it predicted rather than `Analysis.interpretations`, the source's account of what it found." + "devices": { + "description": "The scanners, amplifiers, or recording systems the study collected data on. Declared here once and referenced by identifier.", + "items": { + "$ref": "#/$defs/Device" + }, + "type": [ + "array", + "null" + ] }, - "inference_settings": { - "description": "Thresholding and correction schemes used by one or more analyses. Declared here once and referenced by identifier.", + "acquisitions": { + "description": "Image or signal acquisition protocols used in the study.", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/MRI" + }, + { + "$ref": "#/$defs/EEG" + }, + { + "$ref": "#/$defs/FNIRS" + }, + { + "$ref": "#/$defs/PET" + }, + { + "$ref": "#/$defs/OtherModality" + } + ] + }, + "type": [ + "array", + "null" + ] + }, + "preprocessings": { + "description": "Preprocessing pipelines applied before statistical analysis.", "items": { - "$ref": "#/$defs/InferenceSettings" + "$ref": "#/$defs/Preprocessing" }, "type": [ "array", "null" ] }, - "local_id": { - "description": "Stable identifier for the study record, such as a DOI-based or repository-local identifier.", - "type": "string" - }, - "measures": { - "description": "The scientific quantities the study measured, used by one or more analyses. Declared here once and referenced by identifier.", + "model_estimations": { + "description": "Model estimation records used by one or more analyses.", "items": { - "$ref": "#/$defs/Measure" + "$ref": "#/$defs/ModelEstimation" }, "type": [ "array", "null" ] }, - "model_estimations": { - "description": "Model estimation records used by one or more analyses.", + "inference_settings": { + "description": "Thresholding and correction schemes used by one or more analyses. Declared here once and referenced by identifier.", "items": { - "$ref": "#/$defs/ModelEstimation" + "$ref": "#/$defs/InferenceSettings" }, "type": [ "array", "null" ] }, - "preprocessings": { - "description": "Preprocessing pipelines applied before statistical analysis.", + "measures": { + "description": "The scientific quantities the study measured, used by one or more analyses. Declared here once and referenced by identifier.", "items": { - "$ref": "#/$defs/Preprocessing" + "$ref": "#/$defs/Measure" }, "type": [ "array", "null" ] }, - "regions": { - "description": "Brain regions the study delimited and then used -- as connectivity seeds or targets, as the search space an analysis was restricted to, as the levels of a factor comparing regions, or as the signal a model term carries. Declared here once and referenced by identifier wherever used.", + "analyses": { + "description": "Distinct analyses or inferential tests reported by the study, including those that found nothing (`Analysis.outcome: no_significant_effect`) and those reported without coordinates. Whether an empty list means the paper reports none is `result_reporting`'s to say, not the list's.", "items": { - "$ref": "#/$defs/Region" + "$ref": "#/$defs/Analysis" }, "type": [ "array", @@ -7039,28 +7037,30 @@ ], "description": "Whether the paper reports inferential analyses of its own. An empty `analyses` list is otherwise ambiguous: a methods or data paper that tests nothing and a paper the extractor read badly look the same. With this set, `no_analyses_reported` asserts the emptiness, with the sentences that show it as evidence.\nSays nothing about whether the analyses found anything; that is each `Analysis.outcome`, summarised in `outcome_summary`." }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "tables": { + "description": "Publication tables from which results or analysis metadata were extracted.", + "items": { + "$ref": "#/$defs/Table" + }, "type": [ - "string", + "array", "null" ] }, - "tables": { - "description": "Publication tables from which results or analysis metadata were extracted.", + "coordinate_sets": { + "description": "Every group of coordinates the paper reports, in its tables or its text, with what each is for.", "items": { - "$ref": "#/$defs/Table" + "$ref": "#/$defs/CoordinateSet" }, "type": [ "array", "null" ] }, - "tasks": { - "description": "Task, resting-state, stimulation, passive-viewing, or other meaningful participant paradigms associated with study acquisitions. Each holds the conditions it was made of.", + "external_datasets": { + "description": "Named external datasets used, cited, or made available by the study.", "items": { - "$ref": "#/$defs/Task" + "$ref": "#/$defs/ExternalDataset" }, "type": [ "array", @@ -7079,6 +7079,17 @@ "additionalProperties": false, "description": "The design of the study; how participants were assigned, what they were assigned to, and the occasions on which data were collected. The arms and timepoints it holds are referenced by identifier from Condition and Group.", "properties": { + "description": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedString" + }, + { + "type": "null" + } + ], + "description": "Overall study design in narrative form, including observational or interventional status, cross-sectional or longitudinal structure, allocation, and blinding when applicable." + }, "allocation": { "anyOf": [ { @@ -7090,16 +7101,6 @@ ], "description": "How participants came to be in the arm they were in.\nIts own vocabulary, and not its neighbour's. A study with no arms takes `not_applicable` here -- \"nothing was administered, so allocation does not apply\" -- and says `observational_cohorts` in `assignment_structure`. Writing `observational_cohorts` into this slot was measured on 4 of 5 freshly extracted observational papers: the value is accepted, because the vocabulary is open, and it answers a different question than the one asked. This slot is the only record of whether anything was administered at all." }, - "arms": { - "description": "The arms of the study, active and comparator alike. Every reference to an Arm elsewhere is by identifier, so this list holds the records themselves.", - "items": { - "$ref": "#/$defs/Arm" - }, - "type": [ - "array", - "null" - ] - }, "assignment_structure": { "anyOf": [ { @@ -7122,16 +7123,15 @@ ], "description": "Who was kept unaware of the assignment." }, - "description": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedString" - }, - { - "type": "null" - } - ], - "description": "Overall study design in narrative form, including observational or interventional status, cross-sectional or longitudinal structure, allocation, and blinding when applicable." + "arms": { + "description": "The arms of the study, active and comparator alike. Every reference to an Arm elsewhere is by identifier, so this list holds the records themselves.", + "items": { + "$ref": "#/$defs/Arm" + }, + "type": [ + "array", + "null" + ] }, "timepoints": { "description": "The occasions on which data were collected.", @@ -7151,7 +7151,19 @@ "additionalProperties": false, "description": "A publication table that supplies coordinates, statistics, or contextual metadata. Raw cell structure and point-level provenance may remain in the repository's coordinate-table representation and be linked with source_path.", "properties": { - "caption": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, + "local_id": { + "description": "Stable table identifier used by Analysis foreign-key references.", + "type": "string" + }, + "table_number": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -7160,20 +7172,20 @@ "type": "null" } ], - "description": "Table caption describing the table's contents." + "description": "Printed identifier such as Table 2 or Supplementary Table S4." }, - "column_headings": { + "title": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Ordered leaf column headings retained to aid interpretation of extracted values." + "description": "Table title, when distinct from the caption." }, - "description": { + "caption": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -7182,7 +7194,7 @@ "type": "null" } ], - "description": "Summary of the table contents and its role in the reported analyses." + "description": "Table caption describing the table's contents." }, "footer": { "anyOf": [ @@ -7195,30 +7207,18 @@ ], "description": "Table notes, abbreviations, threshold statements, and footnotes." }, - "local_id": { - "description": "Stable table identifier used by Analysis foreign-key references.", - "type": "string" - }, - "purpose": { + "column_headings": { "anyOf": [ { - "$ref": "#/$defs/ExtractedTablePurpose" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "What this table is for. `reported_effect` when its rows are the foci of a tested effect, which is the common case and which is derived from the analyses that name the table; otherwise what the rows are instead -- region or ROI definitions, atlas parcels, connectivity seeds, the peaks of a decomposition's components, coordinates cited from other papers, a stimulus list, demographics, descriptive statistics with no test.\nA coordinate table is not an analysis merely by having coordinates in it, and this is the only field that says which it is. Absence means nothing decided: a table deliberately encoded as a non-analysis and one no pass considered are otherwise the same silence, and the coordinates of the first belong on the entity they locate (a Region's description) rather than on a contrast that never produced them.\nA per-table answer, so it cannot be right for a table whose row groups serve different purposes -- results beside the ROIs they were tested in. Each row group states its own role in a `CoordinateSet`." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "Ordered leaf column headings retained to aid interpretation of extracted values." }, - "table_number": { + "description": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -7227,18 +7227,18 @@ "type": "null" } ], - "description": "Printed identifier such as Table 2 or Supplementary Table S4." + "description": "Summary of the table contents and its role in the reported analyses." }, - "title": { + "purpose": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedTablePurpose" }, { "type": "null" } ], - "description": "Table title, when distinct from the caption." + "description": "What this table is for. `reported_effect` when its rows are the foci of a tested effect, which is the common case and which is derived from the analyses that name the table; otherwise what the rows are instead -- region or ROI definitions, atlas parcels, connectivity seeds, the peaks of a decomposition's components, coordinates cited from other papers, a stimulus list, demographics, descriptive statistics with no test.\nA coordinate table is not an analysis merely by having coordinates in it, and this is the only field that says which it is. Absence means nothing decided: a table deliberately encoded as a non-analysis and one no pass considered are otherwise the same silence, and the coordinates of the first belong on the entity they locate (a Region's description) rather than on a contrast that never produced them.\nA per-table answer, so it cannot be right for a table whose row groups serve different purposes -- results beside the ROIs they were tested in. Each row group states its own role in a `CoordinateSet`." } }, "required": [ @@ -7267,28 +7267,18 @@ "additionalProperties": false, "description": "A meaningful participant paradigm, such as a task, resting-state protocol, stimulation, or passive viewing, performed in association with an acquisition. A Task record is required wherever the study models conditions, a resting-state protocol included. Acquisitions with no paradigm do not require one, even in a longitudinal or interventional study: a structural comparison across occasions is carried by the Effect's timepoint terms and linked through Analysis.acquisitions.", "properties": { - "acquisitions": { - "description": "The acquisition protocols used to collect data for this task. Exclude structural scans unless they are part of the task itself.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Acquisition" - }, - "conditions": { - "description": "The conditions this task was made of. These are the different states/modes of the task.", - "items": { - "$ref": "#/$defs/Condition" - }, + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": [ - "array", + "string", "null" ] }, - "description": { + "local_id": { + "type": "string" + }, + "name": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -7297,20 +7287,30 @@ "type": "null" } ], - "description": "Complete narrative description of what participants experienced and did." + "description": "Short, unique label for the task or paradigm; corresponds conceptually to BIDS TaskName when applicable." }, - "design_type": { + "paradigm": { "anyOf": [ { - "$ref": "#/$defs/ExtractedDesignType" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "How the task's events were modelled in time. Open vocabulary: write the source's own wording in the field when nothing here fits." + "description": "The name with every qualifier that says how this version varies taken off and moved to its own field: `Stroop task` for an emotional Stroop task, `flanker task` for an auditory flanker task, `n-back task` for a food-picture n-back, `incentive delay task` for a monetary incentive delay task. The same as `name` when nothing qualifies it.\nThis is the extractor's split, made the same way in every paper. Whether the stem or the qualified name is the paradigm is not a fact one paper can state; it is decided across the corpus, in `paradigm_category`." }, - "instructions": { + "variant_qualifiers": { + "description": "The words of `name` that `paradigm` leaves out, each with the axis it varies the paradigm on. Each is also stated in its own field: a stimulus-content qualifier in `stimulus_content`, a modality qualifier in `stimulus_modality` or `response_modality`, a timing qualifier in `design_type`. Empty when `paradigm` equals `name`.", + "items": { + "$ref": "#/$defs/TaskQualifier" + }, + "type": [ + "array", + "null" + ] + }, + "description": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -7319,23 +7319,41 @@ "type": "null" } ], - "description": "Task- or paradigm-specific instructions given to participants." + "description": "Complete narrative description of what participants experienced and did." }, - "local_id": { - "type": "string" + "conditions": { + "description": "The conditions this task was made of. These are the different states/modes of the task.", + "items": { + "$ref": "#/$defs/Condition" + }, + "type": [ + "array", + "null" + ] }, - "name": { + "design_type": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedDesignType" }, { "type": "null" } ], - "description": "Short, unique label for the task or paradigm; corresponds conceptually to BIDS TaskName when applicable." + "description": "How the task's events were modelled in time. Open vocabulary: write the source's own wording in the field when nothing here fits." }, - "paradigm": { + "acquisitions": { + "description": "The acquisition protocols used to collect data for this task. Exclude structural scans unless they are part of the task itself.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Acquisition" + }, + "presentation_software": { "anyOf": [ { "$ref": "#/$defs/ExtractedString" @@ -7344,31 +7362,31 @@ "type": "null" } ], - "description": "The name with every qualifier that says how this version varies taken off and moved to its own field: `Stroop task` for an emotional Stroop task, `flanker task` for an auditory flanker task, `n-back task` for a food-picture n-back, `incentive delay task` for a monetary incentive delay task. The same as `name` when nothing qualifies it.\nThis is the extractor's split, made the same way in every paper. Whether the stem or the qualified name is the paradigm is not a fact one paper can state; it is decided across the corpus, in `paradigm_category`." + "description": "Stimulus-presentation software name, version, and operating system when reported." }, - "performance_measures": { + "instructions": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedString" }, { "type": "null" } ], - "description": "Behavioral variables recorded and summaries used to verify task performance." + "description": "Task- or paradigm-specific instructions given to participants." }, - "presentation_software": { + "references": { "anyOf": [ { - "$ref": "#/$defs/ExtractedString" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "Stimulus-presentation software name, version, and operating system when reported." + "description": "Works the source cites for this task, exactly as it writes them: an author-year citation or a bracketed reference number, kept in the source's own punctuation. Copy the in-text citation; do not resolve, expand or look it up.\nMultivalued -- a task may cite the paradigm's origin, an adaptation, and the stimulus set separately. Empty when the source cites nothing for the task." }, - "references": { + "stimuli": { "anyOf": [ { "$ref": "#/$defs/ExtractedStringList" @@ -7377,69 +7395,51 @@ "type": "null" } ], - "description": "Works the source cites for this task, exactly as it writes them: an author-year citation or a bracketed reference number, kept in the source's own punctuation. Copy the in-text citation; do not resolve, expand or look it up.\nMultivalued -- a task may cite the paradigm's origin, an adaptation, and the stimulus set separately. Empty when the source cites nothing for the task." + "description": "The materials presented to participants, in the source's own terms -- the stimulus set, its name, how many items. One entry per distinct material; `stimulus_modality` carries the sensory channel." }, - "response_modality": { + "stimulus_content": { "anyOf": [ { - "$ref": "#/$defs/ExtractedResponseModalityList" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "How participants responded. Multivalued because a paradigm often collects more than one -- a button press for accuracy and a spoken report afterwards." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] + "description": "What the task's stimuli were about when that holds across all its conditions -- food pictures at every n-back load, emotional words throughout a Stroop -- which `Condition.stimulus_content` leaves unset because it does not tell the conditions apart. The task-level half of the stimulus facet: a meta-analysis can ask for food stimuli across paradigms, or for Stroop tasks across stimuli, without either question splitting the other." }, - "stimuli": { + "stimulus_modality": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedStimulusModalityList" }, { "type": "null" } ], - "description": "The materials presented to participants, in the source's own terms -- the stimulus set, its name, how many items. One entry per distinct material; `stimulus_modality` carries the sensory channel." + "description": "The sensory channel the stimuli were delivered through. Multivalued because a paradigm may use more than one -- a photograph with a spoken label.\nOften not stated in those words: a source naming IAPS, NimStim, Ekman faces or film clips has said `visual` without using the word. Inferring it from such a cue is intended, and the cue is what belongs in the evidence -- not the whole methods section." }, - "stimulus_content": { + "response_modality": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStringList" + "$ref": "#/$defs/ExtractedResponseModalityList" }, { "type": "null" } ], - "description": "What the task's stimuli were about when that holds across all its conditions -- food pictures at every n-back load, emotional words throughout a Stroop -- which `Condition.stimulus_content` leaves unset because it does not tell the conditions apart. The task-level half of the stimulus facet: a meta-analysis can ask for food stimuli across paradigms, or for Stroop tasks across stimuli, without either question splitting the other." + "description": "How participants responded. Multivalued because a paradigm often collects more than one -- a button press for accuracy and a spoken report afterwards." }, - "stimulus_modality": { + "performance_measures": { "anyOf": [ { - "$ref": "#/$defs/ExtractedStimulusModalityList" + "$ref": "#/$defs/ExtractedStringList" }, { "type": "null" } ], - "description": "The sensory channel the stimuli were delivered through. Multivalued because a paradigm may use more than one -- a photograph with a spoken label.\nOften not stated in those words: a source naming IAPS, NimStim, Ekman faces or film clips has said `visual` without using the word. Inferring it from such a cue is intended, and the cue is what belongs in the evidence -- not the whole methods section." - }, - "variant_qualifiers": { - "description": "The words of `name` that `paradigm` leaves out, each with the axis it varies the paradigm on. Each is also stated in its own field: a stimulus-content qualifier in `stimulus_content`, a modality qualifier in `stimulus_modality` or `response_modality`, a timing qualifier in `design_type`. Empty when `paradigm` equals `name`.", - "items": { - "$ref": "#/$defs/TaskQualifier" - }, - "type": [ - "array", - "null" - ] + "description": "Behavioral variables recorded and summaries used to verify task performance." } }, "required": [ @@ -7452,13 +7452,13 @@ "additionalProperties": false, "description": "One word or phrase of a task's name that varies its paradigm rather than naming it: the `emotional` of an emotional Stroop task, the `auditory` of an auditory flanker.", "properties": { - "axis": { - "$ref": "#/$defs/ExtractedTaskVariantAxis", - "description": "What the qualifier varies." - }, "qualifier": { "$ref": "#/$defs/ExtractedString", "description": "The qualifier as the name prints it." + }, + "axis": { + "$ref": "#/$defs/ExtractedTaskVariantAxis", + "description": "What the qualifier varies." } }, "required": [ @@ -7493,6 +7493,14 @@ "additionalProperties": false, "description": "One occasion on which data were collected, and where it sits relative to the intervention. Declared once on `StudyDesign.timepoints` and referenced by identifier; `FactorLevel.timepoints` is the only slot that references one, so it is the whole of how an occasion reaches a model.\nA study that *compared* occasions did so with a `ModelTerm` whose levels name them, paradigm or no paradigm. An occasion no level references was collected but never modelled: legitimate where the record only describes when follow-up happened, a defect where the analyses report change over time, which `validate_record.py` flags. Names stay in the source's wording; what is normalized is where an occasion sits and in what order.", "properties": { + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "type": [ + "string", + "null" + ] + }, "local_id": { "type": "string" }, @@ -7500,6 +7508,10 @@ "$ref": "#/$defs/ExtractedString", "description": "Short occasion label as the source states it, for example baseline, post-treatment, six-month follow-up." }, + "relation_to_intervention": { + "$ref": "#/$defs/ExtractedTimepointRelation", + "description": "Where this occasion sits relative to the intervention. Required, so an occasion the source does not place still carries the slot, as `extraction_status: not_reported`." + }, "order": { "anyOf": [ { @@ -7511,18 +7523,6 @@ ], "description": "Position of this occasion in the study's sequence, counting from 1. Carries the ordering that `relation_to_intervention` cannot when several occasions share a relation -- the earliest `post_intervention` occasion is the study's endpoint, later ones are follow-ups." }, - "relation_to_intervention": { - "$ref": "#/$defs/ExtractedTimepointRelation", - "description": "Where this occasion sits relative to the intervention. Required, so an occasion the source does not place still carries the slot, as `extraction_status: not_reported`." - }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", - "type": [ - "string", - "null" - ] - }, "time_from_intervention": { "anyOf": [ { @@ -7592,61 +7592,21 @@ "description": "The publication-level record that contains the groups, tasks, design, acquisitions, preprocessing pipelines, model estimations, analyses, and reported result tables belonging to one study. Conditions belong to the task they are part of, not to the study.", "metamodel_version": "1.12.0", "properties": { - "acquisitions": { - "description": "Image or signal acquisition protocols used in the study.", - "items": { - "anyOf": [ - { - "$ref": "#/$defs/MRI" - }, - { - "$ref": "#/$defs/EEG" - }, - { - "$ref": "#/$defs/FNIRS" - }, - { - "$ref": "#/$defs/PET" - }, - { - "$ref": "#/$defs/OtherModality" - } - ] - }, - "type": [ - "array", - "null" - ] - }, - "analyses": { - "description": "Distinct analyses or inferential tests reported by the study, including those that found nothing (`Analysis.outcome: no_significant_effect`) and those reported without coordinates. Whether an empty list means the paper reports none is `result_reporting`'s to say, not the list's.", - "items": { - "$ref": "#/$defs/Analysis" - }, - "type": [ - "array", - "null" - ] + "extraction_metadata": { + "$ref": "#/$defs/ExtractionMetadata", + "description": "Provenance for this extraction record." }, - "assessments": { - "description": "Measures of participants that were taken independently of an acquisition.", - "items": { - "$ref": "#/$defs/Assessment" - }, + "stable_id": { + "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.", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": [ - "array", + "string", "null" ] }, - "coordinate_sets": { - "description": "Every group of coordinates the paper reports, in its tables or its text, with what each is for.", - "items": { - "$ref": "#/$defs/CoordinateSet" - }, - "type": [ - "array", - "null" - ] + "local_id": { + "description": "Stable identifier for the study record, such as a DOI-based or repository-local identifier.", + "type": "string" }, "description": { "anyOf": [ @@ -7659,6 +7619,17 @@ ], "description": "Brief free-text summary of the study aims and scope." }, + "hypothesis": { + "anyOf": [ + { + "$ref": "#/$defs/ExtractedStringList" + }, + { + "type": "null" + } + ], + "description": "The study's own statement of what it expected to find, quoted from the paper -- usually the closing paragraph of the Introduction, in the form \"we hypothesized that\" or \"we predicted that\". Verbatim but for citation markers, one entry per prediction, so a study stating several gets several.\nA prediction, not a purpose. \"The aim of this study was to examine whether connectivity differs between patients and controls\" says what was tested and takes no value here; \"we expected connectivity to be lower in patients\" says what was expected and does. Recording an aim as a hypothesis asserts a direction the study never committed to, which is precisely what a reader consults this field to check.\nAbsent when the paper states no prediction, which is common: never paraphrase an aim, an abstract sentence, or a finding into one. Where the only statement is in the Discussion -- \"consistent with our hypothesis that connectivity would be lower\" -- quote that clause, which is still the study's account of what it predicted rather than `Analysis.interpretations`, the source's account of what it found." + }, "design": { "anyOf": [ { @@ -7670,69 +7641,86 @@ ], "description": "The study's design outside its tasks: how participants were assigned, the arms they were assigned to, and the occasions data were collected on. The narrative design description is this record's own `description`." }, - "devices": { - "description": "The scanners, amplifiers, or recording systems the study collected data on. Declared here once and referenced by identifier.", + "groups": { + "description": "Participant or demographic groups.", "items": { - "$ref": "#/$defs/Device" + "$ref": "#/$defs/Group" }, "type": [ "array", "null" ] }, - "external_datasets": { - "description": "Named external datasets used, cited, or made available by the study.", + "tasks": { + "description": "Task, resting-state, stimulation, passive-viewing, or other meaningful participant paradigms associated with study acquisitions. Each holds the conditions it was made of.", "items": { - "$ref": "#/$defs/ExternalDataset" + "$ref": "#/$defs/Task" }, "type": [ "array", "null" ] }, - "extraction_metadata": { - "$ref": "#/$defs/ExtractionMetadata", - "description": "Provenance for this extraction record." - }, - "groups": { - "description": "Participant or demographic groups.", + "assessments": { + "description": "Measures of participants that were taken independently of an acquisition.", "items": { - "$ref": "#/$defs/Group" + "$ref": "#/$defs/Assessment" }, "type": [ "array", "null" ] }, - "hypothesis": { - "anyOf": [ - { - "$ref": "#/$defs/ExtractedStringList" - }, - { - "type": "null" - } - ], - "description": "The study's own statement of what it expected to find, quoted from the paper -- usually the closing paragraph of the Introduction, in the form \"we hypothesized that\" or \"we predicted that\". Verbatim but for citation markers, one entry per prediction, so a study stating several gets several.\nA prediction, not a purpose. \"The aim of this study was to examine whether connectivity differs between patients and controls\" says what was tested and takes no value here; \"we expected connectivity to be lower in patients\" says what was expected and does. Recording an aim as a hypothesis asserts a direction the study never committed to, which is precisely what a reader consults this field to check.\nAbsent when the paper states no prediction, which is common: never paraphrase an aim, an abstract sentence, or a finding into one. Where the only statement is in the Discussion -- \"consistent with our hypothesis that connectivity would be lower\" -- quote that clause, which is still the study's account of what it predicted rather than `Analysis.interpretations`, the source's account of what it found." + "regions": { + "description": "Brain regions the study delimited and then used -- as connectivity seeds or targets, as the search space an analysis was restricted to, as the levels of a factor comparing regions, or as the signal a model term carries. Declared here once and referenced by identifier wherever used.", + "items": { + "$ref": "#/$defs/Region" + }, + "type": [ + "array", + "null" + ] }, - "inference_settings": { - "description": "Thresholding and correction schemes used by one or more analyses. Declared here once and referenced by identifier.", + "devices": { + "description": "The scanners, amplifiers, or recording systems the study collected data on. Declared here once and referenced by identifier.", "items": { - "$ref": "#/$defs/InferenceSettings" + "$ref": "#/$defs/Device" }, "type": [ "array", "null" ] }, - "local_id": { - "description": "Stable identifier for the study record, such as a DOI-based or repository-local identifier.", - "type": "string" + "acquisitions": { + "description": "Image or signal acquisition protocols used in the study.", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/MRI" + }, + { + "$ref": "#/$defs/EEG" + }, + { + "$ref": "#/$defs/FNIRS" + }, + { + "$ref": "#/$defs/PET" + }, + { + "$ref": "#/$defs/OtherModality" + } + ] + }, + "type": [ + "array", + "null" + ] }, - "measures": { - "description": "The scientific quantities the study measured, used by one or more analyses. Declared here once and referenced by identifier.", + "preprocessings": { + "description": "Preprocessing pipelines applied before statistical analysis.", "items": { - "$ref": "#/$defs/Measure" + "$ref": "#/$defs/Preprocessing" }, "type": [ "array", @@ -7749,20 +7737,30 @@ "null" ] }, - "preprocessings": { - "description": "Preprocessing pipelines applied before statistical analysis.", + "inference_settings": { + "description": "Thresholding and correction schemes used by one or more analyses. Declared here once and referenced by identifier.", "items": { - "$ref": "#/$defs/Preprocessing" + "$ref": "#/$defs/InferenceSettings" }, "type": [ "array", "null" ] }, - "regions": { - "description": "Brain regions the study delimited and then used -- as connectivity seeds or targets, as the search space an analysis was restricted to, as the levels of a factor comparing regions, or as the signal a model term carries. Declared here once and referenced by identifier wherever used.", + "measures": { + "description": "The scientific quantities the study measured, used by one or more analyses. Declared here once and referenced by identifier.", "items": { - "$ref": "#/$defs/Region" + "$ref": "#/$defs/Measure" + }, + "type": [ + "array", + "null" + ] + }, + "analyses": { + "description": "Distinct analyses or inferential tests reported by the study, including those that found nothing (`Analysis.outcome: no_significant_effect`) and those reported without coordinates. Whether an empty list means the paper reports none is `result_reporting`'s to say, not the list's.", + "items": { + "$ref": "#/$defs/Analysis" }, "type": [ "array", @@ -7780,28 +7778,30 @@ ], "description": "Whether the paper reports inferential analyses of its own. An empty `analyses` list is otherwise ambiguous: a methods or data paper that tests nothing and a paper the extractor read badly look the same. With this set, `no_analyses_reported` asserts the emptiness, with the sentences that show it as evidence.\nSays nothing about whether the analyses found anything; that is each `Analysis.outcome`, summarised in `outcome_summary`." }, - "stable_id": { - "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.", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "tables": { + "description": "Publication tables from which results or analysis metadata were extracted.", + "items": { + "$ref": "#/$defs/Table" + }, "type": [ - "string", + "array", "null" ] }, - "tables": { - "description": "Publication tables from which results or analysis metadata were extracted.", + "coordinate_sets": { + "description": "Every group of coordinates the paper reports, in its tables or its text, with what each is for.", "items": { - "$ref": "#/$defs/Table" + "$ref": "#/$defs/CoordinateSet" }, "type": [ "array", "null" ] }, - "tasks": { - "description": "Task, resting-state, stimulation, passive-viewing, or other meaningful participant paradigms associated with study acquisitions. Each holds the conditions it was made of.", + "external_datasets": { + "description": "Named external datasets used, cited, or made available by the study.", "items": { - "$ref": "#/$defs/Task" + "$ref": "#/$defs/ExternalDataset" }, "type": [ "array", diff --git a/src/study_schema/jsonschema/parsed-paper.schema.json b/src/study_schema/jsonschema/parsed-paper.schema.json index 9d1635f..8c355f4 100644 --- a/src/study_schema/jsonschema/parsed-paper.schema.json +++ b/src/study_schema/jsonschema/parsed-paper.schema.json @@ -27,22 +27,12 @@ "additionalProperties": false, "description": "pondie's verdict on one analysis of the parse a revision revises, reached while extracting the whole record. The verdict overrides the original's proposal, grouping and role alike.\nWhat a verdict does to what is stored against the analysis -- claims, their evidence, and users' votes on them:\n- accept, relabel: nothing moves. The key is unchanged, so everything stored against\n it applies to the revision as it stands.\n- edit: everything is copied to the one analysis in `replaced_by`, whose points are\n the corrected ones.\n- split: everything is copied to each analysis in `replaced_by`. - merge: everything stored against each merged analysis is copied to the one\n analysis they merge into. Two copies of the same field with the same value become\n one claim; with different values they stay competing claims, resolved like any\n other.\n- omit: nothing is copied. - add: nothing to copy; the analysis is new.\nEvery copy records the key and parse it was copied from, so a reviewer can tell a copied claim from one made against the revision. The revised parse and everything stored against it are kept, unchanged, as history: superseded rather than deleted, and left out of projections and studysets.", "properties": { - "evidence": { - "description": "The sentences of the paper the verdict rests on.", - "items": { - "$ref": "#/$defs/TextSpan" - }, - "type": [ - "array", - "null" - ] - }, "key": { "description": "The revised parse's analysis key; for `add`, the new analysis's key in this revision.", "type": "string" }, - "reason": { - "type": "string" + "verdict": { + "$ref": "#/$defs/AuditVerdict" }, "replaced_by": { "description": "The keys in this revision that take the analysis's place: itself for accept and relabel, the one corrected analysis for edit, the replacements for split and merge, none for omit.", @@ -68,8 +58,18 @@ ], "description": "For relabel, the role the points have instead." }, - "verdict": { - "$ref": "#/$defs/AuditVerdict" + "reason": { + "type": "string" + }, + "evidence": { + "description": "The sentences of the paper the verdict rests on.", + "items": { + "$ref": "#/$defs/TextSpan" + }, + "type": [ + "array", + "null" + ] } }, "required": [ @@ -95,8 +95,8 @@ "additionalProperties": false, "description": "Every identifier known for the paper when the artifact was written. Carried whole, so no consumer has to choose between them or look one up. `doi`, `pmid` and `pmcid` name the version of record; a preprint's identifiers are kept beside them in the `preprint_` fields, so a paper and its preprint are one article.", "properties": { - "doi": { - "description": "The DOI of the version of record. Never a preprint server's DOI, which is in `preprint_dois`.", + "pmid": { + "description": "PubMed's record of the version of record. Never PubMed's record of a preprint, which is `preprint_pmid`.", "type": [ "string", "null" @@ -109,8 +109,8 @@ "null" ] }, - "pmid": { - "description": "PubMed's record of the version of record. Never PubMed's record of a preprint, which is `preprint_pmid`.", + "doi": { + "description": "The DOI of the version of record. Never a preprint server's DOI, which is in `preprint_dois`.", "type": [ "string", "null" @@ -126,15 +126,15 @@ "null" ] }, - "preprint_pmcid": { - "description": "PubMed Central's copy of the preprint.", + "preprint_pmid": { + "description": "PubMed's record of the preprint, which PubMed types `Preprint`.", "type": [ "string", "null" ] }, - "preprint_pmid": { - "description": "PubMed's record of the preprint, which PubMed types `Preprint`.", + "preprint_pmcid": { + "description": "PubMed Central's copy of the preprint.", "type": [ "string", "null" @@ -155,23 +155,23 @@ "additionalProperties": false, "description": "Who made an artifact, from what, for which paper.", "properties": { - "article_id": { - "description": "The ingestion catalog's key for the paper. Minted once, at download, and never recomputed from the identifiers below.", - "type": "string" - }, "artifact_kind": { "$ref": "#/$defs/ArtifactKind" }, - "created_at": { - "format": "date-time", - "type": [ - "string", - "null" - ] + "schema_version": { + "description": "The version of this schema the artifact validates against.", + "type": "string" + }, + "article_id": { + "description": "The ingestion catalog's key for the paper. Minted once, at download, and never recomputed from the identifiers below.", + "type": "string" }, "identifiers": { "$ref": "#/$defs/ArticleIdentifiers" }, + "producer": { + "$ref": "#/$defs/Producer" + }, "inputs": { "description": "Every artifact this one was made from, by fingerprint.", "items": { @@ -182,19 +182,19 @@ "null" ] }, + "created_at": { + "format": "date-time", + "type": [ + "string", + "null" + ] + }, "neurostore_base_study_id": { "description": "Set once the paper has been uploaded; neurostore's BaseStudy id. An upload pointer, not a bibliographic identifier, so it sits here and not in `identifiers`.", "type": [ "string", "null" ] - }, - "producer": { - "$ref": "#/$defs/Producer" - }, - "schema_version": { - "description": "The version of this schema the artifact validates against.", - "type": "string" } }, "required": [ @@ -261,24 +261,8 @@ ] }, "properties": { - "abstract": { - "description": "Absent (null) when no source gave one; never an empty string.", - "pattern": "\\S", - "type": [ - "string", - "null" - ] - }, - "abstract_provider": { - "anyOf": [ - { - "$ref": "#/$defs/MetadataSource" - }, - { - "type": "null" - } - ], - "description": "The source whose abstract `abstract` is. It may differ from `provider`. The abstract is the publisher's abstract of the version `license` describes, so its licence is `license`." + "title": { + "type": "string" }, "authors": { "description": "Author names in published order.", @@ -290,68 +274,47 @@ "null" ] }, - "corrections": { - "description": "Notices attached to the paper -- errata, retractions, expressions of concern -- as the indexer lists them. A retracted paper is still parsed and stored, marked by its `retraction` notice, and left out of studysets by default; a filter can ask for it explicitly.", - "items": { - "$ref": "#/$defs/Correction" - }, + "journal": { "type": [ - "array", + "string", "null" ] }, - "issue": { + "publication_year": { + "description": "Year in which the study was first published or made publicly available.", + "minimum": 1600, "type": [ - "string", + "integer", "null" ] }, - "journal": { + "volume": { "type": [ "string", "null" ] }, - "keywords": { - "items": { - "type": "string" - }, + "issue": { "type": [ - "array", + "string", "null" ] }, - "language": { - "description": "Languages of the article, as the E-utilities API reports them -- the three-letter codes PubMed assigns, `eng` and the rest, and not read off the paper: the text a reader is given is in one language whatever the article is indexed as.\nAt least six of the benchmark's sixteen meta-analyses state \"English language\" as an inclusion criterion. On the 1,804 records measured it excludes exactly one paper, so it buys precision rather than recall; the criterion is stated, so the field states it.", - "items": { - "type": "string" - }, + "pages": { "type": [ - "array", + "string", "null" ] }, - "license": { - "$ref": "#/$defs/License", - "description": "The paper's licence: the version of record's, or the latest preprint's while the header's `publication_status` is `preprint`. Not the licence of a copy of another version, which that copy carries." - }, - "oa_status": { - "anyOf": [ - { - "$ref": "#/$defs/OaStatus" - }, - { - "type": "null" - } - ] - }, - "pages": { + "abstract": { + "description": "Absent (null) when no source gave one; never an empty string.", + "pattern": "\\S", "type": [ "string", "null" ] }, - "provider": { + "abstract_provider": { "anyOf": [ { "$ref": "#/$defs/MetadataSource" @@ -360,7 +323,17 @@ "type": "null" } ], - "description": "The metadata service the facts came from." + "description": "The source whose abstract `abstract` is. It may differ from `provider`. The abstract is the publisher's abstract of the version `license` describes, so its licence is `license`." + }, + "language": { + "description": "Languages of the article, as the E-utilities API reports them -- the three-letter codes PubMed assigns, `eng` and the rest, and not read off the paper: the text a reader is given is in one language whatever the article is indexed as.\nAt least six of the benchmark's sixteen meta-analyses state \"English language\" as an inclusion criterion. On the 1,804 records measured it excludes exactly one paper, so it buys precision rather than recall; the criterion is stated, so the field states it.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] }, "publication_types": { "description": "PubMed publication types for this record, verbatim as the E-utilities API returns them (https://pubmed.ncbi.nlm.nih.gov/help/#publication-types). Multivalued because PubMed assigns several: every article is `Journal Article` and the design of the study motivates other labels. `StudyType` lists the known types only; any other type PubMed returns is kept verbatim.", @@ -379,22 +352,49 @@ "null" ] }, - "publication_year": { - "description": "Year in which the study was first published or made publicly available.", - "minimum": 1600, + "keywords": { + "items": { + "type": "string" + }, "type": [ - "integer", + "array", "null" ] }, - "title": { - "type": "string" + "license": { + "$ref": "#/$defs/License", + "description": "The paper's licence: the version of record's, or the latest preprint's while the header's `publication_status` is `preprint`. Not the licence of a copy of another version, which that copy carries." }, - "volume": { + "oa_status": { + "anyOf": [ + { + "$ref": "#/$defs/OaStatus" + }, + { + "type": "null" + } + ] + }, + "corrections": { + "description": "Notices attached to the paper -- errata, retractions, expressions of concern -- as the indexer lists them. A retracted paper is still parsed and stored, marked by its `retraction` notice, and left out of studysets by default; a filter can ask for it explicitly.", + "items": { + "$ref": "#/$defs/Correction" + }, "type": [ - "string", + "array", "null" ] + }, + "provider": { + "anyOf": [ + { + "$ref": "#/$defs/MetadataSource" + }, + { + "type": "null" + } + ], + "description": "The metadata service the facts came from." } }, "required": [ @@ -422,13 +422,13 @@ "additionalProperties": false, "description": "One table cell an analysis was read from, by row and column group.", "properties": { - "column_group": { - "description": "As on ParsedPoint; 0 in a table with one block of coordinate columns.", + "row": { + "description": "The TableRow's `row`.", "minimum": 0, "type": "integer" }, - "row": { - "description": "The TableRow's `row`.", + "column_group": { + "description": "As on ParsedPoint; 0 in a table with one block of coordinate columns.", "minimum": 0, "type": "integer" } @@ -444,18 +444,9 @@ "additionalProperties": false, "description": "One in-text citation and the reference entries it names. A grouped citation (\"[3-5]\", \"(Smith, 2001; Jones, 2003)\") is one Citation with several references.", "properties": { - "confidence": { - "description": "For a heuristic alignment, how sure; absent for an exact one.", - "maximum": 1, - "minimum": 0, - "type": [ - "number", - "null" - ] - }, - "method": { - "$ref": "#/$defs/CitationAlignment", - "description": "How the marker was tied to its entries -- what its reliability rests on." + "text_span": { + "$ref": "#/$defs/TextSpan", + "description": "The citation marker in the text." }, "references": { "description": "The entries it names, by `Reference.id`. Empty when it could not be aligned.", @@ -468,9 +459,18 @@ ], "x-reference": "Reference" }, - "text_span": { - "$ref": "#/$defs/TextSpan", - "description": "The citation marker in the text." + "method": { + "$ref": "#/$defs/CitationAlignment", + "description": "How the marker was tied to its entries -- what its reliability rests on." + }, + "confidence": { + "description": "For a heuristic alignment, how sure; absent for an exact one.", + "maximum": 1, + "minimum": 0, + "type": [ + "number", + "null" + ] } }, "required": [ @@ -541,31 +541,18 @@ "additionalProperties": false, "description": "Every coordinate a paper reports, grouped into analyses. An original parse is written by the ingestion workflow; a revision is written by pondie when its audit disagrees with the original.\nA parse is never edited after it is written. A reader that disagrees with it writes a revision instead, and the revision is a complete parse on its own: neurostore can store it as a new version of the paper's analyses without consulting the parse it revises.\nAnalyses that pondie accepts or relabels keep their keys, because their name, space and points are unchanged, so claims made against them carry over to the revision untouched. Only a split, a merge or an omission changes which keys exist.", "properties": { - "analyses": { - "description": "May be empty: the parse ran and found no analysis. A paper the parse never ran on has no CoordinateParse at all, so the two are never confused, and `tables` says what reading each table found.", - "items": { - "$ref": "#/$defs/ParsedAnalysis" - }, - "type": "array" - }, "header": { "$ref": "#/$defs/ArtifactHeader", "description": "`inputs` names the parsed paper by `text_sha256`, and for a revision also the parse it revises by `parse_id`." }, - "omitted_analyses": { - "description": "Text or figure analyses the producer read and left out of `analyses`, each with the reason. A table's omissions are in `tables`. Absent when nothing was left out.", - "items": { - "$ref": "#/$defs/OmittedAnalysis" - }, - "type": [ - "array", - "null" - ] - }, "parse_id": { "description": "This parse's identity, the fingerprint every later artifact cites. Unique per paper; a sha256 of the canonical JSON of `analyses` is recommended, so two producers that reach the same analyses reach the same id.", "type": "string" }, + "text_sha256": { + "description": "The parsed paper's fingerprint. Every point's cells and spans address it.", + "type": "string" + }, "revision_of": { "description": "The `parse_id` of the parse this one revises. Absent on an original parse, which is how a reader tells the two apart.", "type": [ @@ -573,6 +560,13 @@ "null" ] }, + "analyses": { + "description": "May be empty: the parse ran and found no analysis. A paper the parse never ran on has no CoordinateParse at all, so the two are never confused, and `tables` says what reading each table found.", + "items": { + "$ref": "#/$defs/ParsedAnalysis" + }, + "type": "array" + }, "tables": { "description": "One entry per table of the parsed paper: what reading it for coordinates found, or why it was not read. A table with no entry was not considered.", "items": { @@ -583,9 +577,15 @@ "null" ] }, - "text_sha256": { - "description": "The parsed paper's fingerprint. Every point's cells and spans address it.", - "type": "string" + "omitted_analyses": { + "description": "Text or figure analyses the producer read and left out of `analyses`, each with the reason. A table's omissions are in `tables`. Absent when nothing was left out.", + "items": { + "$ref": "#/$defs/OmittedAnalysis" + }, + "type": [ + "array", + "null" + ] }, "text_sweep": { "anyOf": [ @@ -657,22 +657,23 @@ ] }, "properties": { - "license": { - "$ref": "#/$defs/License", - "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." + "route": { + "$ref": "#/$defs/CopyRoute" }, - "preprint_server": { + "version": { "anyOf": [ { - "$ref": "#/$defs/PreprintServer" + "$ref": "#/$defs/CopyVersion" }, { "type": "null" } - ] + ], + "description": "Absent when no source says which version the copy is." }, - "route": { - "$ref": "#/$defs/CopyRoute" + "license": { + "$ref": "#/$defs/License", + "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." }, "url": { "type": [ @@ -680,16 +681,15 @@ "null" ] }, - "version": { + "preprint_server": { "anyOf": [ { - "$ref": "#/$defs/CopyVersion" + "$ref": "#/$defs/PreprintServer" }, { "type": "null" } - ], - "description": "Absent when no source says which version the copy is." + ] } }, "required": [ @@ -749,12 +749,6 @@ "additionalProperties": false, "description": "One notice attached to a paper.", "properties": { - "doi": { - "type": [ - "string", - "null" - ] - }, "kind": { "anyOf": [ { @@ -770,6 +764,12 @@ "string", "null" ] + }, + "doi": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -921,22 +921,23 @@ "access": { "$ref": "#/$defs/CopyAccess" }, - "license": { - "$ref": "#/$defs/License", - "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." + "route": { + "$ref": "#/$defs/CopyRoute" }, - "preprint_server": { + "version": { "anyOf": [ { - "$ref": "#/$defs/PreprintServer" + "$ref": "#/$defs/CopyVersion" }, { "type": "null" } - ] + ], + "description": "Absent when no source says which version the copy is." }, - "route": { - "$ref": "#/$defs/CopyRoute" + "license": { + "$ref": "#/$defs/License", + "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." }, "url": { "type": [ @@ -944,16 +945,15 @@ "null" ] }, - "version": { + "preprint_server": { "anyOf": [ { - "$ref": "#/$defs/CopyVersion" + "$ref": "#/$defs/PreprintServer" }, { "type": "null" } - ], - "description": "Absent when no source says which version the copy is." + ] } }, "required": [ @@ -1022,20 +1022,20 @@ "license_id": { "const": "all-rights-reserved" }, - "stated_as": { + "stated_by": { "not": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/MetadataSource" } ] } }, - "stated_by": { + "stated_as": { "not": { "anyOf": [ { - "$ref": "#/$defs/MetadataSource" + "type": "string" } ] } @@ -1053,20 +1053,12 @@ ] }, "properties": { - "determination": { - "$ref": "#/$defs/LicenseDetermination" - }, "license_id": { "$ref": "#/$defs/LicenseId", "description": "Which licence, as OpenAlex's licence slugs name them (`cc-by`, `cc-by-nc`, ...). Not an SPDX License Identifier: sources rarely state the licence's version, and SPDX identifiers require one." }, - "stated_as": { - "description": "The statement exactly as the source gave it: a licence URL, a licence name, or a copyright line. What `other` holds, and what attribution quotes. Never empty: a source that gave no statement did not state a licence.", - "pattern": "\\S", - "type": [ - "string", - "null" - ] + "determination": { + "$ref": "#/$defs/LicenseDetermination" }, "stated_by": { "anyOf": [ @@ -1078,6 +1070,14 @@ } ], "description": "The source whose statement this is." + }, + "stated_as": { + "description": "The statement exactly as the source gave it: a licence URL, a licence name, or a copyright line. What `other` holds, and what attribution quotes. Never empty: a source that gave no statement did not state a licence.", + "pattern": "\\S", + "type": [ + "string", + "null" + ] } }, "required": [ @@ -1086,17 +1086,17 @@ ], "then": { "properties": { - "stated_as": { + "stated_by": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/MetadataSource" } ] }, - "stated_by": { + "stated_as": { "anyOf": [ { - "$ref": "#/$defs/MetadataSource" + "type": "string" } ] } @@ -1264,10 +1264,6 @@ "description": "The analysis's label as the producer read it.", "type": "string" }, - "reason": { - "description": "Why it was left out, for example that it repeats a kept analysis (the same key) or that its passage is not in the text.", - "type": "string" - }, "text_spans": { "description": "The sentences it was read from, when it was located.", "items": { @@ -1277,6 +1273,10 @@ "array", "null" ] + }, + "reason": { + "description": "Why it was left out, for example that it repeats a kept analysis (the same key) or that its passage is not in the text.", + "type": "string" } }, "required": [ @@ -1290,19 +1290,9 @@ "additionalProperties": false, "description": "The points a paper reports together for one tested effect, or for one non-result purpose such as the regions of interest an analysis used.\nIn an original parse, the grouping is the ingestion workflow's proposal, made from a table and its caption. pondie confirms or overrides it with the whole paper in view. The role is required and is set in the ingestion workflow by the `roles` classifier stage, after the analyses are identified. It is also the ingestion workflow's proposal: pondie's accept confirms it, and a relabel overrides it with a reviewed correction that wins.", "properties": { - "anchor_kind": { - "anyOf": [ - { - "$ref": "#/$defs/AnchorKind" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "For an `anchor`, what it defines." + "key": { + "description": "The analysis's identity, derived from what was extracted -- its `name`, `coordinate_space` and `points` -- never from where it was read, so the same analysis read from two sources (a table and its HTML copy, a table and the sentence restating it) reaches the same key, and a re-run that extracts the same content reaches it again. A rename or a changed point is a new key. The name is part of the key because differently named analyses can print identical points.\n`analysis#`, where `h` is the first 16 hex characters of the sha256 of the UTF-8 bytes of the compact JSON array `[, , [, ...]]` (no whitespace, non-ASCII characters unescaped). `` is `name` normalized: Unicode NFKC, casefolded, every dash (Unicode Pd and U+2212 minus) as `-`, zero-width characters and all whitespace dropped, so `PO > Sil` and `PO>Sil` are one name. `` is `coordinate_space`, or `\"\"` when absent.\nEach `` is `[, , , , [[, ], ...]]`: the coordinates rounded to 0.01 mm and written with two decimals (`\"-4.00\"`); the point's own `space` when it differs from the analysis's, else `\"\"`; and each of its `values` as its `kind` and its value written to 6 significant digits (`\"5.8\"`, `\"3.2e-12\"`). Negative zero is written as zero. Each point's values, and then the points, are sorted by their own compact JSON, in code point order.\nAn analysis with no points -- a contrast a table lists as \"n.s.\" -- hashes its name and space alone. The two halves of a sign split hold different points under different names, so each has its own key. `study_schema.keys.analysis_key` computes it.\nThis key is what the extraction record's `Analysis.source_table_analysis` and `CoordinateSet.id` name, and what neurostore stores as `Analysis.source_id`. Two analyses of one parse never share a name, space and points, so never share a key.", + "type": "string" }, "cells": { "description": "For a table analysis, every cell it was read from: each point's row and column group, and the rows that name the contrast without coordinates. Required for `origin: table`; every point's row and column group is among them.", @@ -1314,44 +1304,26 @@ "null" ] }, - "coordinate_space": { - "anyOf": [ - { - "$ref": "#/$defs/ReportedSpace" - }, - { - "type": "null" - } - ], - "description": "The space of the analysis's points; absent when the paper states none. A point states its own only when it differs." - }, - "derived_from": { - "description": "On a revision, the keys of the revised parse's analyses this one was made from by a split or a merge. Absent on an analysis carried over unchanged.", + "text_spans": { + "description": "For a text or figure analysis, the sentences or legend it was read from: those holding its coordinates, or the one stating a result that has none.", "items": { - "type": "string" + "$ref": "#/$defs/TextSpan" }, "type": [ "array", "null" ] }, - "description": { - "type": [ - "string", - "null" - ] + "origin": { + "$ref": "#/$defs/CoordinateOrigin" }, - "from_prior_study": { - "description": "The coordinates come from another publication rather than from this study: a peak quoted for comparison (always true for `role: reference`), another study's peak taken as this study's ROI or seed, or a peak from the authors' own earlier publication given with its statistics, which is `role: result` with this flag true so that a meta-analysis can avoid counting the earlier study twice. Separate from the role, because a borrowed seed is still the seed this study's analysis used. False when the study defined them itself, including an ROI built from its own earlier result, meaning an earlier analysis in this same paper such as a localizer.", + "table_id": { + "description": "The ParsedTable the points were read from. Present exactly when `origin` is `table`.", "type": [ - "boolean", + "string", "null" ] }, - "key": { - "description": "The analysis's identity, derived from what was extracted -- its `name`, `coordinate_space` and `points` -- never from where it was read, so the same analysis read from two sources (a table and its HTML copy, a table and the sentence restating it) reaches the same key, and a re-run that extracts the same content reaches it again. A rename or a changed point is a new key. The name is part of the key because differently named analyses can print identical points.\n`analysis#`, where `h` is the first 16 hex characters of the sha256 of the UTF-8 bytes of the compact JSON array `[, , [, ...]]` (no whitespace, non-ASCII characters unescaped). `` is `name` normalized: Unicode NFKC, casefolded, every dash (Unicode Pd and U+2212 minus) as `-`, zero-width characters and all whitespace dropped, so `PO > Sil` and `PO>Sil` are one name. `` is `coordinate_space`, or `\"\"` when absent.\nEach `` is `[, , , , [[, ], ...]]`: the coordinates rounded to 0.01 mm and written with two decimals (`\"-4.00\"`); the point's own `space` when it differs from the analysis's, else `\"\"`; and each of its `values` as its `kind` and its value written to 6 significant digits (`\"5.8\"`, `\"3.2e-12\"`). Negative zero is written as zero. Each point's values, and then the points, are sorted by their own compact JSON, in code point order.\nAn analysis with no points -- a contrast a table lists as \"n.s.\" -- hashes its name and space alone. The two halves of a sign split hold different points under different names, so each has its own key. `study_schema.keys.analysis_key` computes it.\nThis key is what the extraction record's `Analysis.source_table_analysis` and `CoordinateSet.id` name, and what neurostore stores as `Analysis.source_id`. Two analyses of one parse never share a name, space and points, so never share a key.", - "type": "string" - }, "name": { "description": "The analysis's label, as printed where the paper prints one.", "type": "string" @@ -1363,55 +1335,22 @@ "null" ] }, - "origin": { - "$ref": "#/$defs/CoordinateOrigin" - }, - "points": { - "description": "Empty when the paper names the analysis but reports no coordinates for it -- most often because nothing survived the threshold. Whether it found an effect is the extraction record's `Analysis.outcome`; an empty list here only says that no coordinates were printed.", - "items": { - "$ref": "#/$defs/ParsedPoint" - }, - "type": [ - "array", - "null" - ] - }, - "prior_study_evidence": { - "description": "The citation or sentence showing the coordinates come from another publication.", - "items": { - "$ref": "#/$defs/TextSpan" - }, + "description": { "type": [ - "array", + "string", "null" ] }, - "role": { + "coordinate_space": { "anyOf": [ { - "$ref": "#/$defs/CoordinateRole" + "$ref": "#/$defs/ReportedSpace" }, { - "type": "string" + "type": "null" } ], - "description": "What the points are for. Only a `result` is an analysis with a statistic in the extraction record; an `anchor` -- a region of interest, a seed, a stimulation target -- becomes a CoordinateSet there, and never enters a studyset as an analysis.\nRequired. Set in the ingestion workflow by the `roles` classifier stage, after the analyses are identified: a classifier that reads each set and its context, named by `role_source`. It is the ingestion workflow's proposal, which pondie audits: accept confirms it, and relabel overrides it with a reviewed correction that wins. The extractors never propose a role, and nothing supplies a default.\nNumbers that are not brain coordinates, such as channel numbers or lattice points, get no role. They are not a coordinate set." - }, - "role_confidence": { - "description": "The classifier's probability for `role` (and `anchor_kind`).", - "maximum": 1, - "minimum": 0, - "type": [ - "number", - "null" - ] - }, - "role_source": { - "description": "The classifier that set the role, by name and version, such as `set-role-encoder@2026-10-09`.", - "type": [ - "string", - "null" - ] + "description": "The space of the analysis's points; absent when the paper states none. A point states its own only when it differs." }, "space_as_printed": { "type": [ @@ -1429,21 +1368,21 @@ } ] }, - "split": { + "role": { "anyOf": [ { - "$ref": "#/$defs/SignSplit" + "$ref": "#/$defs/CoordinateRole" }, { - "type": "null" + "type": "string" } ], - "description": "Present on both halves of an analysis split by sign, and only there. The split is declared here and nowhere else: a reader never infers it from a name." + "description": "What the points are for. Only a `result` is an analysis with a statistic in the extraction record; an `anchor` -- a region of interest, a seed, a stimulation target -- becomes a CoordinateSet there, and never enters a studyset as an analysis.\nRequired. Set in the ingestion workflow by the `roles` classifier stage, after the analyses are identified: a classifier that reads each set and its context, named by `role_source`. It is the ingestion workflow's proposal, which pondie audits: accept confirms it, and relabel overrides it with a reviewed correction that wins. The extractors never propose a role, and nothing supplies a default.\nNumbers that are not brain coordinates, such as channel numbers or lattice points, get no role. They are not a coordinate set." }, - "statistic": { + "anchor_kind": { "anyOf": [ { - "$ref": "#/$defs/StatisticKind" + "$ref": "#/$defs/AnchorKind" }, { "type": "string" @@ -1452,24 +1391,17 @@ "type": "null" } ], - "description": "The test statistic of this analysis's one test -- t, z, F, chi_square -- whatever the columns call it. One analysis is one test. Every value at a point that describes the same test belongs to that point: the statistic, its p value (uncorrected and corrected), an effect size, the cluster's p. A different test is a different analysis, even on the same rows: an omnibus F and the directional post-hoc t that follows it are two analyses, told apart by `column_group` when one table prints both." - }, - "statistic_df": { - "description": "Degrees of freedom as printed, such as \"1, 38\" or \"27\".", - "type": [ - "string", - "null" - ] + "description": "For an `anchor`, what it defines." }, - "table_id": { - "description": "The ParsedTable the points were read from. Present exactly when `origin` is `table`.", + "from_prior_study": { + "description": "The coordinates come from another publication rather than from this study: a peak quoted for comparison (always true for `role: reference`), another study's peak taken as this study's ROI or seed, or a peak from the authors' own earlier publication given with its statistics, which is `role: result` with this flag true so that a meta-analysis can avoid counting the earlier study twice. Separate from the role, because a borrowed seed is still the seed this study's analysis used. False when the study defined them itself, including an ROI built from its own earlier result, meaning an earlier analysis in this same paper such as a localizer.", "type": [ - "string", + "boolean", "null" ] }, - "text_spans": { - "description": "For a text or figure analysis, the sentences or legend it was read from: those holding its coordinates, or the one stating a result that has none.", + "prior_study_evidence": { + "description": "The citation or sentence showing the coordinates come from another publication.", "items": { "$ref": "#/$defs/TextSpan" }, @@ -1478,6 +1410,64 @@ "null" ] }, + "role_confidence": { + "description": "The classifier's probability for `role` (and `anchor_kind`).", + "maximum": 1, + "minimum": 0, + "type": [ + "number", + "null" + ] + }, + "role_source": { + "description": "The classifier that set the role, by name and version, such as `set-role-encoder@2026-10-09`.", + "type": [ + "string", + "null" + ] + }, + "split": { + "anyOf": [ + { + "$ref": "#/$defs/SignSplit" + }, + { + "type": "null" + } + ], + "description": "Present on both halves of an analysis split by sign, and only there. The split is declared here and nowhere else: a reader never infers it from a name." + }, + "derived_from": { + "description": "On a revision, the keys of the revised parse's analyses this one was made from by a split or a merge. Absent on an analysis carried over unchanged.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "statistic": { + "anyOf": [ + { + "$ref": "#/$defs/StatisticKind" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The test statistic of this analysis's one test -- t, z, F, chi_square -- whatever the columns call it. One analysis is one test. Every value at a point that describes the same test belongs to that point: the statistic, its p value (uncorrected and corrected), an effect size, the cluster's p. A different test is a different analysis, even on the same rows: an omnibus F and the directional post-hoc t that follows it are two analyses, told apart by `column_group` when one table prints both." + }, + "statistic_df": { + "description": "Degrees of freedom as printed, such as \"1, 38\" or \"27\".", + "type": [ + "string", + "null" + ] + }, "thresholds": { "description": "The thresholds the analysis's results survived, at every level the paper states: most often a voxel-level height (\"p < .001 uncorrected\") together with a cluster-level correction (\"FWE p < .05\"), which one method cannot express. Read from the caption, footer or Methods; each threshold cites where.", "items": { @@ -1487,6 +1477,16 @@ "array", "null" ] + }, + "points": { + "description": "Empty when the paper names the analysis but reports no coordinates for it -- most often because nothing survived the threshold. Whether it found an effect is the extraction record's `Analysis.outcome`; an empty list here only says that no coordinates were printed.", + "items": { + "$ref": "#/$defs/ParsedPoint" + }, + "type": [ + "array", + "null" + ] } }, "required": [ @@ -1502,46 +1502,30 @@ "additionalProperties": false, "description": "One paper as text, tables and bibliographic facts. The same shape for every download source.\nThe text is the only offset space. Every evidence span an extraction record cites, every table row's position and every text-read point addresses `text_sha256`'s characters, so a span can be checked by slicing the text, and a span made against a different text is detectably wrong.", "properties": { - "bibliography": { - "$ref": "#/$defs/Bibliography" - }, - "citations": { - "description": "Every in-text citation, where it sits in the text and which reference it points to. The link from a sentence to a work, which the reference list alone cannot give: what resolves the citations a task's description makes (storage `Task.reference_ids`) and traces a task to the paper that introduced it.", - "items": { - "$ref": "#/$defs/Citation" - }, - "type": [ - "array", - "null" - ] - }, "header": { "$ref": "#/$defs/ArtifactHeader" }, - "is_meta_analysis": { - "description": "Whether the paper is itself a meta-analysis or review, decided here once. Every consumer reads this field; none applies its own test.", - "type": "boolean" - }, - "is_meta_analysis_basis": { + "source": { "anyOf": [ { - "$ref": "#/$defs/MetaAnalysisBasis" + "$ref": "#/$defs/DocumentSource" }, { - "type": "null" + "type": "string" } ] }, - "references": { - "description": "The paper's reference list, in order. Usually fetched from an API by the paper's identifiers rather than parsed from the text (`Reference.provider` says which), so it is complete and carries DOIs even when the text's list is badly extracted.", - "items": { - "$ref": "#/$defs/Reference" - }, + "source_reason": { + "description": "Why this source's text was chosen over the others downloaded, such as \"most tables\" or \"first in download order\". Every stage reads this one text, so the choice is made once and recorded.", "type": [ - "array", + "string", "null" ] }, + "text_copy": { + "$ref": "#/$defs/HeldCopy", + "description": "The copy the text was read from. Its licence and access, not the paper's licence, decide whether the text may be passed on." + }, "reported_copies": { "description": "Copies of the paper that a source lists and that we do not hold, each with its own licence. Where an open copy is, when `bibliography.oa_status` says one exists.", "items": { @@ -1552,29 +1536,44 @@ "null" ] }, - "sections": { - "items": { - "$ref": "#/$defs/Section" - }, + "text_path": { + "description": "The text file's path, relative to the paper's bundle directory.", + "type": "string" + }, + "text_sha256": { + "description": "sha256 of the text file's UTF-8 bytes. This artifact's fingerprint.", + "type": "string" + }, + "text_length": { + "minimum": 0, "type": [ - "array", + "integer", "null" ] }, - "source": { + "bibliography": { + "$ref": "#/$defs/Bibliography" + }, + "is_meta_analysis": { + "description": "Whether the paper is itself a meta-analysis or review, decided here once. Every consumer reads this field; none applies its own test.", + "type": "boolean" + }, + "is_meta_analysis_basis": { "anyOf": [ { - "$ref": "#/$defs/DocumentSource" + "$ref": "#/$defs/MetaAnalysisBasis" }, { - "type": "string" + "type": "null" } ] }, - "source_reason": { - "description": "Why this source's text was chosen over the others downloaded, such as \"most tables\" or \"first in download order\". Every stage reads this one text, so the choice is made once and recorded.", + "sections": { + "items": { + "$ref": "#/$defs/Section" + }, "type": [ - "string", + "array", "null" ] }, @@ -1588,33 +1587,34 @@ "null" ] }, - "tables": { + "references": { + "description": "The paper's reference list, in order. Usually fetched from an API by the paper's identifiers rather than parsed from the text (`Reference.provider` says which), so it is complete and carries DOIs even when the text's list is badly extracted.", "items": { - "$ref": "#/$defs/ParsedTable" + "$ref": "#/$defs/Reference" }, "type": [ "array", "null" ] }, - "text_copy": { - "$ref": "#/$defs/HeldCopy", - "description": "The copy the text was read from. Its licence and access, not the paper's licence, decide whether the text may be passed on." - }, - "text_length": { - "minimum": 0, + "citations": { + "description": "Every in-text citation, where it sits in the text and which reference it points to. The link from a sentence to a work, which the reference list alone cannot give: what resolves the citations a task's description makes (storage `Task.reference_ids`) and traces a task to the paper that introduced it.", + "items": { + "$ref": "#/$defs/Citation" + }, "type": [ - "integer", + "array", "null" ] }, - "text_path": { - "description": "The text file's path, relative to the paper's bundle directory.", - "type": "string" - }, - "text_sha256": { - "description": "sha256 of the text file's UTF-8 bytes. This artifact's fingerprint.", - "type": "string" + "tables": { + "items": { + "$ref": "#/$defs/ParsedTable" + }, + "type": [ + "array", + "null" + ] } }, "required": [ @@ -1633,20 +1633,31 @@ "additionalProperties": false, "description": "One reported coordinate. `coordinates`, `space` and `values` are NIMADS's point fields with NIMADS's meaning, so the projection to a studyset copies them; everything else is the parse's own.", "properties": { - "cluster_measure": { + "coordinates": { + "description": "x, y, z in millimetres.", + "items": { + "type": "number" + }, + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "space": { "anyOf": [ { - "$ref": "#/$defs/ClusterMeasure" + "$ref": "#/$defs/ReportedSpace" }, { "type": "null" } - ] + ], + "description": "Only when it differs from the analysis's `coordinate_space`." }, - "cluster_size": { + "row": { + "description": "For a table point, the TableRow it was read from.", "minimum": 0, "type": [ - "number", + "integer", "null" ] }, @@ -1658,65 +1669,54 @@ "null" ] }, - "coordinates": { - "description": "x, y, z in millimetres.", + "text_span": { + "anyOf": [ + { + "$ref": "#/$defs/TextSpan" + }, + { + "type": "null" + } + ], + "description": "For a text point, the characters it was read from." + }, + "values": { + "description": "The values at the point. They are the only record of its sign (StatisticKind gives the rule): a negative value makes it negative, and a point with only p, F or chi-square values, or none, is unsigned and takes the side of its analysis's `split` half. On the inverse half the directional statistic values are negated so they read positive; p, F and chi-square stay as printed. `split` declares the inverse half, and a consumer records the inverse on the contrast's conditions (negated weights), not per point. There is no point-level deactivation flag, and the parse never writes neurostore's Point.deactivation.", "items": { - "type": "number" + "$ref": "#/$defs/PointValue" }, - "maxItems": 3, - "minItems": 3, - "type": "array" - }, - "is_subpeak": { - "type": [ - "boolean", - "null" - ] - }, - "label": { - "description": "The region name printed beside the point.", "type": [ - "string", + "array", "null" ] }, - "row": { - "description": "For a table point, the TableRow it was read from.", + "cluster_size": { "minimum": 0, "type": [ - "integer", + "number", "null" ] }, - "space": { + "cluster_measure": { "anyOf": [ { - "$ref": "#/$defs/ReportedSpace" + "$ref": "#/$defs/ClusterMeasure" }, { "type": "null" } - ], - "description": "Only when it differs from the analysis's `coordinate_space`." + ] }, - "text_span": { - "anyOf": [ - { - "$ref": "#/$defs/TextSpan" - }, - { - "type": "null" - } - ], - "description": "For a text point, the characters it was read from." + "is_subpeak": { + "type": [ + "boolean", + "null" + ] }, - "values": { - "description": "The values at the point. They are the only record of its sign (StatisticKind gives the rule): a negative value makes it negative, and a point with only p, F or chi-square values, or none, is unsigned and takes the side of its analysis's `split` half. On the inverse half the directional statistic values are negated so they read positive; p, F and chi-square stay as printed. `split` declares the inverse half, and a consumer records the inverse on the contrast's conditions (negated weights), not per point. There is no point-level deactivation flag, and the parse never writes neurostore's Point.deactivation.", - "items": { - "$ref": "#/$defs/PointValue" - }, + "label": { + "description": "The region name printed beside the point.", "type": [ - "array", + "string", "null" ] } @@ -1731,55 +1731,48 @@ "additionalProperties": false, "description": "A table of the paper, cell by cell.", "properties": { - "caption": { + "table_id": { + "description": "The table's key, as the source's parser names it. Stable for the life of the parsed paper and used unchanged by every consumer: the coordinate parse's `table_id`s, the record's Table.id, neurostore's Table.t_id. Never renumbered from the printed label and never sanitized differently on disk.", + "type": "string" + }, + "label": { + "description": "The printed label, such as Table 2 or Supplementary Table S4.", "type": [ "string", "null" ] }, - "column_headings": { - "items": { - "type": "string" - }, + "number": { "type": [ - "array", + "string", "null" ] }, - "coordinate_space_hint": { - "anyOf": [ - { - "$ref": "#/$defs/ReportedSpace" - }, - { - "type": "null" - } - ], - "description": "The source parser's own guess at the table's space. A hint for the coordinate stages, not an answer: the space a point is stored in is the coordinate parse's." - }, - "footer": { + "caption": { "type": [ "string", "null" ] }, - "label": { - "description": "The printed label, such as Table 2 or Supplementary Table S4.", + "footer": { "type": [ "string", "null" ] }, - "number": { + "raw_path": { + "description": "The source's raw table file, relative to the bundle directory.", "type": [ "string", "null" ] }, - "raw_path": { - "description": "The source's raw table file, relative to the bundle directory.", + "column_headings": { + "items": { + "type": "string" + }, "type": [ - "string", + "array", "null" ] }, @@ -1793,10 +1786,6 @@ "null" ] }, - "table_id": { - "description": "The table's key, as the source's parser names it. Stable for the life of the parsed paper and used unchanged by every consumer: the coordinate parse's `table_id`s, the record's Table.id, neurostore's Table.t_id. Never renumbered from the printed label and never sanitized differently on disk.", - "type": "string" - }, "text_span": { "anyOf": [ { @@ -1808,6 +1797,17 @@ ], "description": "Where the table's rows are inlined in the text, when the source inlines them." }, + "coordinate_space_hint": { + "anyOf": [ + { + "$ref": "#/$defs/ReportedSpace" + }, + { + "type": "null" + } + ], + "description": "The source parser's own guess at the table's space. A hint for the coordinate stages, not an answer: the space a point is stored in is the coordinate parse's." + }, "triage": { "anyOf": [ { @@ -1839,20 +1839,6 @@ "additionalProperties": false, "description": "A value reported at a point. NIMADS's point value.", "properties": { - "correction": { - "anyOf": [ - { - "$ref": "#/$defs/CorrectionMethod" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "For a p value, the correction it is under; absent for a statistic or effect size." - }, "kind": { "anyOf": [ { @@ -1869,6 +1855,9 @@ "null" ] }, + "value": { + "type": "number" + }, "level": { "anyOf": [ { @@ -1880,8 +1869,19 @@ ], "description": "Whether the value describes the peak or the cluster the peak belongs to. A cluster-level p printed on the peak's row is the cluster's." }, - "value": { - "type": "number" + "correction": { + "anyOf": [ + { + "$ref": "#/$defs/CorrectionMethod" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "For a p value, the correction it is under; absent for a statistic or effect size." } }, "required": [ @@ -1972,32 +1972,32 @@ "additionalProperties": false, "description": "The program and stage that wrote an artifact.", "properties": { - "model": { - "description": "The language model a stage called, if any.", - "type": [ - "string", - "null" - ] - }, "name": { "description": "ns-pond-ingestion-workflow or pondie.", "type": "string" }, - "prompt_version": { + "version": { + "type": "string" + }, + "stage": { + "description": "The stage within the producer, such as extract, space or audit.", "type": [ "string", "null" ] }, - "stage": { - "description": "The stage within the producer, such as extract, space or audit.", + "model": { + "description": "The language model a stage called, if any.", "type": [ "string", "null" ] }, - "version": { - "type": "string" + "prompt_version": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -2020,29 +2020,10 @@ "additionalProperties": false, "description": "One entry of a paper's reference list.", "properties": { - "doi": { - "type": [ - "string", - "null" - ] - }, "id": { "description": "Stable within the paper: the source's own id where it has one (a JATS `ref` id such as `B12`), else `ref`.", "type": "string" }, - "label": { - "description": "How the text cites it: the number of a numbered style (\"12\"), or the author-year key of an author-year style (\"Stroop 1935\").", - "type": [ - "string", - "null" - ] - }, - "pmid": { - "type": [ - "string", - "null" - ] - }, "position": { "description": "1-based order in the list, which a numbered citation style cites by.", "minimum": 1, @@ -2065,9 +2046,28 @@ ], "description": "Where the entry came from." }, - "text": { - "description": "The entry as printed.", - "type": "string" + "label": { + "description": "How the text cites it: the number of a numbered style (\"12\"), or the author-year key of an author-year style (\"Stroop 1935\").", + "type": [ + "string", + "null" + ] + }, + "text": { + "description": "The entry as printed.", + "type": "string" + }, + "doi": { + "type": [ + "string", + "null" + ] + }, + "pmid": { + "type": [ + "string", + "null" + ] }, "title": { "type": [ @@ -2129,25 +2129,26 @@ ] }, "properties": { - "license": { - "$ref": "#/$defs/License", - "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." + "reported_by": { + "$ref": "#/$defs/MetadataSource" }, - "preprint_server": { + "route": { + "$ref": "#/$defs/CopyRoute" + }, + "version": { "anyOf": [ { - "$ref": "#/$defs/PreprintServer" + "$ref": "#/$defs/CopyVersion" }, { "type": "null" } - ] - }, - "reported_by": { - "$ref": "#/$defs/MetadataSource" + ], + "description": "Absent when no source says which version the copy is." }, - "route": { - "$ref": "#/$defs/CopyRoute" + "license": { + "$ref": "#/$defs/License", + "description": "This copy's own licence. A manuscript or preprint can be licensed differently from the version of record." }, "url": { "type": [ @@ -2155,16 +2156,15 @@ "null" ] }, - "version": { + "preprint_server": { "anyOf": [ { - "$ref": "#/$defs/CopyVersion" + "$ref": "#/$defs/PreprintServer" }, { "type": "null" } - ], - "description": "Absent when no source says which version the copy is." + ] } }, "required": [ @@ -2229,17 +2229,6 @@ "additionalProperties": false, "description": "A span of the text and what it is.", "properties": { - "end_char": { - "minimum": 0, - "type": "integer" - }, - "heading": { - "description": "The heading as printed.", - "type": [ - "string", - "null" - ] - }, "kind": { "anyOf": [ { @@ -2250,9 +2239,20 @@ } ] }, + "heading": { + "description": "The heading as printed.", + "type": [ + "string", + "null" + ] + }, "start_char": { "minimum": 0, "type": "integer" + }, + "end_char": { + "minimum": 0, + "type": "integer" } }, "required": [ @@ -2416,23 +2416,23 @@ "additionalProperties": false, "description": "One item of supplementary material.", "properties": { - "fetched": { - "type": "boolean" - }, "label": { "type": [ "string", "null" ] }, - "path": { - "description": "Where the fetched file is, relative to the bundle directory.", + "url": { "type": [ "string", "null" ] }, - "url": { + "fetched": { + "type": "boolean" + }, + "path": { + "description": "Where the fetched file is, relative to the bundle directory.", "type": [ "string", "null" @@ -2465,6 +2465,9 @@ "additionalProperties": false, "description": "What reading one table for coordinates found.", "properties": { + "table_id": { + "type": "string" + }, "reading": { "$ref": "#/$defs/TableReadingKind" }, @@ -2474,9 +2477,6 @@ "string", "null" ] - }, - "table_id": { - "type": "string" } }, "required": [ @@ -2502,6 +2502,11 @@ "additionalProperties": false, "description": "One body row of a table.", "properties": { + "row": { + "description": "The row's index among the table's body rows, from 0. What a coordinate parse's point cites.", + "minimum": 0, + "type": "integer" + }, "cells": { "description": "The row's cells, as printed, in column order.", "items": { @@ -2512,11 +2517,6 @@ "null" ] }, - "row": { - "description": "The row's index among the table's body rows, from 0. What a coordinate parse's point cites.", - "minimum": 0, - "type": "integer" - }, "text_span": { "anyOf": [ { @@ -2539,14 +2539,16 @@ "additionalProperties": false, "description": "Whether a table is worth reading for coordinates, and why.", "properties": { - "excluded_by_hand": { - "description": "A person marked the table as not holding coordinates. Overrides `passes`, and no consumer reads coordinates from it.", + "passes": { + "type": "boolean" + }, + "score": { "type": [ - "boolean", + "number", "null" ] }, - "exclusion_reason": { + "route": { "type": [ "string", "null" @@ -2558,18 +2560,16 @@ "null" ] }, - "passes": { - "type": "boolean" - }, - "route": { + "excluded_by_hand": { + "description": "A person marked the table as not holding coordinates. Overrides `passes`, and no consumer reads coordinates from it.", "type": [ - "string", + "boolean", "null" ] }, - "score": { + "exclusion_reason": { "type": [ - "number", + "string", "null" ] } @@ -2593,12 +2593,12 @@ "additionalProperties": false, "description": "A range of the parsed paper's text, by character offset.", "properties": { - "end_char": { - "description": "Exclusive.", + "start_char": { "minimum": 0, "type": "integer" }, - "start_char": { + "end_char": { + "description": "Exclusive.", "minimum": 0, "type": "integer" }, @@ -2637,6 +2637,9 @@ "null" ] }, + "truncated": { + "type": "boolean" + }, "points_dropped_as_table_restatements": { "description": "Points found in the text but dropped because a table already reports them.", "minimum": 0, @@ -2644,9 +2647,6 @@ "integer", "null" ] - }, - "truncated": { - "type": "boolean" } }, "required": [ @@ -2659,6 +2659,20 @@ "additionalProperties": false, "description": "One threshold an analysis's results survived. Maps onto the storage schema's InferenceSettings: a `height` threshold is height_threshold_value and _type, a `cluster_extent` is cluster_extent_threshold, a `cluster_level` is clusterwise_threshold_value, `tfce` sets tfce_used; `correction` is multiple_comparison_method at that level.", "properties": { + "level": { + "$ref": "#/$defs/ThresholdLevel" + }, + "quantity": { + "$ref": "#/$defs/ThresholdQuantity", + "description": "What `value` is measured in." + }, + "value": { + "description": "Absent when the paper names the correction but not the number.", + "type": [ + "number", + "null" + ] + }, "correction": { "anyOf": [ { @@ -2672,13 +2686,6 @@ } ] }, - "level": { - "$ref": "#/$defs/ThresholdLevel" - }, - "quantity": { - "$ref": "#/$defs/ThresholdQuantity", - "description": "What `value` is measured in." - }, "scope_as_printed": { "description": "What the correction ran over, as printed, such as \"whole brain\" or \"within the amygdala mask\".", "type": [ @@ -2696,13 +2703,6 @@ } ], "description": "Where the threshold was read." - }, - "value": { - "description": "Absent when the paper names the correction but not the number.", - "type": [ - "number", - "null" - ] } }, "required": [ @@ -2763,46 +2763,30 @@ "description": "One paper as text, tables and bibliographic facts. The same shape for every download source.\nThe text is the only offset space. Every evidence span an extraction record cites, every table row's position and every text-read point addresses `text_sha256`'s characters, so a span can be checked by slicing the text, and a span made against a different text is detectably wrong.", "metamodel_version": "1.12.0", "properties": { - "bibliography": { - "$ref": "#/$defs/Bibliography" - }, - "citations": { - "description": "Every in-text citation, where it sits in the text and which reference it points to. The link from a sentence to a work, which the reference list alone cannot give: what resolves the citations a task's description makes (storage `Task.reference_ids`) and traces a task to the paper that introduced it.", - "items": { - "$ref": "#/$defs/Citation" - }, - "type": [ - "array", - "null" - ] - }, "header": { "$ref": "#/$defs/ArtifactHeader" }, - "is_meta_analysis": { - "description": "Whether the paper is itself a meta-analysis or review, decided here once. Every consumer reads this field; none applies its own test.", - "type": "boolean" - }, - "is_meta_analysis_basis": { + "source": { "anyOf": [ { - "$ref": "#/$defs/MetaAnalysisBasis" + "$ref": "#/$defs/DocumentSource" }, { - "type": "null" + "type": "string" } ] }, - "references": { - "description": "The paper's reference list, in order. Usually fetched from an API by the paper's identifiers rather than parsed from the text (`Reference.provider` says which), so it is complete and carries DOIs even when the text's list is badly extracted.", - "items": { - "$ref": "#/$defs/Reference" - }, + "source_reason": { + "description": "Why this source's text was chosen over the others downloaded, such as \"most tables\" or \"first in download order\". Every stage reads this one text, so the choice is made once and recorded.", "type": [ - "array", + "string", "null" ] }, + "text_copy": { + "$ref": "#/$defs/HeldCopy", + "description": "The copy the text was read from. Its licence and access, not the paper's licence, decide whether the text may be passed on." + }, "reported_copies": { "description": "Copies of the paper that a source lists and that we do not hold, each with its own licence. Where an open copy is, when `bibliography.oa_status` says one exists.", "items": { @@ -2813,29 +2797,44 @@ "null" ] }, - "sections": { - "items": { - "$ref": "#/$defs/Section" - }, + "text_path": { + "description": "The text file's path, relative to the paper's bundle directory.", + "type": "string" + }, + "text_sha256": { + "description": "sha256 of the text file's UTF-8 bytes. This artifact's fingerprint.", + "type": "string" + }, + "text_length": { + "minimum": 0, "type": [ - "array", + "integer", "null" ] }, - "source": { + "bibliography": { + "$ref": "#/$defs/Bibliography" + }, + "is_meta_analysis": { + "description": "Whether the paper is itself a meta-analysis or review, decided here once. Every consumer reads this field; none applies its own test.", + "type": "boolean" + }, + "is_meta_analysis_basis": { "anyOf": [ { - "$ref": "#/$defs/DocumentSource" + "$ref": "#/$defs/MetaAnalysisBasis" }, { - "type": "string" + "type": "null" } ] }, - "source_reason": { - "description": "Why this source's text was chosen over the others downloaded, such as \"most tables\" or \"first in download order\". Every stage reads this one text, so the choice is made once and recorded.", + "sections": { + "items": { + "$ref": "#/$defs/Section" + }, "type": [ - "string", + "array", "null" ] }, @@ -2849,33 +2848,34 @@ "null" ] }, - "tables": { + "references": { + "description": "The paper's reference list, in order. Usually fetched from an API by the paper's identifiers rather than parsed from the text (`Reference.provider` says which), so it is complete and carries DOIs even when the text's list is badly extracted.", "items": { - "$ref": "#/$defs/ParsedTable" + "$ref": "#/$defs/Reference" }, "type": [ "array", "null" ] }, - "text_copy": { - "$ref": "#/$defs/HeldCopy", - "description": "The copy the text was read from. Its licence and access, not the paper's licence, decide whether the text may be passed on." - }, - "text_length": { - "minimum": 0, + "citations": { + "description": "Every in-text citation, where it sits in the text and which reference it points to. The link from a sentence to a work, which the reference list alone cannot give: what resolves the citations a task's description makes (storage `Task.reference_ids`) and traces a task to the paper that introduced it.", + "items": { + "$ref": "#/$defs/Citation" + }, "type": [ - "integer", + "array", "null" ] }, - "text_path": { - "description": "The text file's path, relative to the paper's bundle directory.", - "type": "string" - }, - "text_sha256": { - "description": "sha256 of the text file's UTF-8 bytes. This artifact's fingerprint.", - "type": "string" + "tables": { + "items": { + "$ref": "#/$defs/ParsedTable" + }, + "type": [ + "array", + "null" + ] } }, "required": [ diff --git a/src/study_schema/jsonschema/storage-study.schema.json b/src/study_schema/jsonschema/storage-study.schema.json index 2c769b8..fea5316 100644 --- a/src/study_schema/jsonschema/storage-study.schema.json +++ b/src/study_schema/jsonschema/storage-study.schema.json @@ -4,13 +4,8 @@ "additionalProperties": false, "description": "A modality-agnostic protocol used to acquire neuroimaging or associated physiological data, holding only what every modality has in common. One Acquisition carries one modality: simultaneous recordings such as EEG+fMRI are separate Acquisition records that a Task lists together. Everything modality-specific lives in the class the Modality value instantiates -- MRI, EEG, FNIRS, PET, or OtherModality.", "properties": { - "acquisition_duration_seconds": { - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", - "minimum": 0, - "type": [ - "number", - "null" - ] + "id": { + "type": "string" }, "acquisition_type": { "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", @@ -19,16 +14,24 @@ ], "type": "string" }, - "brain_coverage": { + "modality": { "anyOf": [ { - "$ref": "#/$defs/BrainCoverage" + "$ref": "#/$defs/Modality" }, { - "type": "null" + "type": "string" } ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." + "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + }, + "acquisition_duration_seconds": { + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", + "minimum": 0, + "type": [ + "number", + "null" + ] }, "device": { "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", @@ -38,19 +41,16 @@ ], "x-reference": "Device" }, - "id": { - "type": "string" - }, - "modality": { + "brain_coverage": { "anyOf": [ { - "$ref": "#/$defs/Modality" + "$ref": "#/$defs/BrainCoverage" }, { - "type": "string" + "type": "null" } ], - "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -139,19 +139,62 @@ ], "description": "A distinct reported inferential map or tested effect, linking its source acquisitions, sample, paradigm context, tested effect, preprocessing, model estimation, inference settings, and source result tables.\nCreate one Analysis for each distinct tested effect. Separate Analyses are required when normalized direction, participant-group comparison, method, the pattern of `effect.cells`, seed or connectivity model, decoded variable, component identity, or spatial_scope differ, and when an omnibus effect and its directional post-hoc contrast are both reported. ROI definitions, acquisition summaries, descriptive sample tables, and masks without inferential effects are not Analyses. Nor is a map reported with no inferential test at all -- an ICA or PCA component map presented descriptively, a connectivity matrix given without a test -- there being nothing for such a map to be the effect of.", "properties": { - "acquisitions": { - "description": "Acquisition protocols supplying data to this analysis. Structural, diffusion, and other non-paradigm analyses are linked directly through this field.", + "id": { + "type": "string" + }, + "name": { + "description": "Study-local label for the analysis or inferential result, in the source's words.", + "type": "string" + }, + "definition": { + "description": "Precise statement of the tested effect in the source's own words, minimally cleaned. Always source-grounded. For an interaction it must state the direction of the interaction as the source states it; for an ordered contrast it must keep the ordering that the cell directions cannot express.", + "type": "string" + }, + "prespecification": { + "anyOf": [ + { + "$ref": "#/$defs/Prespecification" + }, + { + "type": "string" + } + ], + "description": "Whether this contrast was planned before the data were seen or arrived at afterwards.\nOpen vocabulary: write the source's own wording in the field when neither value fits. The two are not a spectrum and the words a source uses for them are many -- `post-hoc`, `confirmatory`, `planned`, `hypothesis-driven` -- so a closed range would discard the whole analysis over a synonym. Normalization maps the wording onto the two values." + }, + "interpretations": { + "description": "The source's own statement of what this analysis found and what it means, typically in Results and Discussion, verbatim or minimally cleaned. Always source-grounded, and the counterpart of `definition`: that field is the Methods-side statement of what was tested, this one is the Results/Discussion-side statement of what came of it.", "items": { "type": "string" }, "type": [ "array", "null" + ] + }, + "outcome": { + "anyOf": [ + { + "$ref": "#/$defs/AnalysisOutcome" + }, + { + "type": "null" + } ], - "x-reference": "Acquisition" + "description": "Whether anything survived the threshold this analysis was tested at: a cluster, a peak, a significant region. The answer to \"did this contrast find anything\", which `Effect.cells` cannot give -- a cell's direction is the sign of the contrast that was tested, and is written the same whether the test found an effect or not. Meta-analyses exclude null effects and pool only analyses that reported foci, so a tested-and-null contrast and a reported effect must not extract to the same record. Applies only to an analysis whose coordinate set is a `result` (or has no role yet): an ROI, seed, reference or other set was not tested here, so it has no outcome." }, - "assessments": { - "description": "Assessments whose measurements were used in or selected the sample for this analysis.", + "spatial_scope": { + "anyOf": [ + { + "$ref": "#/$defs/SpatialScope" + }, + { + "type": "string" + } + ], + "description": "Spatial domain of the analysis modelled. The spatial_scope refers to what voxels were modelled, not which voxels/brain regions had a correction applied." + }, + "regions": { + "description": "The regions the analysis ran over -- its search space, what `spatial_scope: roi` restricts it to. Empty for whole-brain and searchlight, where the emptiness asserts that inference was not restricted.\nNot the analysis's seeds: a whole-brain seed-based analysis has a seed and no search-space region, so putting one here says the opposite of what it did. Seeds are `ConnectivityDetails.seed_regions`.", "items": { "type": "string" }, @@ -159,14 +202,7 @@ "array", "null" ], - "x-reference": "Assessment" - }, - "coordinate_space": { - "description": "Coordinate reference space or template the spatial results are reported in, such as MNI152 or Talairach. **The authoritative of the two places this can live**: where this and a `Table.coordinate_space` disagree, this one is the analysis's space, since a table can be parsed as one space while the analysis it reports states another.\nBoth may be filled, and usually are: the table parser fills the tables and the same parse supplies this when every table behind the analysis agrees, leaving the model to answer only where they do not. Never inferred from the model's `spatial_unit` -- an analysis on a surface reports a surface template.", - "type": [ - "string", - "null" - ] + "x-reference": "Region" }, "defines_regions": { "description": "Regions this analysis produced: a localizer's clusters, or peaks of a contrast later used as seeds or ROIs. Populated on the analysis a region came *from*, not on those that use it, so a region whose `definition_method` is `same_study_analysis` and which another analysis lists in `regions` or `seed_regions` is a circularity the record states rather than asserts.", @@ -179,9 +215,21 @@ ], "x-reference": "Region" }, - "definition": { - "description": "Precise statement of the tested effect in the source's own words, minimally cleaned. Always source-grounded. For an interaction it must state the direction of the interaction as the source states it; for an ordered contrast it must keep the ordering that the cell directions cannot express.", - "type": "string" + "measure": { + "description": "The scientific quantity of the map, by identifier, independent of the statistic used to test it. Analyses of one quantity reference one record; an analysis of a different quantity takes its own. Required: a paper vague about what it measured still takes a record, with `source_label` as given and `extraction_status: not_reported` elsewhere.", + "type": "string", + "x-reference": "Measure" + }, + "groups": { + "description": "The participant cohorts the analysis ran on, and how many participants each contributed. Membership only: this slot says which people were analysed, never what was compared. A cohort comparison is a pair of directional `Cell`s on the model term whose levels name the cohorts, exactly as a condition or occasion comparison is.", + "items": { + "$ref": "#/$defs/AnalysisGroup" + }, + "type": "array" + }, + "effect": { + "$ref": "#/$defs/Effect", + "description": "What was tested about the map. Universal across every method." }, "details": { "anyOf": [ @@ -212,91 +260,43 @@ ], "description": "The method that produced the map and its method-specific payload. The populated subclass identifies the method. A voxelwise, vertexwise, or ROI-wise GLM, mixed-effects model, ANOVA, ANCOVA, or robust regression takes MassUnivariateDetails; a method outside the first-class families takes OtherAnalysisDetails and names itself in method_label." }, - "effect": { - "$ref": "#/$defs/Effect", - "description": "What was tested about the map. Universal across every method." - }, - "groups": { - "description": "The participant cohorts the analysis ran on, and how many participants each contributed. Membership only: this slot says which people were analysed, never what was compared. A cohort comparison is a pair of directional `Cell`s on the model term whose levels name the cohorts, exactly as a condition or occasion comparison is.", + "acquisitions": { + "description": "Acquisition protocols supplying data to this analysis. Structural, diffusion, and other non-paradigm analyses are linked directly through this field.", "items": { - "$ref": "#/$defs/AnalysisGroup" + "type": "string" }, - "type": "array" - }, - "id": { - "type": "string" - }, - "inference_settings": { - "description": "The thresholding and correction this analysis was reported under, by identifier. Analyses sharing one scheme reference one record; an analysis thresholded or corrected differently takes its own. Not the model's -- one fit thresholded two ways is two records here and one `model_estimation`. A contrast reported at two search volumes is two analyses sharing one `model_estimation`, not one analysis with the stricter threshold.", "type": [ - "string", + "array", "null" ], - "x-reference": "InferenceSettings" + "x-reference": "Acquisition" }, - "interpretations": { - "description": "The source's own statement of what this analysis found and what it means, typically in Results and Discussion, verbatim or minimally cleaned. Always source-grounded, and the counterpart of `definition`: that field is the Methods-side statement of what was tested, this one is the Results/Discussion-side statement of what came of it.", + "tasks": { + "description": "The participant paradigms that contextualize this analysis. An analysis can span paradigms, as a cross-task conjunction or a localizer-plus-main-task analysis does.\nA resting-state analysis names its Task here like any other: the resting-state protocol is a paradigm, and a Task record exists for it. What leaves this empty is an analysis with no paradigm at all -- morphometry, diffusion, a static PET scan -- which is linked through `acquisitions` instead.", "items": { "type": "string" }, "type": [ "array", "null" - ] - }, - "measure": { - "description": "The scientific quantity of the map, by identifier, independent of the statistic used to test it. Analyses of one quantity reference one record; an analysis of a different quantity takes its own. Required: a paper vague about what it measured still takes a record, with `source_label` as given and `extraction_status: not_reported` elsewhere.", - "type": "string", - "x-reference": "Measure" - }, - "mirror_of": { - "description": "Present only on an analysis the builder derived by reversing another one, and naming the analysis it reverses.\nA results table with both signs in one column reports one contrast read two ways. The paper's prose describes one direction, so only that half is extracted and the other is rebuilt arithmetically -- same cells, directions flipped. This slot is what says so. Without it the two halves look like independent findings, and a meta-analysis that counts both counts one sample twice.", - "type": [ - "string", - "null" ], - "x-reference": "Analysis" + "x-reference": "Task" }, "model_estimation": { "description": "The model this analysis's contrast was taken from: its family, estimator, stage, software, and the terms it was specified with. What was selected out of those terms is `effect`. Required: a paper that describes a contrast and never names an estimator still takes a record, with the terms it does state and `extraction_status: not_reported` elsewhere. Analyses sharing one design reference one record; an analysis specified or estimated differently takes its own.", "type": "string", "x-reference": "ModelEstimation" }, - "model_representation_notes": { - "description": "Model components that could not be represented or were represented only approximately, for an analysis whose method is otherwise first-class. Examples include random slopes, latent variables, dynamic connectivity, and dependencies between terms. An analysis whose method has no stable structured decomposition at all takes NotStructurableDetails instead.", + "inference_settings": { + "description": "The thresholding and correction this analysis was reported under, by identifier. Analyses sharing one scheme reference one record; an analysis thresholded or corrected differently takes its own. Not the model's -- one fit thresholded two ways is two records here and one `model_estimation`. A contrast reported at two search volumes is two analyses sharing one `model_estimation`, not one analysis with the stricter threshold.", "type": [ "string", "null" - ] - }, - "name": { - "description": "Study-local label for the analysis or inferential result, in the source's words.", - "type": "string" - }, - "outcome": { - "anyOf": [ - { - "$ref": "#/$defs/AnalysisOutcome" - }, - { - "type": "null" - } - ], - "description": "Whether anything survived the threshold this analysis was tested at: a cluster, a peak, a significant region. The answer to \"did this contrast find anything\", which `Effect.cells` cannot give -- a cell's direction is the sign of the contrast that was tested, and is written the same whether the test found an effect or not. Meta-analyses exclude null effects and pool only analyses that reported foci, so a tested-and-null contrast and a reported effect must not extract to the same record. Applies only to an analysis whose coordinate set is a `result` (or has no role yet): an ROI, seed, reference or other set was not tested here, so it has no outcome." - }, - "prespecification": { - "anyOf": [ - { - "$ref": "#/$defs/Prespecification" - }, - { - "type": "string" - } ], - "description": "Whether this contrast was planned before the data were seen or arrived at afterwards.\nOpen vocabulary: write the source's own wording in the field when neither value fits. The two are not a spectrum and the words a source uses for them are many -- `post-hoc`, `confirmatory`, `planned`, `hypothesis-driven` -- so a closed range would discard the whole analysis over a synonym. Normalization maps the wording onto the two values." + "x-reference": "InferenceSettings" }, - "regions": { - "description": "The regions the analysis ran over -- its search space, what `spatial_scope: roi` restricts it to. Empty for whole-brain and searchlight, where the emptiness asserts that inference was not restricted.\nNot the analysis's seeds: a whole-brain seed-based analysis has a seed and no search-space region, so putting one here says the opposite of what it did. Seeds are `ConnectivityDetails.seed_regions`.", + "tables": { + "description": "The source tables reporting results for this analysis.", "items": { "type": "string" }, @@ -304,7 +304,15 @@ "array", "null" ], - "x-reference": "Region" + "x-reference": "Table" + }, + "mirror_of": { + "description": "Present only on an analysis the builder derived by reversing another one, and naming the analysis it reverses.\nA results table with both signs in one column reports one contrast read two ways. The paper's prose describes one direction, so only that half is extracted and the other is rebuilt arithmetically -- same cells, directions flipped. This slot is what says so. Without it the two halves look like independent findings, and a meta-analysis that counts both counts one sample twice.", + "type": [ + "string", + "null" + ], + "x-reference": "Analysis" }, "source_table_analysis": { "description": "The key of the coordinate-parse analysis this analysis's results were read from: `ParsedAnalysis.key` in neuroimaging-paper-parse, `analysis#`, where `h` hashes the parse analysis's name, space and points. Derived from what was extracted, not where it was read, so a re-parse that extracts the same analysis reaches the same key. When pondie revised the parse, the key is the revision's.\nThis schema stores no coordinates, so reaching an analysis's foci means joining through the parse, and `tables` alone cannot do it: a table usually reports several contrasts and several analyses usually cite the same table. This key is what makes the join exact.\nCopy it from the bracketed key the stage-1 listing prints beside each parsed analysis. One key and not a list, because one analysis is read from one row group; an analysis whose results span two tables cites both in `tables` and names here the row group that defined it.", @@ -313,17 +321,6 @@ "null" ] }, - "spatial_scope": { - "anyOf": [ - { - "$ref": "#/$defs/SpatialScope" - }, - { - "type": "string" - } - ], - "description": "Spatial domain of the analysis modelled. The spatial_scope refers to what voxels were modelled, not which voxels/brain regions had a correction applied." - }, "statistical_maps": { "description": "Statistical maps made available for this analysis.", "items": { @@ -334,19 +331,15 @@ "null" ] }, - "tables": { - "description": "The source tables reporting results for this analysis.", - "items": { - "type": "string" - }, + "coordinate_space": { + "description": "Coordinate reference space or template the spatial results are reported in, such as MNI152 or Talairach. **The authoritative of the two places this can live**: where this and a `Table.coordinate_space` disagree, this one is the analysis's space, since a table can be parsed as one space while the analysis it reports states another.\nBoth may be filled, and usually are: the table parser fills the tables and the same parse supplies this when every table behind the analysis agrees, leaving the model to answer only where they do not. Never inferred from the model's `spatial_unit` -- an analysis on a surface reports a surface template.", "type": [ - "array", + "string", "null" - ], - "x-reference": "Table" + ] }, - "tasks": { - "description": "The participant paradigms that contextualize this analysis. An analysis can span paradigms, as a cross-task conjunction or a localizer-plus-main-task analysis does.\nA resting-state analysis names its Task here like any other: the resting-state protocol is a paradigm, and a Task record exists for it. What leaves this empty is an analysis with no paradigm at all -- morphometry, diffusion, a static PET scan -- which is linked through `acquisitions` instead.", + "assessments": { + "description": "Assessments whose measurements were used in or selected the sample for this analysis.", "items": { "type": "string" }, @@ -354,7 +347,14 @@ "array", "null" ], - "x-reference": "Task" + "x-reference": "Assessment" + }, + "model_representation_notes": { + "description": "Model components that could not be represented or were represented only approximately, for an analysis whose method is otherwise first-class. Examples include random slopes, latent variables, dynamic connectivity, and dependencies between terms. An analysis whose method has no stable structured decomposition at all takes NotStructurableDetails instead.", + "type": [ + "string", + "null" + ] } }, "required": [ @@ -457,8 +457,15 @@ "additionalProperties": false, "description": "One thing participants were assigned to receive, or to receive in place of it.", "properties": { - "agent": { - "description": "The drug, agent, or stimulation target this arm received, as the source names it. The name only; dose, unit, route, and schedule are not recorded.", + "id": { + "type": "string" + }, + "name": { + "description": "Short arm label as the source states it, for example methylphenidate, sham tDCS, waitlist.", + "type": "string" + }, + "description": { + "description": "Complete definition of what this arm received.", "type": [ "string", "null" @@ -475,19 +482,12 @@ ], "description": "What class of thing this arm received. Required, so an arm whose kind the source leaves unclear still carries the slot, as `extraction_status: not_reported`." }, - "description": { - "description": "Complete definition of what this arm received.", + "agent": { + "description": "The drug, agent, or stimulation target this arm received, as the source names it. The name only; dose, unit, route, and schedule are not recorded.", "type": [ "string", "null" ] - }, - "id": { - "type": "string" - }, - "name": { - "description": "Short arm label as the source states it, for example methylphenidate, sham tDCS, waitlist.", - "type": "string" } }, "required": [ @@ -517,8 +517,8 @@ "additionalProperties": false, "description": "Every identifier known for the paper when the artifact was written. Carried whole, so no consumer has to choose between them or look one up. `doi`, `pmid` and `pmcid` name the version of record; a preprint's identifiers are kept beside them in the `preprint_` fields, so a paper and its preprint are one article.", "properties": { - "doi": { - "description": "The DOI of the version of record. Never a preprint server's DOI, which is in `preprint_dois`.", + "pmid": { + "description": "PubMed's record of the version of record. Never PubMed's record of a preprint, which is `preprint_pmid`.", "type": [ "string", "null" @@ -531,8 +531,8 @@ "null" ] }, - "pmid": { - "description": "PubMed's record of the version of record. Never PubMed's record of a preprint, which is `preprint_pmid`.", + "doi": { + "description": "The DOI of the version of record. Never a preprint server's DOI, which is in `preprint_dois`.", "type": [ "string", "null" @@ -548,15 +548,15 @@ "null" ] }, - "preprint_pmcid": { - "description": "PubMed Central's copy of the preprint.", + "preprint_pmid": { + "description": "PubMed's record of the preprint, which PubMed types `Preprint`.", "type": [ "string", "null" ] }, - "preprint_pmid": { - "description": "PubMed's record of the preprint, which PubMed types `Preprint`.", + "preprint_pmcid": { + "description": "PubMed Central's copy of the preprint.", "type": [ "string", "null" @@ -577,27 +577,27 @@ "additionalProperties": false, "description": "A behavioral, cognitive, biological, clinical, or questionnaire-based instrument or procedure that produces participant-level measurements independently of the neuroimaging acquisition or experimental conditions.", "properties": { - "assessment_type": { - "description": "Broad type, such as cognitive test, clinical scale, diagnostic interview, questionnaire, behavioral task, physiological measure, or a biospecimen assay such as salivary cortisol or a plasma marker. Free text rather than a vocabulary: create a category if none of these fit.", + "id": { + "description": "Stable identifier.", + "type": "string" + }, + "name": { + "description": "Name of the assessment, such as Digit Span, MMSE, or BDI.", + "type": "string" + }, + "description": { + "description": "Description of the assessment and the measurements it produced, including the purpose the source gives for administering it -- establishing a group's diagnosis, screening a comorbidity, grading severity, characterizing everyone. That purpose is what decides whether a group may name this assessment as its `diagnostic_instrument`, and it is often the only sentence distinguishing the two.", "type": [ "string", "null" ] }, - "description": { - "description": "Description of the assessment and the measurements it produced, including the purpose the source gives for administering it -- establishing a group's diagnosis, screening a comorbidity, grading severity, characterizing everyone. That purpose is what decides whether a group may name this assessment as its `diagnostic_instrument`, and it is often the only sentence distinguishing the two.", + "assessment_type": { + "description": "Broad type, such as cognitive test, clinical scale, diagnostic interview, questionnaire, behavioral task, physiological measure, or a biospecimen assay such as salivary cortisol or a plasma marker. Free text rather than a vocabulary: create a category if none of these fit.", "type": [ "string", "null" ] - }, - "id": { - "description": "Stable identifier.", - "type": "string" - }, - "name": { - "description": "Name of the assessment, such as Digit Span, MMSE, or BDI.", - "type": "string" } }, "required": [ @@ -650,24 +650,8 @@ ] }, "properties": { - "abstract": { - "description": "Absent (null) when no source gave one; never an empty string.", - "pattern": "\\S", - "type": [ - "string", - "null" - ] - }, - "abstract_provider": { - "anyOf": [ - { - "$ref": "#/$defs/MetadataSource" - }, - { - "type": "null" - } - ], - "description": "The source whose abstract `abstract` is. It may differ from `provider`. The abstract is the publisher's abstract of the version `license` describes, so its licence is `license`." + "title": { + "type": "string" }, "authors": { "description": "Author names in published order.", @@ -679,68 +663,47 @@ "null" ] }, - "corrections": { - "description": "Notices attached to the paper -- errata, retractions, expressions of concern -- as the indexer lists them. A retracted paper is still parsed and stored, marked by its `retraction` notice, and left out of studysets by default; a filter can ask for it explicitly.", - "items": { - "$ref": "#/$defs/Correction" - }, + "journal": { "type": [ - "array", + "string", "null" ] }, - "issue": { + "publication_year": { + "description": "Year in which the study was first published or made publicly available.", + "minimum": 1600, "type": [ - "string", + "integer", "null" ] }, - "journal": { + "volume": { "type": [ "string", "null" ] }, - "keywords": { - "items": { - "type": "string" - }, + "issue": { "type": [ - "array", + "string", "null" ] }, - "language": { - "description": "Languages of the article, as the E-utilities API reports them -- the three-letter codes PubMed assigns, `eng` and the rest, and not read off the paper: the text a reader is given is in one language whatever the article is indexed as.\nAt least six of the benchmark's sixteen meta-analyses state \"English language\" as an inclusion criterion. On the 1,804 records measured it excludes exactly one paper, so it buys precision rather than recall; the criterion is stated, so the field states it.", - "items": { - "type": "string" - }, + "pages": { "type": [ - "array", + "string", "null" ] }, - "license": { - "$ref": "#/$defs/License", - "description": "The paper's licence: the version of record's, or the latest preprint's while the header's `publication_status` is `preprint`. Not the licence of a copy of another version, which that copy carries." - }, - "oa_status": { - "anyOf": [ - { - "$ref": "#/$defs/OaStatus" - }, - { - "type": "null" - } - ] - }, - "pages": { + "abstract": { + "description": "Absent (null) when no source gave one; never an empty string.", + "pattern": "\\S", "type": [ "string", "null" ] }, - "provider": { + "abstract_provider": { "anyOf": [ { "$ref": "#/$defs/MetadataSource" @@ -749,7 +712,17 @@ "type": "null" } ], - "description": "The metadata service the facts came from." + "description": "The source whose abstract `abstract` is. It may differ from `provider`. The abstract is the publisher's abstract of the version `license` describes, so its licence is `license`." + }, + "language": { + "description": "Languages of the article, as the E-utilities API reports them -- the three-letter codes PubMed assigns, `eng` and the rest, and not read off the paper: the text a reader is given is in one language whatever the article is indexed as.\nAt least six of the benchmark's sixteen meta-analyses state \"English language\" as an inclusion criterion. On the 1,804 records measured it excludes exactly one paper, so it buys precision rather than recall; the criterion is stated, so the field states it.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] }, "publication_types": { "description": "PubMed publication types for this record, verbatim as the E-utilities API returns them (https://pubmed.ncbi.nlm.nih.gov/help/#publication-types). Multivalued because PubMed assigns several: every article is `Journal Article` and the design of the study motivates other labels. `StudyType` lists the known types only; any other type PubMed returns is kept verbatim.", @@ -768,22 +741,49 @@ "null" ] }, - "publication_year": { - "description": "Year in which the study was first published or made publicly available.", - "minimum": 1600, + "keywords": { + "items": { + "type": "string" + }, "type": [ - "integer", + "array", "null" ] }, - "title": { - "type": "string" + "license": { + "$ref": "#/$defs/License", + "description": "The paper's licence: the version of record's, or the latest preprint's while the header's `publication_status` is `preprint`. Not the licence of a copy of another version, which that copy carries." }, - "volume": { + "oa_status": { + "anyOf": [ + { + "$ref": "#/$defs/OaStatus" + }, + { + "type": "null" + } + ] + }, + "corrections": { + "description": "Notices attached to the paper -- errata, retractions, expressions of concern -- as the indexer lists them. A retracted paper is still parsed and stored, marked by its `retraction` notice, and left out of studysets by default; a filter can ask for it explicitly.", + "items": { + "$ref": "#/$defs/Correction" + }, "type": [ - "string", + "array", "null" ] + }, + "provider": { + "anyOf": [ + { + "$ref": "#/$defs/MetadataSource" + }, + { + "type": "null" + } + ], + "description": "The metadata service the facts came from." } }, "required": [ @@ -851,20 +851,20 @@ "null" ] }, - "denominator": { - "description": "Number of participants used as the denominator for the percentage.", + "percentage": { + "description": "Percentage of the relevant group in this category.", + "maximum": 100, "minimum": 0, "type": [ - "integer", + "number", "null" ] }, - "percentage": { - "description": "Percentage of the relevant group in this category.", - "maximum": 100, + "denominator": { + "description": "Number of participants used as the denominator for the percentage.", "minimum": 0, "type": [ - "number", + "integer", "null" ] }, @@ -886,28 +886,28 @@ "additionalProperties": false, "description": "One side of the tested comparison: a level of a model term, and which way that level entered. The unit a contrast is built from.\nA cell points at a `ModelTerm`. The term's `FactorLevel`s record which cohort, condition, occasion, or arm carries each level. Joining through the model recovers the contrast's entities, including its conditions and cohorts. The `term` is the comparison axis: cells sharing a term compare within a factor, cells on different terms compare across them.\nA cell over a term that declares no levels -- a slope, or a product column -- leaves `level` unset and carries its `direction` alone.", "properties": { - "direction": { - "$ref": "#/$defs/Direction", - "description": "How this cell entered the contrast: the weight's sign for a categorical level, the fitted coefficient's sign for a continuous term, or the interaction coefficient's sign for a product column -- read as the named level's slope relative to the others when the cell names one (`level`).\nSigns, never numeric weights. For an ordered comparison mark the extremes `positive` and `negative`, leave the levels in between out of `cells` -- absence is the zero weight -- and keep the ordering on `FactorLevel.order`." + "term": { + "description": "The model term this cell belongs to, by identifier. It must be one of the terms of the ModelEstimation the containing Analysis references, or of a stage that model reaches through `inputs_from` -- a group contrast of a first-level condition, or of a seed's time series, is a cell on that stage's term rather than on a copy of it.", + "type": "string", + "x-reference": "ModelTerm" }, - "label": { - "description": "The source's wording for this level in this comparison, when it differs from the level's own label on the model.", + "level": { + "description": "The level represented by this cell, matching a `FactorLevel.level` on the referenced ModelTerm. The model states the level vocabulary; this field only identifies the level placed on this side of the comparison.\nRequired exactly when the referenced term declares levels. A slope declares none, and its cell names none. A product column declares none either, but a signed cell on one crossing a factor with a continuous term may name a level of the factor: the cohort or condition whose slope has the cell's sign, relative to the factor's other levels. \"Maltreatment was negatively associated with rACC activation in the PTSD group, but not in controls\" is `direction: negative` with `level: PTSD group`. Without the level the sign has no reference, since which level the difference is taken from is a coding choice.", "type": [ "string", "null" ] }, - "level": { - "description": "The level represented by this cell, matching a `FactorLevel.level` on the referenced ModelTerm. The model states the level vocabulary; this field only identifies the level placed on this side of the comparison.\nRequired exactly when the referenced term declares levels. A slope declares none, and its cell names none. A product column declares none either, but a signed cell on one crossing a factor with a continuous term may name a level of the factor: the cohort or condition whose slope has the cell's sign, relative to the factor's other levels. \"Maltreatment was negatively associated with rACC activation in the PTSD group, but not in controls\" is `direction: negative` with `level: PTSD group`. Without the level the sign has no reference, since which level the difference is taken from is a coding choice.", + "direction": { + "$ref": "#/$defs/Direction", + "description": "How this cell entered the contrast: the weight's sign for a categorical level, the fitted coefficient's sign for a continuous term, or the interaction coefficient's sign for a product column -- read as the named level's slope relative to the others when the cell names one (`level`).\nSigns, never numeric weights. For an ordered comparison mark the extremes `positive` and `negative`, leave the levels in between out of `cells` -- absence is the zero weight -- and keep the ordering on `FactorLevel.order`." + }, + "label": { + "description": "The source's wording for this level in this comparison, when it differs from the level's own label on the model.", "type": [ "string", "null" ] - }, - "term": { - "description": "The model term this cell belongs to, by identifier. It must be one of the terms of the ModelEstimation the containing Analysis references, or of a stage that model reaches through `inputs_from` -- a group contrast of a first-level condition, or of a seed's time series, is a cell on that stage's term rather than on a copy of it.", - "type": "string", - "x-reference": "ModelTerm" } }, "required": [ @@ -927,6 +927,24 @@ "additionalProperties": false, "description": "A named condition of a task: a modelled state, event type, or baseline that an analysis can contrast against another. Only what happened inside the paradigm -- the occasion the task was run on and the arm it was run under are Timepoints and Arms, and enter an analysis as levels of their own model terms, compared by `Cell`s on the Effect.", "properties": { + "id": { + "description": "Stable identifier.", + "type": "string" + }, + "name": { + "description": "Short condition label that distinguishes the condition from other task periods or modeled effects.", + "type": "string" + }, + "stimulus_content": { + "description": "What this condition's stimuli were about -- their category, not the channel (`Task.stimulus_modality`) or the materials (`Task.stimuli`): a cigarette photograph is `cigarettes`. Faces, scenes, words, tones, music, food, money -- examples, not a vocabulary.\nLeave unset where the stimulus does not distinguish this condition from the others: every n-back load level shows the same letters.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "condition_kind": { "anyOf": [ { @@ -947,24 +965,6 @@ "string", "null" ] - }, - "id": { - "description": "Stable identifier.", - "type": "string" - }, - "name": { - "description": "Short condition label that distinguishes the condition from other task periods or modeled effects.", - "type": "string" - }, - "stimulus_content": { - "description": "What this condition's stimuli were about -- their category, not the channel (`Task.stimulus_modality`) or the materials (`Task.stimuli`): a cigarette photograph is `cigarettes`. Faces, scenes, words, tones, music, food, money -- examples, not a vocabulary.\nLeave unset where the stimulus does not distinguish this condition from the others: every n-back load level shows the same letters.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -1035,12 +1035,16 @@ "null" ] }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "ConjunctionDetails" + "null_hypothesis": { + "anyOf": [ + { + "$ref": "#/$defs/NullHypothesis" + }, + { + "type": "null" + } ], - "type": "string" + "description": "Which null hypothesis was tested. Only the conjunction null licenses the strong reading." }, "implementation": { "anyOf": [ @@ -1056,16 +1060,12 @@ ], "description": "Procedure that computed the conjunction, recorded separately from the hypothesis it tested." }, - "null_hypothesis": { - "anyOf": [ - { - "$ref": "#/$defs/NullHypothesis" - }, - { - "type": "null" - } + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "ConjunctionDetails" ], - "description": "Which null hypothesis was tested. Only the conjunction null licenses the strong reading." + "type": "string" } }, "required": [ @@ -1099,12 +1099,27 @@ ], "description": "Method used to estimate connectivity." }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "ConnectivityDetails" + "seed_regions": { + "description": "Seed or source regions, by identifier. What each one is and how it was delimited is on the `Region`; naming it here says only that this analysis seeded from it.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" ], - "type": "string" + "x-reference": "Region" + }, + "target_regions": { + "description": "Target regions, by identifier. Empty for a seed-to-whole-brain analysis.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Region" }, "edges": { "description": "Named region pairs, when the source reports specific edges.", @@ -1116,6 +1131,13 @@ "null" ] }, + "modulatory_input": { + "description": "Modulatory input of the model, for DCM and related methods.", + "type": [ + "string", + "null" + ] + }, "inference_target": { "anyOf": [ { @@ -1130,34 +1152,12 @@ ], "description": "What the statistical test was about: the strength of a connection, which of several models fits better, or a network taken as a whole. A model comparison and a parameter test answer different questions and are not poolable." }, - "modulatory_input": { - "description": "Modulatory input of the model, for DCM and related methods.", - "type": [ - "string", - "null" - ] - }, - "seed_regions": { - "description": "Seed or source regions, by identifier. What each one is and how it was delimited is on the `Region`; naming it here says only that this analysis seeded from it.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Region" - }, - "target_regions": { - "description": "Target regions, by identifier. Empty for a seed-to-whole-brain analysis.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "ConnectivityDetails" ], - "x-reference": "Region" + "type": "string" } }, "required": [ @@ -1171,17 +1171,6 @@ "additionalProperties": false, "description": "One reported connection between two named regions.", "properties": { - "directionality": { - "anyOf": [ - { - "$ref": "#/$defs/EdgeDirectionality" - }, - { - "type": "null" - } - ], - "description": "Whether this edge is genuinely directed. Derived from the containing analysis's `connectivity_method`, which is what decides it: only a directed model supports a claim about which region influences which, and the wording of the paper cannot change that. The mapper's `edge_directionality_from_method` table in extraction-to-storage.map.yaml holds the value per method, and leaves this unset for a method written as free text, there being nothing to derive from." - }, "source_region": { "description": "Source region of the edge, by identifier.", "type": [ @@ -1197,6 +1186,17 @@ "null" ], "x-reference": "Region" + }, + "directionality": { + "anyOf": [ + { + "$ref": "#/$defs/EdgeDirectionality" + }, + { + "type": "null" + } + ], + "description": "Whether this edge is genuinely directed. Derived from the containing analysis's `connectivity_method`, which is what decides it: only a directed model supports a claim about which region influences which, and the wording of the paper cannot change that. The mapper's `edge_directionality_from_method` table in extraction-to-storage.map.yaml holds the value per method, and leaves this unset for a method written as free text, there being nothing to derive from." } }, "title": "ConnectivityEdge", @@ -1233,18 +1233,29 @@ "additionalProperties": false, "description": "A group of coordinates reported together -- a row group of a table, or a run of sentences in the text -- and what it is for. The coordinates stay in the parse, as for tables; this records their role and links them to what they belong to: the analysis a result reports, the regions an anchor locates.\nA set has one role: its dominant one. The groups come from the table parse, which splits a table by what was tested rather than by what each row is for, so a set can mix roles -- a connectivity results table that lists its own seed as a row. Such a set keeps the role of most of its points, and the odd point takes that role with it. This is a known error, about 1.3% of sets, accepted rather than splitting the parse's groups.", "properties": { - "analysis": { - "description": "For a `result`, the analysis whose findings these are. The same join `Analysis.source_table_analysis` makes from the other side, here for sets read from the text as well.", + "id": { + "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 the set's name, space and points, so the key survives a re-parse that extracts the same set.", + "type": "string" + }, + "table": { + "description": "The table this set was read from. Absent for a set read from the text.", "type": [ "string", "null" ], - "x-reference": "Analysis" + "x-reference": "Table" }, - "anchor_kind": { + "text_excerpt": { + "description": "For a set read from the text, the sentences it was read from: the sentence holding the coordinates and its neighbours, extended past any neighbour that also holds coordinates.", + "type": [ + "string", + "null" + ] + }, + "role": { "anyOf": [ { - "$ref": "#/$defs/AnchorKind" + "$ref": "#/$defs/CoordinateRole" }, { "type": "string" @@ -1253,29 +1264,7 @@ "type": "null" } ], - "description": "For an `anchor`, what it defines. Absent for every other role." - }, - "description": { - "description": "What the set is, in the source's words, when the role alone does not say.", - "type": [ - "string", - "null" - ] - }, - "id": { - "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 the set's name, space and points, so the key survives a re-parse that extracts the same set.", - "type": "string" - }, - "regions": { - "description": "For an `anchor`, the regions these coordinates locate.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Region" + "description": "What these coordinates are for." }, "result_kind": { "anyOf": [ @@ -1291,10 +1280,10 @@ ], "description": "For a `result`, the kind of analysis that produced it. Absent for every other role." }, - "role": { + "anchor_kind": { "anyOf": [ { - "$ref": "#/$defs/CoordinateRole" + "$ref": "#/$defs/AnchorKind" }, { "type": "string" @@ -1303,18 +1292,29 @@ "type": "null" } ], - "description": "What these coordinates are for." + "description": "For an `anchor`, what it defines. Absent for every other role." }, - "table": { - "description": "The table this set was read from. Absent for a set read from the text.", + "analysis": { + "description": "For a `result`, the analysis whose findings these are. The same join `Analysis.source_table_analysis` makes from the other side, here for sets read from the text as well.", "type": [ "string", "null" ], - "x-reference": "Table" + "x-reference": "Analysis" }, - "text_excerpt": { - "description": "For a set read from the text, the sentences it was read from: the sentence holding the coordinates and its neighbours, extended past any neighbour that also holds coordinates.", + "regions": { + "description": "For an `anchor`, the regions these coordinates locate.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Region" + }, + "description": { + "description": "What the set is, in the source's words, when the role alone does not say.", "type": [ "string", "null" @@ -1331,12 +1331,6 @@ "additionalProperties": false, "description": "One notice attached to a paper.", "properties": { - "doi": { - "type": [ - "string", - "null" - ] - }, "kind": { "anyOf": [ { @@ -1352,6 +1346,12 @@ "string", "null" ] + }, + "doi": { + "type": [ + "string", + "null" + ] } }, "required": [ @@ -1376,6 +1376,10 @@ "additionalProperties": false, "description": "One class discriminated by a decoding analysis.", "properties": { + "label": { + "description": "The class label as the source names it, such as `faces` or `left hand`.", + "type": "string" + }, "condition": { "description": "The experimental condition this class corresponds to, when it is one.", "type": [ @@ -1383,10 +1387,6 @@ "null" ], "x-reference": "Condition" - }, - "label": { - "description": "The class label as the source names it, such as `faces` or `left hand`.", - "type": "string" } }, "required": [ @@ -1399,6 +1399,10 @@ "additionalProperties": false, "description": "Payload for a classification or pattern-discriminability analysis. Above-chance decoding is one signed cell with nothing crossed, and the reference it beat is `PerformanceMetric.reference_value`, which is per metric because accuracy against chance and AUC against 0.5 are two references. A between-cohort accuracy comparison is a crossed cohort term; accuracy regressed on a behavioural score is a cell on a continuous term. Spatial scope is recorded on the Analysis, including for searchlight analyses.", "properties": { + "decoded_variable": { + "description": "The variable the classifier or discriminability test predicted.", + "type": "string" + }, "classes": { "description": "The classes discriminated. These are non-directional: direction, where it exists, is carried by Effect.", "items": { @@ -1409,24 +1413,6 @@ "null" ] }, - "decoded_variable": { - "description": "The variable the classifier or discriminability test predicted.", - "type": "string" - }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "DecodingDetails" - ], - "type": "string" - }, - "generalization": { - "description": "Train/test split for cross-decoding, when the analysis generalized across sets.", - "type": [ - "string", - "null" - ] - }, "performance_metrics": { "description": "The performance measures reported for this map, one entry each. Papers routinely give several of the same analysis -- accuracy with sensitivity and specificity, or accuracy with AUC -- and each carries its own value and its own reference. Record every one the source reports.", "items": { @@ -1440,6 +1426,20 @@ "string", "null" ] + }, + "generalization": { + "description": "Train/test split for cross-decoding, when the analysis generalized across sets.", + "type": [ + "string", + "null" + ] + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "DecodingDetails" + ], + "type": "string" } }, "required": [ @@ -1524,46 +1524,19 @@ "additionalProperties": false, "description": "A scalp EEG acquisition. Its own attributes are the ones that materially distinguish recordings used in source-localized coordinate or image-based meta-analyses.", "properties": { - "acquisition_duration_seconds": { - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", + "sampling_frequency_hz": { + "description": "Sampling frequency of the recording in hertz.", "minimum": 0, "type": [ "number", "null" ] }, - "acquisition_type": { - "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", - "enum": [ - "EEG" - ], - "type": "string" - }, - "brain_coverage": { - "anyOf": [ - { - "$ref": "#/$defs/BrainCoverage" - }, - { - "type": "null" - } - ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "eeg_reference": { + "description": "Reference scheme and location used for the raw recording.", "type": [ "string", "null" - ], - "x-reference": "Device" - }, - "eeg_channel_count": { - "description": "Number of simultaneously recorded EEG channels.", - "minimum": 0, - "type": [ - "integer", - "null" ] }, "eeg_placement_scheme": { @@ -1573,16 +1546,31 @@ "null" ] }, - "eeg_reference": { - "description": "Reference scheme and location used for the raw recording.", + "recording_type": { + "description": "Recording organization, such as continuous, discontinuous, or epoched.", "type": [ "string", "null" ] }, + "eeg_channel_count": { + "description": "Number of simultaneously recorded EEG channels.", + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, "id": { "type": "string" }, + "acquisition_type": { + "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", + "enum": [ + "EEG" + ], + "type": "string" + }, "modality": { "anyOf": [ { @@ -1594,20 +1582,32 @@ ], "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." }, - "recording_type": { - "description": "Recording organization, such as continuous, discontinuous, or epoched.", + "acquisition_duration_seconds": { + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", + "minimum": 0, "type": [ - "string", + "number", "null" ] }, - "sampling_frequency_hz": { - "description": "Sampling frequency of the recording in hertz.", - "minimum": 0, + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", "type": [ - "number", + "string", "null" - ] + ], + "x-reference": "Device" + }, + "brain_coverage": { + "anyOf": [ + { + "$ref": "#/$defs/BrainCoverage" + }, + { + "type": "null" + } + ], + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -1733,53 +1733,20 @@ "additionalProperties": false, "description": "An fNIRS acquisition. Its own attributes are the ones affecting cortical sensitivity, spatial sampling, or comparability, and so can support study filtering or moderator analyses.", "properties": { - "acquisition_duration_seconds": { - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", + "sampling_frequency_hz": { + "description": "Sampling frequency of the fNIRS data in hertz.", "minimum": 0, "type": [ "number", "null" ] }, - "acquisition_type": { - "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", - "enum": [ - "FNIRS" - ], - "type": "string" - }, - "brain_coverage": { - "anyOf": [ - { - "$ref": "#/$defs/BrainCoverage" - }, - { - "type": "null" - } - ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "nirs_system_type": { + "description": "Instrument principle, such as continuous-wave, frequency-domain, or time-domain NIRS.", "type": [ "string", "null" - ], - "x-reference": "Device" - }, - "id": { - "type": "string" - }, - "modality": { - "anyOf": [ - { - "$ref": "#/$defs/Modality" - }, - { - "type": "string" - } - ], - "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + ] }, "nirs_channel_count": { "description": "Total number of NIRS channels, including short-separation channels.", @@ -1789,41 +1756,17 @@ "null" ] }, - "nirs_placement_scheme": { - "description": "Standard placement system or listed optode positions, such as 10-20, Cz, or Pz.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "nirs_system_type": { - "description": "Instrument principle, such as continuous-wave, frequency-domain, or time-domain NIRS.", - "type": [ - "string", - "null" - ] - }, - "recorded_signal_types": { - "description": "Raw or derived channel signal types, such as continuous-wave amplitude, optical density, oxygenated hemoglobin, or deoxygenated hemoglobin.", + "wavelengths_nm": { + "description": "Nominal emitted wavelengths in nanometers.", "items": { - "type": "string" + "minimum": 0, + "type": "number" }, "type": [ "array", "null" ] }, - "sampling_frequency_hz": { - "description": "Sampling frequency of the fNIRS data in hertz.", - "minimum": 0, - "type": [ - "number", - "null" - ] - }, "short_channel_count": { "description": "Number of short-separation channels; zero indicates none.", "minimum": 0, @@ -1843,16 +1786,73 @@ "null" ] }, - "wavelengths_nm": { - "description": "Nominal emitted wavelengths in nanometers.", + "nirs_placement_scheme": { + "description": "Standard placement system or listed optode positions, such as 10-20, Cz, or Pz.", "items": { - "minimum": 0, - "type": "number" + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "recorded_signal_types": { + "description": "Raw or derived channel signal types, such as continuous-wave amplitude, optical density, oxygenated hemoglobin, or deoxygenated hemoglobin.", + "items": { + "type": "string" }, "type": [ "array", "null" ] + }, + "id": { + "type": "string" + }, + "acquisition_type": { + "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", + "enum": [ + "FNIRS" + ], + "type": "string" + }, + "modality": { + "anyOf": [ + { + "$ref": "#/$defs/Modality" + }, + { + "type": "string" + } + ], + "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + }, + "acquisition_duration_seconds": { + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", + "minimum": 0, + "type": [ + "number", + "null" + ] + }, + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "type": [ + "string", + "null" + ], + "x-reference": "Device" + }, + "brain_coverage": { + "anyOf": [ + { + "$ref": "#/$defs/BrainCoverage" + }, + { + "type": "null" + } + ], + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -1867,16 +1867,17 @@ "additionalProperties": false, "description": "One level of a categorical model term and the entities that carry it. The ModelTerm records which design cell a cohort or condition occupies because that placement belongs to the design, not to a contrast drawn from it.\nThe five entity slots are what make the entity view of a contrast recoverable: an Effect's cells name levels, and joining those levels here says which cohorts, conditions, occasions, arms or regions were compared. A factor normally ranges over one of the five -- conditions for a task factor, cohorts for a participant factor, occasions for a time factor, arms for a treatment factor, regions for one comparing places in the brain -- so the others stay empty; a level realized by a crossing, such as a drug given at follow-up, fills more than one.\nA factor whose levels are not entities of the study at all -- hemispheres, tasks, frequency bands, acquisition sessions -- leaves all five empty and is carried by `level` alone. Such a factor is complete, and takes `Cell`s like any other.", "properties": { - "arms": { - "description": "The arms carrying this level, by identifier. A factor whose levels are drug and placebo, or active and sham, ranges over arms. In a parallel trial each level is both an arm and a cohort and both slots are filled; in a crossover one cohort receives every arm, so `arms` alone distinguishes the levels and filling only `groups` loses the allocation.", - "items": { - "type": "string" - }, + "level": { + "description": "The level's label, as the source words it. Unique within its term: a level appearing twice is one level recorded twice.", + "type": "string" + }, + "order": { + "description": "Position of this level in the factor's sequence, counting from 1, for a factor whose levels are ordered. It carries an ordering `Effect.cells` cannot: a contrast signs only its extremes and omits what lies between, so without this 1-back, 2-back and 3-back are an unordered set.\nPopulate it whenever the levels have an order the source states or plainly implies -- a dose series, a load or difficulty series, an ordinal severity grading. Leave it unset for a nominal factor, where imposing an order would assert a structure the design does not have. Timepoint.order is the same device for occasions.", + "minimum": 1, "type": [ - "array", + "integer", "null" - ], - "x-reference": "Arm" + ] }, "conditions": { "description": "The conditions carrying this level, by identifier.", @@ -1900,20 +1901,19 @@ ], "x-reference": "Group" }, - "level": { - "description": "The level's label, as the source words it. Unique within its term: a level appearing twice is one level recorded twice.", - "type": "string" - }, - "order": { - "description": "Position of this level in the factor's sequence, counting from 1, for a factor whose levels are ordered. It carries an ordering `Effect.cells` cannot: a contrast signs only its extremes and omits what lies between, so without this 1-back, 2-back and 3-back are an unordered set.\nPopulate it whenever the levels have an order the source states or plainly implies -- a dose series, a load or difficulty series, an ordinal severity grading. Leave it unset for a nominal factor, where imposing an order would assert a structure the design does not have. Timepoint.order is the same device for occasions.", - "minimum": 1, + "timepoints": { + "description": "The occasions carrying this level, by identifier. A factor whose levels are baseline, week 6 or follow-up ranges over occasions and fills this: left empty, the level stands on its label alone and no query can tell which scan preceded the intervention.", + "items": { + "type": "string" + }, "type": [ - "integer", + "array", "null" - ] + ], + "x-reference": "Timepoint" }, - "regions": { - "description": "The brain regions carrying this level, by identifier, for a factor comparing places. A three-region factor is one `ModelTerm` with three levels, one region on each.", + "arms": { + "description": "The arms carrying this level, by identifier. A factor whose levels are drug and placebo, or active and sham, ranges over arms. In a parallel trial each level is both an arm and a cohort and both slots are filled; in a crossover one cohort receives every arm, so `arms` alone distinguishes the levels and filling only `groups` loses the allocation.", "items": { "type": "string" }, @@ -1921,10 +1921,10 @@ "array", "null" ], - "x-reference": "Region" + "x-reference": "Arm" }, - "timepoints": { - "description": "The occasions carrying this level, by identifier. A factor whose levels are baseline, week 6 or follow-up ranges over occasions and fills this: left empty, the level stands on its label alone and no query can tell which scan preceded the intervention.", + "regions": { + "description": "The brain regions carrying this level, by identifier, for a factor comparing places. A three-region factor is one `ModelTerm` with three levels, one region on each.", "items": { "type": "string" }, @@ -1932,7 +1932,7 @@ "array", "null" ], - "x-reference": "Timepoint" + "x-reference": "Region" } }, "required": [ @@ -1961,165 +1961,175 @@ "additionalProperties": false, "description": "An aggregate participant group and its reported demographics.", "properties": { - "acquired_count": { - "description": "Number for whom data were acquired or who were scanned.", - "minimum": 0, + "id": { + "type": "string" + }, + "name": { + "description": "Study-local group label, for example healthy controls or patients with Parkinson disease.", + "type": "string" + }, + "description": { + "description": "Narrative definition of the group and its role in the study.", "type": [ - "integer", + "string", "null" ] }, - "age_maximum": { - "description": "Oldest age reported for the group.", - "minimum": 0, - "type": [ - "number", - "null" - ] + "species": { + "anyOf": [ + { + "$ref": "#/$defs/Species" + }, + { + "type": "string" + } + ], + "description": "The species of this cohort." }, - "age_mean": { - "description": "Mean participant age in the unit given by age_unit.", - "minimum": 0, + "arm": { + "description": "The arm this cohort was allocated to. Set it whenever a cohort was allocated to one, which is the parallel-group case: every participant of this cohort received this arm and no other. A crossover study allocates nobody to one arm, so its cohorts leave this empty and an arm reaches an analysis only as a level of a model term, through `FactorLevel.arms`. Distinct from `medication_status`, which is the on/off state during acquisition rather than the allocation.", "type": [ - "number", + "string", "null" - ] + ], + "x-reference": "Arm" }, - "age_median": { - "description": "Median participant age for the group.", + "approached_count": { + "description": "Number of prospective participants approached for this group.", "minimum": 0, "type": [ - "number", + "integer", "null" ] }, - "age_minimum": { - "description": "Youngest age reported for the group.", + "consented_count": { + "description": "Number who consented or assented to participate.", "minimum": 0, "type": [ - "number", + "integer", "null" ] }, - "age_standard_deviation": { - "description": "Standard deviation of participant age in the unit given by age_unit.", + "enrolled_count": { + "description": "Number enrolled after screening and before acquisition.", "minimum": 0, "type": [ - "number", + "integer", "null" ] }, - "age_unit": { - "description": "Unit used for age summaries, normally years but potentially months, weeks, or gestational weeks.", + "acquired_count": { + "description": "Number for whom data were acquired or who were scanned.", + "minimum": 0, "type": [ - "string", + "integer", "null" ] }, - "age_unit_normalized": { - "anyOf": [ - { - "$ref": "#/$defs/AgeUnit" - }, - { - "type": "null" - } - ], - "description": "`age_unit` folded onto a fixed set. **Derived, not extracted**: `pondie.normalization.age_unit` sets it from the source's own wording, which is kept in `age_unit`.\nWithout it the age fields are not comparable across studies: `6` under \"months\" and `6` under \"years\" are the same number, and a filter on age reads every neonatal and infant cohort wrong. `UNKNOWN` where the wording does not say -- not `years`, because assuming the common case is what makes that error silent." - }, - "approached_count": { - "description": "Number of prospective participants approached for this group.", + "excluded_count": { + "description": "Number excluded after consent, enrollment, acquisition, and quality control.", "minimum": 0, "type": [ "integer", "null" ] }, - "arm": { - "description": "The arm this cohort was allocated to. Set it whenever a cohort was allocated to one, which is the parallel-group case: every participant of this cohort received this arm and no other. A crossover study allocates nobody to one arm, so its cohorts leave this empty and an arm reaches an analysis only as a level of a model term, through `FactorLevel.arms`. Distinct from `medication_status`, which is the on/off state during acquisition rather than the allocation.", + "inclusion_criteria": { + "description": "Eligibility requirements that a participant had to satisfy for membership in this group.", + "items": { + "type": "string" + }, "type": [ - "string", + "array", "null" - ], - "x-reference": "Arm" + ] }, - "clinical_characteristics": { - "description": "Disease duration, severity, treatment status, or other clinically relevant summaries.", + "exclusion_criteria": { + "description": "Screening, imaging-safety, clinical, behavioral, or quality-control criteria causing exclusion.", + "items": { + "type": "string" + }, "type": [ - "string", + "array", "null" ] }, - "consented_count": { - "description": "Number who consented or assented to participate.", - "minimum": 0, + "recruitment_method": { + "description": "How, where, and from what setting participants were recruited.", "type": [ - "integer", + "string", "null" ] }, - "description": { - "description": "Narrative definition of the group and its role in the study.", + "recruitment_dates": { + "description": "Free-text recruitment period when the source does not support a precise normalized date range; examples include January 2021 to June 2022, 2018-2020, or autumn 2023.", "type": [ "string", "null" ] }, - "diagnostic_instrument": { - "description": "The study assessment that established this group's defining condition -- the `medical_condition` entries, or for a healthy group its confirmed absence. Naming one here claims it classified this cohort, which is narrower than having been administered to it: an interview screening a comorbidity the group is not defined by, or grading the severity of a diagnosis made elsewhere, is recorded in `assessments` and pointed at from nowhere. Groups defined by enrollment or history rather than by an assessment -- a treatment programme's patients, self-reported users -- name nothing, as do sources reporting a diagnostic system but never the instrument applying it. Every entry must also appear in the study's `assessments`.", - "items": { - "type": "string" - }, + "age_mean": { + "description": "Mean participant age in the unit given by age_unit.", + "minimum": 0, "type": [ - "array", + "number", "null" - ], - "x-reference": "Assessment" + ] }, - "diagnostic_system": { - "description": "Classification system and edition under which this group's `medical_condition` was diagnosed, such as DSM-5 or ICD-10. An edition that appears only inside an instrument's title does not establish it: \"SCID for DSM-IV Axis II Disorders\", administered to screen comorbidities, says nothing about the system used to diagnose the condition that defines the group.", + "age_standard_deviation": { + "description": "Standard deviation of participant age in the unit given by age_unit.", + "minimum": 0, "type": [ - "string", + "number", "null" ] }, - "education_summary": { - "description": "Aggregate education level and the measurement or coding system used.", + "age_minimum": { + "description": "Youngest age reported for the group.", + "minimum": 0, "type": [ - "string", + "number", "null" ] }, - "enrolled_count": { - "description": "Number enrolled after screening and before acquisition.", + "age_maximum": { + "description": "Oldest age reported for the group.", "minimum": 0, "type": [ - "integer", + "number", "null" ] }, - "ethnicity_distribution": { - "description": "Aggregate ethnic distribution using the study-reported category labels.", - "items": { - "$ref": "#/$defs/CategoryDistribution" - }, + "age_median": { + "description": "Median participant age for the group.", + "minimum": 0, "type": [ - "array", + "number", "null" ] }, - "excluded_count": { - "description": "Number excluded after consent, enrollment, acquisition, and quality control.", - "minimum": 0, + "age_unit": { + "description": "Unit used for age summaries, normally years but potentially months, weeks, or gestational weeks.", "type": [ - "integer", + "string", "null" ] }, - "exclusion_criteria": { - "description": "Screening, imaging-safety, clinical, behavioral, or quality-control criteria causing exclusion.", + "age_unit_normalized": { + "anyOf": [ + { + "$ref": "#/$defs/AgeUnit" + }, + { + "type": "null" + } + ], + "description": "`age_unit` folded onto a fixed set. **Derived, not extracted**: `pondie.normalization.age_unit` sets it from the source's own wording, which is kept in `age_unit`.\nWithout it the age fields are not comparable across studies: `6` under \"months\" and `6` under \"years\" are the same number, and a filter on age reads every neonatal and infant cohort wrong. `UNKNOWN` where the wording does not say -- not `years`, because assuming the common case is what makes that error silent." + }, + "sex_distribution": { + "description": "Aggregate phenotypic sex distribution. Use one entry per reported category, with the study's category label and a count and/or percentage. A cohort described wholly as one sex reports its distribution without ever printing a breakdown: \"22 male smokers\", \"young women (n = 8)\" and \"ten male patients\" are each one entry whose count is the whole group, plus a second entry for the absent sex with a count of zero. Read a demographics table row (\"N female | 5\") exactly as you would read the same fact in a sentence.", "items": { - "type": "string" + "$ref": "#/$defs/CategoryDistribution" }, "type": [ "array", @@ -2146,13 +2156,20 @@ "null" ] }, - "id": { - "type": "string" + "race_distribution": { + "description": "Aggregate racial distribution using the study-reported category labels.", + "items": { + "$ref": "#/$defs/CategoryDistribution" + }, + "type": [ + "array", + "null" + ] }, - "inclusion_criteria": { - "description": "Eligibility requirements that a participant had to satisfy for membership in this group.", + "ethnicity_distribution": { + "description": "Aggregate ethnic distribution using the study-reported category labels.", "items": { - "type": "string" + "$ref": "#/$defs/CategoryDistribution" }, "type": [ "array", @@ -2166,8 +2183,8 @@ "null" ] }, - "medical_condition": { - "description": "Medical, neurological, psychiatric, developmental, or physiological condition(s) defining or characterizing the group. List the primary diagnosis and all comorbidities.", + "population_characteristics": { + "description": "How this cohort **deviates from the typical**, where no other field covers it: habitual exposure (heavy caffeine use, social drinkers, smoking status), training or expertise (athletes, musicians, bilinguals, gamers), occupation or social role (veterans, undergraduates, shift workers), lifestyle (sedentary, vegetarian, meditation practitioners), and body habitus where it is atypical (obese, overweight, underweight).\nDeviation is the test, and it is what makes this field worth querying. A study selects its cohort for something; that something is what belongs here. The unremarkable does not: \"normal weight\", \"healthy weight\", \"typically developing\", \"right-handed\", \"no hearing loss\" and an ordinary age range describe a cohort that was not selected for anything, and recording them fills the field with the absence of information. If every plausible cohort could carry the value, leave it out.\n**Every other Group slot takes priority.** Nothing here that belongs in `age_mean`, `age_minimum`, `age_maximum`, `age_median`, `sex_distribution`, `gender_distribution`, `handedness_distribution`, `race_distribution`, `ethnicity_distribution`, `education_summary`, `socioeconomic_status_summary`, `medications`, `medication_status`, `recruitment_method`, `species`, `inclusion_criteria`, `exclusion_criteria` or `medical_condition`. \"All Caucasian\" is `race_distribution`; \"children aged 8-11\" is `age_minimum` and `age_maximum`; \"five participants were female\" is `sex_distribution`.\nNot a diagnosis either. Where a trait is also the cohort's condition -- alcohol dependence, obesity as a diagnosed disease -- `medical_condition` takes it alone, which is where a clinician would look.\nWhat was screened out is not a characteristic. \"Excluding caffeine\" in an exclusion list says nothing about caffeine use in the cohort.", "items": { "type": "string" }, @@ -2176,15 +2193,8 @@ "null" ] }, - "medication_status": { - "description": "Whether the cohort was medicated, unmedicated, drug-naive, or withdrawn, and the on/off medication state during acquisition or testing. The state, not the agents: which drugs they were taking is `medications`.", - "type": [ - "string", - "null" - ] - }, - "medications": { - "description": "The drugs or other agents this cohort was taking, as the source names them, one agent per entry. Names only.", + "other_characteristics": { + "description": "Cohort traits that are true but not selective: the entries moved out of `population_characteristics` because every plausible cohort could carry them. \"normal weight\", \"typically developing\", \"right-handed\", \"normal or corrected-to-normal vision\", \"no history of neurological illness\", \"MRI compatible\", \"native English speakers\" are the recurring forms.\n**Derived, not extracted.** `pondie.normalization.population_characteristics` partitions the extracted list and code writes this side of it, so the slot is never put to a model. It exists because the alternative to moving these values is dropping them, which loses a cohort detail a reader auditing the study wants to see.\nThe partition is by selectivity, not by truth. A value here is not wrong; it is a value no query can usefully filter on, which is the only reason it cannot share a field with the trait a study chose its cohort for.\nAsymmetric where the literature is: \"right-handed\" is normative and \"left-handed\" or \"mixed-handed\" is selective, because a study recruiting left-handers recruited for that. Likewise a negated *condition* is normative (\"no psychiatric history\") and a negated *exposure* is not (\"no history of smoking\" defines the control arm of a smoking study).", "items": { "type": "string" }, @@ -2193,12 +2203,8 @@ "null" ] }, - "name": { - "description": "Study-local group label, for example healthy controls or patients with Parkinson disease.", - "type": "string" - }, - "other_characteristics": { - "description": "Cohort traits that are true but not selective: the entries moved out of `population_characteristics` because every plausible cohort could carry them. \"normal weight\", \"typically developing\", \"right-handed\", \"normal or corrected-to-normal vision\", \"no history of neurological illness\", \"MRI compatible\", \"native English speakers\" are the recurring forms.\n**Derived, not extracted.** `pondie.normalization.population_characteristics` partitions the extracted list and code writes this side of it, so the slot is never put to a model. It exists because the alternative to moving these values is dropping them, which loses a cohort detail a reader auditing the study wants to see.\nThe partition is by selectivity, not by truth. A value here is not wrong; it is a value no query can usefully filter on, which is the only reason it cannot share a field with the trait a study chose its cohort for.\nAsymmetric where the literature is: \"right-handed\" is normative and \"left-handed\" or \"mixed-handed\" is selective, because a study recruiting left-handers recruited for that. Likewise a negated *condition* is normative (\"no psychiatric history\") and a negated *exposure* is not (\"no history of smoking\" defines the control arm of a smoking study).", + "medical_condition": { + "description": "Medical, neurological, psychiatric, developmental, or physiological condition(s) defining or characterizing the group. List the primary diagnosis and all comorbidities.", "items": { "type": "string" }, @@ -2207,45 +2213,59 @@ "null" ] }, - "population_characteristics": { - "description": "How this cohort **deviates from the typical**, where no other field covers it: habitual exposure (heavy caffeine use, social drinkers, smoking status), training or expertise (athletes, musicians, bilinguals, gamers), occupation or social role (veterans, undergraduates, shift workers), lifestyle (sedentary, vegetarian, meditation practitioners), and body habitus where it is atypical (obese, overweight, underweight).\nDeviation is the test, and it is what makes this field worth querying. A study selects its cohort for something; that something is what belongs here. The unremarkable does not: \"normal weight\", \"healthy weight\", \"typically developing\", \"right-handed\", \"no hearing loss\" and an ordinary age range describe a cohort that was not selected for anything, and recording them fills the field with the absence of information. If every plausible cohort could carry the value, leave it out.\n**Every other Group slot takes priority.** Nothing here that belongs in `age_mean`, `age_minimum`, `age_maximum`, `age_median`, `sex_distribution`, `gender_distribution`, `handedness_distribution`, `race_distribution`, `ethnicity_distribution`, `education_summary`, `socioeconomic_status_summary`, `medications`, `medication_status`, `recruitment_method`, `species`, `inclusion_criteria`, `exclusion_criteria` or `medical_condition`. \"All Caucasian\" is `race_distribution`; \"children aged 8-11\" is `age_minimum` and `age_maximum`; \"five participants were female\" is `sex_distribution`.\nNot a diagnosis either. Where a trait is also the cohort's condition -- alcohol dependence, obesity as a diagnosed disease -- `medical_condition` takes it alone, which is where a clinician would look.\nWhat was screened out is not a characteristic. \"Excluding caffeine\" in an exclusion list says nothing about caffeine use in the cohort.", + "diagnostic_system": { + "description": "Classification system and edition under which this group's `medical_condition` was diagnosed, such as DSM-5 or ICD-10. An edition that appears only inside an instrument's title does not establish it: \"SCID for DSM-IV Axis II Disorders\", administered to screen comorbidities, says nothing about the system used to diagnose the condition that defines the group.", + "type": [ + "string", + "null" + ] + }, + "diagnostic_instrument": { + "description": "The study assessment that established this group's defining condition -- the `medical_condition` entries, or for a healthy group its confirmed absence. Naming one here claims it classified this cohort, which is narrower than having been administered to it: an interview screening a comorbidity the group is not defined by, or grading the severity of a diagnosis made elsewhere, is recorded in `assessments` and pointed at from nowhere. Groups defined by enrollment or history rather than by an assessment -- a treatment programme's patients, self-reported users -- name nothing, as do sources reporting a diagnostic system but never the instrument applying it. Every entry must also appear in the study's `assessments`.", "items": { "type": "string" }, "type": [ "array", "null" + ], + "x-reference": "Assessment" + }, + "clinical_characteristics": { + "description": "Disease duration, severity, treatment status, or other clinically relevant summaries.", + "type": [ + "string", + "null" ] }, - "race_distribution": { - "description": "Aggregate racial distribution using the study-reported category labels.", + "medications": { + "description": "The drugs or other agents this cohort was taking, as the source names them, one agent per entry. Names only.", "items": { - "$ref": "#/$defs/CategoryDistribution" + "type": "string" }, "type": [ "array", "null" ] }, - "recruitment_dates": { - "description": "Free-text recruitment period when the source does not support a precise normalized date range; examples include January 2021 to June 2022, 2018-2020, or autumn 2023.", + "medication_status": { + "description": "Whether the cohort was medicated, unmedicated, drug-naive, or withdrawn, and the on/off medication state during acquisition or testing. The state, not the agents: which drugs they were taking is `medications`.", "type": [ "string", "null" ] }, - "recruitment_method": { - "description": "How, where, and from what setting participants were recruited.", + "education_summary": { + "description": "Aggregate education level and the measurement or coding system used.", "type": [ "string", "null" ] }, - "sample_overlap_count": { - "description": "How many of this group were in that prior report, when the source says so. It is the difference between two papers being one sample and their sharing part of one, which is the difference between dropping a study and down-weighting it.", - "minimum": 0, + "socioeconomic_status_summary": { + "description": "Aggregate socioeconomic status and the instrument or proxy used to measure it.", "type": [ - "integer", + "string", "null" ] }, @@ -2267,33 +2287,13 @@ "null" ] }, - "sex_distribution": { - "description": "Aggregate phenotypic sex distribution. Use one entry per reported category, with the study's category label and a count and/or percentage. A cohort described wholly as one sex reports its distribution without ever printing a breakdown: \"22 male smokers\", \"young women (n = 8)\" and \"ten male patients\" are each one entry whose count is the whole group, plus a second entry for the absent sex with a count of zero. Read a demographics table row (\"N female | 5\") exactly as you would read the same fact in a sentence.", - "items": { - "$ref": "#/$defs/CategoryDistribution" - }, - "type": [ - "array", - "null" - ] - }, - "socioeconomic_status_summary": { - "description": "Aggregate socioeconomic status and the instrument or proxy used to measure it.", + "sample_overlap_count": { + "description": "How many of this group were in that prior report, when the source says so. It is the difference between two papers being one sample and their sharing part of one, which is the difference between dropping a study and down-weighting it.", + "minimum": 0, "type": [ - "string", + "integer", "null" ] - }, - "species": { - "anyOf": [ - { - "$ref": "#/$defs/Species" - }, - { - "type": "string" - } - ], - "description": "The species of this cohort." } }, "required": [ @@ -2365,15 +2365,32 @@ ], "description": "One thresholding and correction procedure. This is the canonical storage home for thresholding, correction, and cluster-inference metadata. The test statistic itself belongs to the tested effect and lives in Effect.statistic.\nOne record may be referenced by several Analyses, so every field must hold for all of them; an analysis thresholded or corrected differently takes its own record. The test is mechanical: two analyses share a record when the threshold, its type, the correction method, the scope corrected over and the alpha are all the same. A paper that states one correction scheme in Methods and applies it throughout has one record.\nInference attaches to the analysis, not to the fit: one model estimated once is often thresholded several ways -- an ROI test Bonferroni-corrected over four regions and a searchlight over the same fit corrected by FDR. See storage-schema-design-notes.md for the reasoning behind this shape.", "properties": { - "alpha_level": { - "description": "Family-wise, false-discovery, or nominal alpha threshold when reported.", - "maximum": 1, + "id": { + "description": "Stable identifier used by Analysis foreign-key references.", + "type": "string" + }, + "inference_level": { + "description": "Level at which inference is made, such as voxel, vertex, cluster, region, network or other.", + "type": [ + "string", + "null" + ] + }, + "height_threshold_value": { + "description": "The threshold applied to each element's statistic, before correction and before any cluster-level test. Papers name it for whichever of its two jobs they are describing -- the *voxelwise* (or vertexwise) threshold when elements are tested individually, the *cluster-forming* threshold when surviving elements are grouped into clusters -- and it is one number either way, applied once to the map. Record it once here, whichever word the source uses.\nNot the cluster-level threshold: `clusterwise_threshold_value` is the p or q a whole cluster had to beat, and `cluster_extent_threshold` the size it had to reach. A paper reporting \"p < 0.001 uncorrected, cluster-level p < 0.05 FWE\" states this field and `clusterwise_threshold_value`, not this field twice.", "minimum": 0, "type": [ "number", "null" ] }, + "height_threshold_type": { + "description": "Meaning of height_threshold_value, such as p, q, t, z, or F.", + "type": [ + "string", + "null" + ] + }, "cluster_extent_threshold": { "description": "Minimum surviving cluster extent in voxels, vertices, or the unit stated in the source.", "minimum": 0, @@ -2390,16 +2407,12 @@ "null" ] }, - "correction_regions": { - "description": "The regions correction was restricted to -- what `correction_scope: roi` names, as `Analysis.regions` names what `spatial_scope: roi` restricts the search space to. Empty for a whole-brain correction.\nNot the analysis's search space. Naming the correction mask on the analysis instead says the model was fitted within it, which is a different claim about what the paper did.", - "items": { - "type": "string" - }, + "multiple_comparison_method": { + "description": "Correction method, such as FWE, FDR, Bonferroni, permutation, Monte Carlo, or none.", "type": [ - "array", + "string", "null" - ], - "x-reference": "Region" + ] }, "correction_scope": { "anyOf": [ @@ -2415,51 +2428,42 @@ ], "description": "Whether the multiple-comparison correction searched the whole acquired volume or was restricted to regions. The correction's own domain, not the analysis's: a whole-brain voxelwise model whose significance was corrected inside an anatomical mask has `Analysis.spatial_scope: whole_brain` and `correction_scope: roi`, and the two differing is the ordinary case rather than a contradiction.\nA domain the enum cannot name is written here as free text. An inference level such as \"cluster level\" is not a scope and belongs in `inference_level`." }, - "height_threshold_type": { - "description": "Meaning of height_threshold_value, such as p, q, t, z, or F.", - "type": [ - "string", - "null" - ] - }, - "height_threshold_value": { - "description": "The threshold applied to each element's statistic, before correction and before any cluster-level test. Papers name it for whichever of its two jobs they are describing -- the *voxelwise* (or vertexwise) threshold when elements are tested individually, the *cluster-forming* threshold when surviving elements are grouped into clusters -- and it is one number either way, applied once to the map. Record it once here, whichever word the source uses.\nNot the cluster-level threshold: `clusterwise_threshold_value` is the p or q a whole cluster had to beat, and `cluster_extent_threshold` the size it had to reach. A paper reporting \"p < 0.001 uncorrected, cluster-level p < 0.05 FWE\" states this field and `clusterwise_threshold_value`, not this field twice.", - "minimum": 0, + "correction_regions": { + "description": "The regions correction was restricted to -- what `correction_scope: roi` names, as `Analysis.regions` names what `spatial_scope: roi` restricts the search space to. Empty for a whole-brain correction.\nNot the analysis's search space. Naming the correction mask on the analysis instead says the model was fitted within it, which is a different claim about what the paper did.", + "items": { + "type": "string" + }, "type": [ - "number", + "array", "null" - ] - }, - "id": { - "description": "Stable identifier used by Analysis foreign-key references.", - "type": "string" + ], + "x-reference": "Region" }, - "inference_level": { - "description": "Level at which inference is made, such as voxel, vertex, cluster, region, network or other.", + "search_volume": { + "description": "Reported search volume, mask size, or number of vertices or voxels used for inference.", "type": [ "string", "null" ] }, - "multiple_comparison_method": { - "description": "Correction method, such as FWE, FDR, Bonferroni, permutation, Monte Carlo, or none.", + "neighborhood_definition": { + "description": "Voxel or vertex adjacency or connectivity rule used for cluster formation.", "type": [ "string", "null" ] }, - "neighborhood_definition": { - "description": "Voxel or vertex adjacency or connectivity rule used for cluster formation.", + "tfce_used": { + "description": "Whether threshold-free cluster enhancement was used.", "type": [ - "string", + "boolean", "null" ] }, - "number_of_tests": { - "description": "Number of statistical tests considered by the multiple-comparison procedure.", - "minimum": 0, + "tfce_parameters": { + "description": "TFCE parameters such as height and extent exponents when reported.", "type": [ - "integer", + "string", "null" ] }, @@ -2471,24 +2475,20 @@ "null" ] }, - "search_volume": { - "description": "Reported search volume, mask size, or number of vertices or voxels used for inference.", - "type": [ - "string", - "null" - ] - }, - "tfce_parameters": { - "description": "TFCE parameters such as height and extent exponents when reported.", + "alpha_level": { + "description": "Family-wise, false-discovery, or nominal alpha threshold when reported.", + "maximum": 1, + "minimum": 0, "type": [ - "string", + "number", "null" ] }, - "tfce_used": { - "description": "Whether threshold-free cluster enhancement was used.", + "number_of_tests": { + "description": "Number of statistical tests considered by the multiple-comparison procedure.", + "minimum": 0, "type": [ - "boolean", + "integer", "null" ] } @@ -2513,82 +2513,16 @@ "additionalProperties": false, "description": "Payload for an analysis whose map is one component or latent variable of a decomposition -- ICA, PCA, NMF, PLS, CCA, and the multimodal fusion methods. Covers a component whose map or loading was *tested*: compared between cohorts, correlated with behaviour, or otherwise given an inferential result. A component map presented descriptively with no test is not an Analysis, so it takes no record here. Being representable here does not imply eligibility for coordinate-based meta-analysis.\nOne analysis reports one component: a second latent variable or component from the same decomposition is a second Analysis.\nOne invariant LinkML does not express, so the structural validator carries it: `crossblock_covariance_percent` is meaningless when `second_block` is `[none]`, there being no second block for the covariance to cross. `variance_explained_percent` is permitted either way, since a two-block decomposition may still report how much of the brain block its component captures.", "properties": { - "component_id": { - "description": "Identifier of the component or latent variable within the decomposition, as reported -- commonly an index such as `IC12` or `LV1`. Required because without it two analyses from one decomposition are indistinguishable.", - "type": "string" - }, - "component_label": { - "description": "The source's name for the component, such as a network label, or for what a latent variable captures.", - "type": [ - "string", - "null" - ] - }, - "crossblock_covariance_percent": { - "description": "Percentage of crossblock covariance this latent variable accounts for, as reported. The nearest thing a two-block decomposition has to an effect size, and what a paper uses to justify reporting one latent variable and not another. Unset when `second_block` is `[none]`, there being no second block to cross.", - "maximum": 100, - "minimum": 0, - "type": [ - "number", - "null" - ] - }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "LatentDecompositionDetails" - ], - "type": "string" - }, - "dimensionality_selection_method": { - "description": "How the number of components was chosen, as reported -- minimum description length, a variance-retained cutoff, a scree criterion, permutation on the singular values, or a fixed a priori number.", - "type": [ - "string", - "null" - ] - }, - "map_quantity": { - "anyOf": [ - { - "$ref": "#/$defs/MapQuantity" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "What the values of the reported map represent -- a loading, a weight, a PLS salience, a standardized score." - }, "method": { "anyOf": [ - { - "$ref": "#/$defs/DecompositionMethod" - }, - { - "type": "string" - } - ], - "description": "The algorithm that produced the components, which decides what their values and signs mean." - }, - "polarity_semantics": { - "anyOf": [ - { - "$ref": "#/$defs/PolaritySemantics" - }, - { - "type": "null" - } - ], - "description": "Whether the sign of the reported map carries meaning. A latent variable's sign is arbitrary up to a flip, and so is an independent component's, so a paper must say what positive means before a direction can be pooled." - }, - "salience_threshold": { - "description": "The criterion deciding which elements count as reliable contributors, as reported -- conventionally a bootstrap ratio cutoff such as \"|BSR| > 3.0\", or a standardized cutoff such as \"|Z| > 3.5\". Not `InferenceSettings`: a bootstrap ratio is a stability measure and not a test statistic, so reading it as a voxelwise threshold would misstate the inference.", - "type": [ - "string", - "null" - ] + { + "$ref": "#/$defs/DecompositionMethod" + }, + { + "type": "string" + } + ], + "description": "The algorithm that produced the components, which decides what their values and signs mean." }, "second_block": { "description": "What the brain block was decomposed against, one value per additional block, and the primary filter on this payload. `[none]` for a decomposition of the brain data alone, which is a positive assertion rather than an empty slot.\nMultivalued because papers cross more than two blocks: a three-modality mCCA+jICA carries `other_imaging_modality` twice, and a design-plus-behaviour PLS carries `[design, behavioral]`. Name the blocks, never their number.", @@ -2625,8 +2559,12 @@ "null" ] }, - "selection_criterion": { - "description": "How this component was selected for reporting.", + "component_id": { + "description": "Identifier of the component or latent variable within the decomposition, as reported -- commonly an index such as `IC12` or `LV1`. Required because without it two analyses from one decomposition are indistinguishable.", + "type": "string" + }, + "component_label": { + "description": "The source's name for the component, such as a network label, or for what a latent variable captures.", "type": [ "string", "null" @@ -2640,6 +2578,20 @@ "null" ] }, + "dimensionality_selection_method": { + "description": "How the number of components was chosen, as reported -- minimum description length, a variance-retained cutoff, a scree criterion, permutation on the singular values, or a fixed a priori number.", + "type": [ + "string", + "null" + ] + }, + "selection_criterion": { + "description": "How this component was selected for reporting.", + "type": [ + "string", + "null" + ] + }, "variance_explained_percent": { "description": "Percentage of the brain block's variance this component accounts for, as reported. Not interchangeable with `crossblock_covariance_percent`: one is variance within a block and the other covariance between blocks, and averaging across the two would combine incomparable quantities.", "maximum": 100, @@ -2648,6 +2600,54 @@ "number", "null" ] + }, + "crossblock_covariance_percent": { + "description": "Percentage of crossblock covariance this latent variable accounts for, as reported. The nearest thing a two-block decomposition has to an effect size, and what a paper uses to justify reporting one latent variable and not another. Unset when `second_block` is `[none]`, there being no second block to cross.", + "maximum": 100, + "minimum": 0, + "type": [ + "number", + "null" + ] + }, + "map_quantity": { + "anyOf": [ + { + "$ref": "#/$defs/MapQuantity" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "What the values of the reported map represent -- a loading, a weight, a PLS salience, a standardized score." + }, + "salience_threshold": { + "description": "The criterion deciding which elements count as reliable contributors, as reported -- conventionally a bootstrap ratio cutoff such as \"|BSR| > 3.0\", or a standardized cutoff such as \"|Z| > 3.5\". Not `InferenceSettings`: a bootstrap ratio is a stability measure and not a test statistic, so reading it as a voxelwise threshold would misstate the inference.", + "type": [ + "string", + "null" + ] + }, + "polarity_semantics": { + "anyOf": [ + { + "$ref": "#/$defs/PolaritySemantics" + }, + { + "type": "null" + } + ], + "description": "Whether the sign of the reported map carries meaning. A latent variable's sign is arbitrary up to a flip, and so is an independent component's, so a paper must say what positive means before a direction can be pooled." + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "LatentDecompositionDetails" + ], + "type": "string" } }, "required": [ @@ -2667,20 +2667,20 @@ "license_id": { "const": "all-rights-reserved" }, - "stated_as": { + "stated_by": { "not": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/MetadataSource" } ] } }, - "stated_by": { + "stated_as": { "not": { "anyOf": [ { - "$ref": "#/$defs/MetadataSource" + "type": "string" } ] } @@ -2698,20 +2698,12 @@ ] }, "properties": { - "determination": { - "$ref": "#/$defs/LicenseDetermination" - }, "license_id": { "$ref": "#/$defs/LicenseId", "description": "Which licence, as OpenAlex's licence slugs name them (`cc-by`, `cc-by-nc`, ...). Not an SPDX License Identifier: sources rarely state the licence's version, and SPDX identifiers require one." }, - "stated_as": { - "description": "The statement exactly as the source gave it: a licence URL, a licence name, or a copyright line. What `other` holds, and what attribution quotes. Never empty: a source that gave no statement did not state a licence.", - "pattern": "\\S", - "type": [ - "string", - "null" - ] + "determination": { + "$ref": "#/$defs/LicenseDetermination" }, "stated_by": { "anyOf": [ @@ -2723,6 +2715,14 @@ } ], "description": "The source whose statement this is." + }, + "stated_as": { + "description": "The statement exactly as the source gave it: a licence URL, a licence name, or a copyright line. What `other` holds, and what attribution quotes. Never empty: a source that gave no statement did not state a licence.", + "pattern": "\\S", + "type": [ + "string", + "null" + ] } }, "required": [ @@ -2731,17 +2731,17 @@ ], "then": { "properties": { - "stated_as": { + "stated_by": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/MetadataSource" } ] }, - "stated_by": { + "stated_as": { "anyOf": [ { - "$ref": "#/$defs/MetadataSource" + "type": "string" } ] } @@ -2784,50 +2784,35 @@ "additionalProperties": false, "description": "An MRI acquisition of any kind -- functional, structural, or diffusion, as modality records. Its own attributes are the ones commonly useful for study selection, subgrouping, or sensitivity analyses in coordinate- or image-based meta-analysis.", "properties": { - "acquisition_duration_seconds": { - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", + "magnetic_field_strength_tesla": { + "description": "Nominal static magnetic field strength in tesla.", "minimum": 0, "type": [ "number", "null" ] }, - "acquisition_type": { - "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", - "enum": [ - "MRI" - ], - "type": "string" - }, - "acquisition_voxel_size_mm": { - "description": "Original acquisition voxel size [x, y, z] in millimeters, before interpolation or resampling.", - "items": { - "minimum": 0, - "type": "number" - }, + "pulse_sequence_type": { + "description": "General pulse sequence family, such as EPI, MPRAGE, SPGR, or spin echo.", "type": [ - "array", + "string", "null" ] }, - "brain_coverage": { - "anyOf": [ - { - "$ref": "#/$defs/BrainCoverage" - }, - { - "type": "null" - } - ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "mr_acquisition_type": { + "description": "Acquisition dimensionality, such as 2D or 3D; useful for distinguishing protocols with materially different spatial sampling.", "type": [ "string", "null" - ], - "x-reference": "Device" + ] + }, + "repetition_time_seconds": { + "description": "Time between the start of successive acquired volumes, in seconds.", + "minimum": 0, + "type": [ + "number", + "null" + ] }, "echo_time_seconds": { "description": "Echo time in seconds, as a list of one value per echo. A multi-echo sequence has several; a single-echo one has a list of length one.", @@ -2840,17 +2825,35 @@ "null" ] }, - "id": { - "type": "string" + "acquisition_voxel_size_mm": { + "description": "Original acquisition voxel size [x, y, z] in millimeters, before interpolation or resampling.", + "items": { + "minimum": 0, + "type": "number" + }, + "type": [ + "array", + "null" + ] }, - "magnetic_field_strength_tesla": { - "description": "Nominal static magnetic field strength in tesla.", + "number_of_volumes": { + "description": "Number of acquired volumes contributing to the recording, when reported; a proxy for scan length and temporal sampling in functional acquisitions.", "minimum": 0, "type": [ - "number", + "integer", "null" ] }, + "id": { + "type": "string" + }, + "acquisition_type": { + "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", + "enum": [ + "MRI" + ], + "type": "string" + }, "modality": { "anyOf": [ { @@ -2862,35 +2865,32 @@ ], "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." }, - "mr_acquisition_type": { - "description": "Acquisition dimensionality, such as 2D or 3D; useful for distinguishing protocols with materially different spatial sampling.", - "type": [ - "string", - "null" - ] - }, - "number_of_volumes": { - "description": "Number of acquired volumes contributing to the recording, when reported; a proxy for scan length and temporal sampling in functional acquisitions.", + "acquisition_duration_seconds": { + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", "minimum": 0, "type": [ - "integer", + "number", "null" ] }, - "pulse_sequence_type": { - "description": "General pulse sequence family, such as EPI, MPRAGE, SPGR, or spin echo.", + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", "type": [ "string", "null" - ] + ], + "x-reference": "Device" }, - "repetition_time_seconds": { - "description": "Time between the start of successive acquired volumes, in seconds.", - "minimum": 0, - "type": [ - "number", - "null" - ] + "brain_coverage": { + "anyOf": [ + { + "$ref": "#/$defs/BrainCoverage" + }, + { + "type": "null" + } + ], + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -2935,6 +2935,10 @@ "additionalProperties": false, "description": "The scientific quantity of the map, independent of the statistic used to test it. When a paper alternates terms for the same map -- grey matter concentration in Methods and volume in Results -- take the Methods term for type and keep both wordings in source_label.\nOne record may be referenced by several Analyses, so every field must hold for all of them; an analysis of a different quantity takes its own record. The test is mechanical: two analyses share a record when `family`, `type`, `specific_metric` and `unit` all match. A study measures one or two quantities and tests them many ways, so sharing is the common case -- eight analyses of one connectivity measure are one record, not eight.\nNot a property of the thresholding scheme: one correction scheme routinely covers several measures, and one measure is often tested under several schemes. See storage-schema-design-notes.md for the reasoning behind this shape.", "properties": { + "id": { + "description": "Stable identifier used by Analysis foreign-key references.", + "type": "string" + }, "family": { "anyOf": [ { @@ -2946,9 +2950,16 @@ ], "description": "Broad imaging or signal family, for coarse filtering." }, - "id": { - "description": "Stable identifier used by Analysis foreign-key references.", - "type": "string" + "type": { + "anyOf": [ + { + "$ref": "#/$defs/MeasureType" + }, + { + "type": "string" + } + ], + "description": "Normalized quantity, for cross-study queries." }, "source_label": { "description": "The paper's own term for the measure. Unset only when the source does not identify the measure.", @@ -2964,17 +2975,6 @@ "null" ] }, - "type": { - "anyOf": [ - { - "$ref": "#/$defs/MeasureType" - }, - { - "type": "string" - } - ], - "description": "Normalized quantity, for cross-study queries." - }, "unit": { "description": "The unit the measure is reported in, when the source gives one and it carries information -- mm for a thickness, mL for a volume. Omitted for a unitless quantity such as a correlation or a normalized index.", "type": [ @@ -3035,14 +3035,14 @@ "additionalProperties": false, "description": "The mediation model an effect's tested quantity is a path through: which path, and the intervening term it runs through. Present only when there is one; the ordinary case leaves `Effect.mediation` unset. Both slots are required, so a path always names its mediator.", "properties": { + "path": { + "$ref": "#/$defs/EffectPath", + "description": "The path through the mediation model represented by this effect's map. An effect that is not a mediated path has no Mediation, so there is no `not_applicable` case." + }, "mediator": { "description": "The intervening term, by identifier. It must be a term of the ModelEstimation the containing Analysis names, or of a stage that model reaches through `inputs_from`.", "type": "string", "x-reference": "ModelTerm" - }, - "path": { - "$ref": "#/$defs/EffectPath", - "description": "The path through the mediation model represented by this effect's map. An effect that is not a mediated path has no Mediation, so there is no `not_applicable` case." } }, "required": [ @@ -3088,35 +3088,10 @@ "additionalProperties": false, "description": "How a model was specified in software and estimated: its terms, the family, the source's wording for it, the estimator, the stage, the software, the pipeline that produced the data it was fitted to, and the spatial element it was fitted over. One record may be referenced by several Analyses, so every field must hold for all of them; an analysis specified or estimated differently takes its own record. **The data a model was fitted to is part of its specification**.\nOne record is one estimation stage. Neuroimaging splits a model across stages for tractability, and `inputs_from` is what puts it back together: a stage's terms are its own plus those of the stages it consumed, so the split costs the record nothing while each stage keeps its own family, estimator, software and basis.", "properties": { - "estimator": { - "description": "Estimation method, such as OLS, WLS, maximum likelihood, restricted maximum likelihood, permutation, or Bayesian estimation.", - "type": [ - "string", - "null" - ] - }, - "hrf_model": { - "description": "The haemodynamic response basis the design matrix was built with: a canonical HRF, a canonical plus temporal or dispersion derivatives, an FIR or gamma basis, or a named alternative. Unset for a measure that models no timeseries -- morphometry, diffusion, static PET.", - "type": [ - "string", - "null" - ] - }, "id": { "description": "Stable identifier used by Analysis foreign-key references.", "type": "string" }, - "inputs_from": { - "description": "The models whose fitted output this model was estimated on: the first-level GLM whose contrast images a group model takes as its data, the subject-level fit an ROI ANOVA summarises. The stage graph, recorded only where the source states it.\n**A model's terms are its own plus, transitively, those of the models named here.** That combined list is what `terms` would be had the analysis been fitted in one step.\nLeave it empty when the source describes one design matrix, and when it describes the lower stage too thinly to constitute a model. Empty is not a claim that no lower stage existed; it is the absence of a statement about one, exactly as an unrecorded term is.\nMust be acyclic, no model may reach itself.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "ModelEstimation" - }, "model_family": { "anyOf": [ { @@ -3128,6 +3103,17 @@ ], "description": "Normalized statistical model family, which is the queryable form. Required, so a paper that never says still carries the slot, as `extraction_status: not_reported`. A method the vocabulary cannot name -- ICA, representational similarity, a decoder -- is written here as free text; the Analysis.details subclass also identifies the method." }, + "model_type": { + "description": "The source's own wording for the model, minimally cleaned, such as mass- univariate GLM, meta-analysis, multivariate, predictive model, ICA, or representational similarity analysis.", + "type": "string" + }, + "hrf_model": { + "description": "The haemodynamic response basis the design matrix was built with: a canonical HRF, a canonical plus temporal or dispersion derivatives, an FIR or gamma basis, or a named alternative. Unset for a measure that models no timeseries -- morphometry, diffusion, static PET.", + "type": [ + "string", + "null" + ] + }, "model_settings": { "description": "Essential estimator, variance, autocorrelation, covariance, regularization, and software-specific settings.", "type": [ @@ -3135,20 +3121,16 @@ "null" ] }, - "model_type": { - "description": "The source's own wording for the model, minimally cleaned, such as mass- univariate GLM, meta-analysis, multivariate, predictive model, ICA, or representational similarity analysis.", + "stage": { + "description": "Stage of analysis, in the source's terms: run, session, subject and group are the usual words, and any other the source uses is storable as written. Deliberately left open -- no vocabulary fits every design, and nothing derives anything from it.\nA label, not an ordering. Only `inputs_from` records which stage fed another: two records labelled `group` say nothing about their relationship, and these strings never imply a chain.", "type": "string" }, - "preprocessing": { - "description": "The pipelines that produced the data this model was fitted to. Every Analysis referencing this record came through them, so a model fitted to differently prepared data -- unsmoothed images for a classifier, smoothed ones for a mass-univariate fit -- takes its own record rather than sharing one.\nMore than one entry when the fit consumed more than one kind of image: a multimodal regression of perfusion on grey-matter volume was fitted to the output of both pipelines, and naming either alone says the other's smoothing and normalisation did not touch the map.", - "items": { - "type": "string" - }, + "estimator": { + "description": "Estimation method, such as OLS, WLS, maximum likelihood, restricted maximum likelihood, permutation, or Bayesian estimation.", "type": [ - "array", + "string", "null" - ], - "x-reference": "Preprocessing" + ] }, "software": { "description": "Statistical software name, version, and relevant implementation details.", @@ -3171,9 +3153,27 @@ ], "description": "The element this model was fitted over. Distinct from the Analysis's `spatial_scope`, which gives the extent rather than the element: a whole-brain model can be voxelwise or vertexwise, and a model over an ROI can average the region or test its parcels. Record it rather than inferring it from `coordinate_space`.\nA model fitted over a different element is a different model, so a voxelwise GLM and an ROI-mean ANOVA of the same design take two records: no contrast of one can be read off the other's fit." }, - "stage": { - "description": "Stage of analysis, in the source's terms: run, session, subject and group are the usual words, and any other the source uses is storable as written. Deliberately left open -- no vocabulary fits every design, and nothing derives anything from it.\nA label, not an ordering. Only `inputs_from` records which stage fed another: two records labelled `group` say nothing about their relationship, and these strings never imply a chain.", - "type": "string" + "preprocessing": { + "description": "The pipelines that produced the data this model was fitted to. Every Analysis referencing this record came through them, so a model fitted to differently prepared data -- unsmoothed images for a classifier, smoothed ones for a mass-univariate fit -- takes its own record rather than sharing one.\nMore than one entry when the fit consumed more than one kind of image: a multimodal regression of perfusion on grey-matter volume was fitted to the output of both pipelines, and naming either alone says the other's smoothing and normalisation did not touch the map.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "Preprocessing" + }, + "inputs_from": { + "description": "The models whose fitted output this model was estimated on: the first-level GLM whose contrast images a group model takes as its data, the subject-level fit an ROI ANOVA summarises. The stage graph, recorded only where the source states it.\n**A model's terms are its own plus, transitively, those of the models named here.** That combined list is what `terms` would be had the analysis been fitted in one step.\nLeave it empty when the source describes one design matrix, and when it describes the lower stage too thinly to constitute a model. Empty is not a claim that no lower stage existed; it is the absence of a statement about one, exactly as an unrecorded term is.\nMust be acyclic, no model may reach itself.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ], + "x-reference": "ModelEstimation" }, "terms": { "description": "The terms **this stage** was specified with -- the columns of its design matrix, in the general sense that covers a GLM's regressors and the predictors of any other family. Stated once here however many contrasts are taken from them, and never restated on a stage above: the terms of a lower stage are reached through `inputs_from`.\nRecord every term the source states, including ones no reported contrast tests, and including ones mentioned only in passing: \"controlling for age and sex\" is a statement about the design matrix. This list, together with the lists reached through `inputs_from`, is **the adjustment set of every contrast taken from this model** -- each contrast's is that combined list minus the terms its cells name -- so a term omitted from every stage reads as one the model did not adjust for. Where the list is incomplete, the slot's extraction_status is what says so.\nMay be empty, and legitimately is for a stage that fits only an intercept: a one-sample group test over first-level contrast images has no column of its own. Do not invent an intercept term to fill it.\nAnalyses whose design matrices differ take their own records rather than sharing one.", @@ -3211,6 +3211,28 @@ "additionalProperties": false, "description": "One term of the model: a column of the design matrix, or the equivalent predictor in a family with no design matrix. A categorical term names its levels and the entities carrying them; a continuous term carries the facts that make a slope interpretable. Nothing here is a fact about any one contrast -- what a contrast did with this term is a `Cell` on that contrast's Effect. A term with no cell on some contrast is a term that contrast was adjusted for rather than tested.", "properties": { + "id": { + "description": "Stable identifier, referenced by an Effect's `cells` and its `mediation.mediator`, and by `interaction_with`.", + "type": "string" + }, + "name": { + "description": "Term label, unique within the model: a name appearing twice is one term recorded twice. For a categorical term this is the factor's identity.\nOften a label the source never writes. A paper reporting 1-back, 2-back and 3-back conditions states the levels and states that they vary together, but frequently names no axis; `load` is then a name supplied for a grouping the source does make. That is permitted, within two limits: the levels must be source-stated, and the name must not assert a comparison or a construct the paper does not.", + "type": "string" + }, + "type": { + "$ref": "#/$defs/TermType", + "description": "Whether the term is categorical or continuous. Categorical when the column distinguishes named cells of the design -- occasions, cohorts, conditions, arms, regions -- whether or not the paper names the axis; `levels` is then required. Continuous when the column carries a measured value and the model fits a slope.\nSmall as it is, this field settles four others: whether `levels` exists, whether a `Cell` here takes a `level`, whether the term can be crossed, and which branch of the derived kind fires. A term recorded continuous is one no contrast can be built across.\n**A term whose name states a comparison -- `pre > post`, `A versus B` -- is almost always a categorical factor written down from its contrast's side.** The design matrix distinguished two occasions, cohorts or conditions; the paper labelled the difference and left the axis unnamed. Record the factor with a level per side and let the cells carry the direction (`representing-models.md` §5.6). Collapsed into one continuous column, nothing says what was compared and the effect reads as a slope.\nThe exception is a column genuinely holding one number per participant -- a difference score, a percent change entered as a covariate. That is continuous, and `source_definition` records the subtraction it came from." + }, + "levels": { + "description": "The levels of a categorical term, and for each the entities carrying it. This is the whole of what a factor is: its level vocabulary, and which cohort, condition, occasion, or arm occupies each cell. Record a level the source names even when no reported contrast uses it -- a three-level factor contrasted at two levels is still a three-level factor. Empty for a continuous term.", + "items": { + "$ref": "#/$defs/FactorLevel" + }, + "type": [ + "array", + "null" + ] + }, "assessment": { "description": "The assessment whose measurements supply a continuous term, **including when the column is derived from them rather than being one of them**: a change score, a percent change, a composite of subscales, a residualized score. The instrument is still where the values came from, and one administration or several makes no difference to that -- `region` says the same by example, an ROI mean and a PPI regressor both naming their region.\nOne instrument, one record, however many columns it supplies: two subscale scores of one questionnaire each name it, and which subscale a column carries is the term's `name`.\nUnset only when the values come from somewhere the study does not record as an Assessment -- age, dose, a motion parameter, cardiac phase, scanner drift -- in which case `source_definition` names the origin. A term with neither is a column whose origin the record does not state.", "type": [ @@ -3219,6 +3241,28 @@ ], "x-reference": "Assessment" }, + "region": { + "description": "The region whose signal supplies this term: a seed timecourse, an ROI mean covariate, a PPI physiological regressor. The counterpart of `assessment` for a column drawn from a place in the brain rather than an instrument.\nSingular, so it does not serve a factor that *compares* regions: that is one term with several levels, and the regions hang off `FactorLevel.regions`.", + "type": [ + "string", + "null" + ], + "x-reference": "Region" + }, + "variation_level": { + "anyOf": [ + { + "$ref": "#/$defs/VariationLevel" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Whether this term moves within a participant or only across the sample. Populate it for **any** term: a repeated-measures factor is within-subject and a diagnosis factor is between-subject in exactly the sense a trial-wise modulator and an age covariate are.\nFor a continuous term it is what separates a parametric modulation from a cross-subject regression, and the derived kind of any effect testing it reads this on that basis. For a categorical factor it feeds no derivation, so setting it changes no other value; it is the factor's scope, and two studies that crossed a factor within participants and between them did not estimate the same thing.\nA categorical factor's levels usually settle it, and the two should agree: levels naming `groups` are between-subject, since a participant belongs to one cohort; levels naming `timepoints` are within-subject, since an occasion is when the same people were measured again; levels naming `conditions` are within-subject in the ordinary repeated-measures case; levels naming `arms` follow `StudyDesign.assignment_structure`, crossover being within and parallel between.\nA level the vocabulary cannot name is written here as free text; the derived kind then cannot choose between a modulation and a regression, so it is undetermined for that record rather than wrong." + }, "functional_form": { "anyOf": [ { @@ -3233,10 +3277,6 @@ ], "description": "Shape of the term's relationship to the measure. A squared term with a negative coefficient is an inverted-U relationship, not a negative association, so non-linear and transformed terms must not be recorded as linear. A model fitting both a linear and a quadratic form of the same measurement has two terms, not one.\nNot where a product is recorded: a non-empty `interaction_with` is what makes a column a product." }, - "id": { - "description": "Stable identifier, referenced by an Effect's `cells` and its `mediation.mediator`, and by `interaction_with`.", - "type": "string" - }, "interaction_with": { "description": "The other terms this term is a product of -- what makes this column an interaction column. Populate it when the source names an interaction term in one model; leave it empty when the source describes a regression on a contrast.\nThe terms it may name are those of this model and of the stages it reaches through `inputs_from`, which is how a group stage crosses a cohort factor with a first-level condition: the product column is declared where it was fitted and names the lower stage's column directly.\nA crossing of two factors is normally read off the cells, since a factor compared against itself carries a positive cell and a negative one. A continuous term has no levels and so cannot be crossed that way, which makes this slot the only record of a moderation such as `age x group`; the derived kind reads it before `variation_level`.\nA product of terms that are all categorical and all crossed is legal but decides nothing, the crossed cells already saying what it says, and is flagged for review.\nA product column declares no `levels` of its own; a signed cell on one with a continuous component may name a level of the factor, saying whose slope the sign describes. Such a column is not a covariate of a contrast whose cells name all of its components.", "items": { @@ -3248,27 +3288,12 @@ ], "x-reference": "ModelTerm" }, - "levels": { - "description": "The levels of a categorical term, and for each the entities carrying it. This is the whole of what a factor is: its level vocabulary, and which cohort, condition, occasion, or arm occupies each cell. Record a level the source names even when no reported contrast uses it -- a three-level factor contrasted at two levels is still a three-level factor. Empty for a continuous term.", - "items": { - "$ref": "#/$defs/FactorLevel" - }, - "type": [ - "array", - "null" - ] - }, - "name": { - "description": "Term label, unique within the model: a name appearing twice is one term recorded twice. For a categorical term this is the factor's identity.\nOften a label the source never writes. A paper reporting 1-back, 2-back and 3-back conditions states the levels and states that they vary together, but frequently names no axis; `load` is then a name supplied for a grouping the source does make. That is permitted, within two limits: the levels must be source-stated, and the name must not assert a comparison or a construct the paper does not.", - "type": "string" - }, - "region": { - "description": "The region whose signal supplies this term: a seed timecourse, an ROI mean covariate, a PPI physiological regressor. The counterpart of `assessment` for a column drawn from a place in the brain rather than an instrument.\nSingular, so it does not serve a factor that *compares* regions: that is one term with several levels, and the regions hang off `FactorLevel.regions`.", + "unit": { + "description": "The unit a continuous term is measured in, when the source gives one -- years for age, mg for a dose. It is what makes a reported slope interpretable.", "type": [ "string", "null" - ], - "x-reference": "Region" + ] }, "source_definition": { "description": "The source's own definition of the term, and the origin of a continuous term that no Assessment supplies, such as age, dose, a motion parameter, cardiac phase, or scanner drift.\n**Required for a derived column, whatever its `assessment` says.** A change score names its instrument in `assessment` and its derivation here, in the source's own words and including the occasions it spans -- \"percent reduction in BDI, (post-pre)/pre, from baseline to post-treatment\". Nothing else can carry those occasions: `FactorLevel.timepoints` names the occasions a factor *compares*, and a derived column has no levels to hang them on, so where a study has several post-intervention occasions this is the only thing separating a change to the endpoint from a change to a later follow-up.\nNot the shape of the fitted relationship, which is `functional_form`: a percent change entered linearly is a linear term computed from a difference, so recording the construction as the form would lose whether the fit was linear or quadratic. A transform *of the measure* the vocabulary cannot name still goes in functional_form.", @@ -3276,31 +3301,6 @@ "string", "null" ] - }, - "type": { - "$ref": "#/$defs/TermType", - "description": "Whether the term is categorical or continuous. Categorical when the column distinguishes named cells of the design -- occasions, cohorts, conditions, arms, regions -- whether or not the paper names the axis; `levels` is then required. Continuous when the column carries a measured value and the model fits a slope.\nSmall as it is, this field settles four others: whether `levels` exists, whether a `Cell` here takes a `level`, whether the term can be crossed, and which branch of the derived kind fires. A term recorded continuous is one no contrast can be built across.\n**A term whose name states a comparison -- `pre > post`, `A versus B` -- is almost always a categorical factor written down from its contrast's side.** The design matrix distinguished two occasions, cohorts or conditions; the paper labelled the difference and left the axis unnamed. Record the factor with a level per side and let the cells carry the direction (`representing-models.md` §5.6). Collapsed into one continuous column, nothing says what was compared and the effect reads as a slope.\nThe exception is a column genuinely holding one number per participant -- a difference score, a percent change entered as a covariate. That is continuous, and `source_definition` records the subtraction it came from." - }, - "unit": { - "description": "The unit a continuous term is measured in, when the source gives one -- years for age, mg for a dose. It is what makes a reported slope interpretable.", - "type": [ - "string", - "null" - ] - }, - "variation_level": { - "anyOf": [ - { - "$ref": "#/$defs/VariationLevel" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Whether this term moves within a participant or only across the sample. Populate it for **any** term: a repeated-measures factor is within-subject and a diagnosis factor is between-subject in exactly the sense a trial-wise modulator and an age covariate are.\nFor a continuous term it is what separates a parametric modulation from a cross-subject regression, and the derived kind of any effect testing it reads this on that basis. For a categorical factor it feeds no derivation, so setting it changes no other value; it is the factor's scope, and two studies that crossed a factor within participants and between them did not estimate the same thing.\nA categorical factor's levels usually settle it, and the two should agree: levels naming `groups` are between-subject, since a participant belongs to one cohort; levels naming `timepoints` are within-subject, since an occasion is when the same people were measured again; levels naming `conditions` are within-subject in the ordinary repeated-measures case; levels naming `arms` follow `StudyDesign.assignment_structure`, crossover being within and parallel between.\nA level the vocabulary cannot name is written here as free text; the derived kind then cannot choose between a modulation and a regression, so it is undetermined for that record rather than wrong." } }, "required": [ @@ -3320,17 +3320,6 @@ "additionalProperties": false, "description": "Payload for an effect that is grounded in the source but has no stable structured decomposition. Analysis.definition remains required, so the meaning survives even though the structure does not.", "properties": { - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "NotStructurableDetails" - ], - "type": "string" - }, - "explanation": { - "description": "What specifically could not be represented, in the source's own terms. The reason gives the category; this gives the instance, so a reader can judge whether the effect is usable.", - "type": "string" - }, "reason": { "anyOf": [ { @@ -3341,6 +3330,17 @@ } ], "description": "The kind of obstacle that prevented structuring, selected from a short vocabulary. Pairs with `explanation`, which says what specifically was lost." + }, + "explanation": { + "description": "What specifically could not be represented, in the source's own terms. The reason gives the category; this gives the instance, so a reader can judge whether the effect is usable.", + "type": "string" + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "NotStructurableDetails" + ], + "type": "string" } }, "required": [ @@ -3387,27 +3387,27 @@ "additionalProperties": false, "description": "Payload for a method outside the first-class families. Available for any analysis, so an unusual design never has to delete true terms in order to validate.", "properties": { - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "OtherAnalysisDetails" - ], - "type": "string" - }, "method_label": { "description": "The method's name as the source gives it. This is the only record of what the method was, so write what the paper calls it rather than a paraphrase.", "type": "string" }, - "reason_first_class_type_not_used": { - "description": "Why no first-class details subclass fits this method.", - "type": "string" - }, "structured_details": { "description": "JSON-encoded method-specific payload, when the method has structure worth keeping.", "type": [ "string", "null" ] + }, + "reason_first_class_type_not_used": { + "description": "Why no first-class details subclass fits this method.", + "type": "string" + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "OtherAnalysisDetails" + ], + "type": "string" } }, "required": [ @@ -3422,14 +3422,20 @@ "additionalProperties": false, "description": "An acquisition whose modality has no class of its own: MEG, SPECT, and `other`. Nothing here is normalized, so this is where a modality the schema does not name keeps its parameters. A parameter that MRI, EEG, FNIRS, or PET has no field for belongs on that sibling class instead.", "properties": { - "acquisition_duration_seconds": { - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", - "minimum": 0, + "modality_label": { + "description": "The source's own name for the modality or recording technique, verbatim. Always populated, since the Modality vocabulary is coarser than what a source reports.", + "type": "string" + }, + "reason_no_modality_class_used": { + "description": "Why this is not an MRI, EEG, FNIRS, or PET record.", "type": [ - "number", + "string", "null" ] }, + "id": { + "type": "string" + }, "acquisition_type": { "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", "enum": [ @@ -3437,28 +3443,6 @@ ], "type": "string" }, - "brain_coverage": { - "anyOf": [ - { - "$ref": "#/$defs/BrainCoverage" - }, - { - "type": "null" - } - ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", - "type": [ - "string", - "null" - ], - "x-reference": "Device" - }, - "id": { - "type": "string" - }, "modality": { "anyOf": [ { @@ -3470,16 +3454,32 @@ ], "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." }, - "modality_label": { - "description": "The source's own name for the modality or recording technique, verbatim. Always populated, since the Modality vocabulary is coarser than what a source reports.", - "type": "string" + "acquisition_duration_seconds": { + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", + "minimum": 0, + "type": [ + "number", + "null" + ] }, - "reason_no_modality_class_used": { - "description": "Why this is not an MRI, EEG, FNIRS, or PET record.", + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", "type": [ "string", "null" - ] + ], + "x-reference": "Device" + }, + "brain_coverage": { + "anyOf": [ + { + "$ref": "#/$defs/BrainCoverage" + }, + { + "type": "null" + } + ], + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -3506,61 +3506,27 @@ "additionalProperties": false, "description": "A PET acquisition. Its own attributes are the ones defining the biological signal or spatial comparability, and so are likely to be useful for study selection, subgrouping, or image harmonization.", "properties": { - "acquisition_duration_seconds": { - "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", - "minimum": 0, + "image_units": { + "description": "Units encoded in the PET image, such as Bq/mL or SUV.", "type": [ - "number", + "string", "null" ] }, - "acquisition_type": { - "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", - "enum": [ - "PET" - ], - "type": "string" - }, - "brain_coverage": { - "anyOf": [ - { - "$ref": "#/$defs/BrainCoverage" - }, - { - "type": "null" - } - ], - "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." - }, - "device": { - "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", + "tracer_name": { + "description": "Name of the injected tracer compound.", "type": [ "string", "null" - ], - "x-reference": "Device" - }, - "id": { - "type": "string" + ] }, - "image_units": { - "description": "Units encoded in the PET image, such as Bq/mL or SUV.", + "tracer_radionuclide": { + "description": "Radioisotope labeling the tracer, such as C11, F18, or O15.", "type": [ "string", "null" ] }, - "modality": { - "anyOf": [ - { - "$ref": "#/$defs/Modality" - }, - { - "type": "string" - } - ], - "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." - }, "mode_of_administration": { "description": "Tracer administration mode, such as bolus, infusion, or bolus plus infusion.", "type": [ @@ -3568,6 +3534,21 @@ "null" ] }, + "scan_type": { + "description": "Whether the analyzed PET data were acquired or summarized as a static or dynamic scan.", + "type": [ + "string", + "null" + ] + }, + "uptake_time_seconds": { + "description": "Interval from tracer administration to the start of the analyzed scan window, in seconds.", + "minimum": 0, + "type": [ + "number", + "null" + ] + }, "reconstructed_voxel_size_mm": { "description": "Reconstructed PET voxel size [x, y, z] in millimeters.", "items": { @@ -3579,13 +3560,6 @@ "null" ] }, - "scan_type": { - "description": "Whether the analyzed PET data were acquired or summarized as a static or dynamic scan.", - "type": [ - "string", - "null" - ] - }, "spatial_resolution_mm": { "description": "Reported reconstructed spatial resolution, such as FWHM dimensions, in millimeters.", "items": { @@ -3597,27 +3571,53 @@ "null" ] }, - "tracer_name": { - "description": "Name of the injected tracer compound.", + "id": { + "type": "string" + }, + "acquisition_type": { + "description": "The concrete subclass this record instantiates. Derived by the mapper from the modality value's `instantiates`, never extracted.", + "enum": [ + "PET" + ], + "type": "string" + }, + "modality": { + "anyOf": [ + { + "$ref": "#/$defs/Modality" + }, + { + "type": "string" + } + ], + "description": "Imaging or recording modality. A value in the Modality vocabulary also names the class holding this acquisition's modality-specific parameters, through that value's `instantiates`.\nOpen vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range would discard the whole acquisition over one unrecognised word. Only a vocabulary value carries an `instantiates`, so an acquisition outside it resolves to the base class until normalization maps the wording back on -- which is why `acquisition_type` is derived rather than extracted, and why an unmapped modality is worth reporting." + }, + "acquisition_duration_seconds": { + "description": "Total usable acquisition or recording duration in seconds. Useful for filtering studies by the amount of data contributing to an analysis, especially resting-state and continuous recordings.", + "minimum": 0, "type": [ - "string", + "number", "null" ] }, - "tracer_radionuclide": { - "description": "Radioisotope labeling the tracer, such as C11, F18, or O15.", + "device": { + "description": "The scanner, amplifier, or recording system this acquisition was made on, by identifier. Acquisitions made on one machine reference one record; a second record asserts the study used a second machine.", "type": [ "string", "null" - ] + ], + "x-reference": "Device" }, - "uptake_time_seconds": { - "description": "Interval from tracer administration to the start of the analyzed scan window, in seconds.", - "minimum": 0, - "type": [ - "number", - "null" - ] + "brain_coverage": { + "anyOf": [ + { + "$ref": "#/$defs/BrainCoverage" + }, + { + "type": "null" + } + ], + "description": "How much of the brain the acquired volume contained. This is the extent the SCANNER sampled, and it is a third axis distinct from the two the schema already carries: `Analysis.spatial_scope` is what the analysis tested, and `InferenceSettings.correction_scope` with `search_volume` is where multiple comparisons were controlled. A voxelwise analysis over a volume that clipped the cerebellum is `whole_brain` in scope and `partial` here, and an ROI analysis of a whole-head acquisition is the reverse. Never infer this from either of those.\nIt describes the volume behind the REPORTED results. A scan that clipped a structure in participants who were then excluded is `whole_brain`, because the analysed volume was not clipped; calling it `partial` would say the opposite of what the results rest on.\nA field of view or slice prescription in millimetres is not by itself a statement of coverage. Where only the geometry is given, the value is an inference and `value_source` records it as one. Which structure a partial volume missed is not a field of its own -- the evidence span carries that detail." } }, "required": [ @@ -3636,6 +3636,13 @@ "description": "The measure as the source names it, such as accuracy, balanced accuracy, AUC, d-prime, sensitivity, specificity, or a correlation between predicted and observed scores.", "type": "string" }, + "value": { + "description": "The value as reported, in the source's own scale: a percentage and a proportion are both recorded as written rather than converted, since the scale is not always stated and converting would invent precision.", + "type": [ + "number", + "null" + ] + }, "reference_value": { "description": "Numeric reference this measure was compared against, when reported -- chance for an accuracy, zero for a correlation. Each measure carries its own: accuracy against chance and AUC against 0.5 are two references, not one. `relation` says which side of it the measure fell on.", "type": [ @@ -3653,13 +3660,6 @@ } ], "description": "Direction of this measure relative to its own reference_value." - }, - "value": { - "description": "The value as reported, in the source's own scale: a percentage and a proportion are both recorded as written rather than converted, since the scale is not always stated and converting would invent precision.", - "type": [ - "number", - "null" - ] } }, "required": [ @@ -3749,6 +3749,10 @@ "additionalProperties": false, "description": "An ordered preprocessing pipeline applied to acquired data before or as part of statistical modeling. This can be expanded with COBIDAS.", "properties": { + "id": { + "description": "Stable identifier used by Analysis foreign-key references.", + "type": "string" + }, "description": { "description": "Narrative overview of the pipeline, its inputs, outputs, and intended purpose.", "type": [ @@ -3756,23 +3760,18 @@ "null" ] }, - "id": { - "description": "Stable identifier used by Analysis foreign-key references.", - "type": "string" - }, - "smoothing_fwhm_mm": { - "description": "Full width at half maximum of the applied spatial smoothing kernel, in millimetres: a list of one value for an isotropic kernel and of three for an anisotropic [x, y, z] one. Record 0 when the source states that no smoothing was applied.", + "software": { + "description": "Software packages or methods used, including version and revision identifiers.", "items": { - "minimum": 0, - "type": "number" + "type": "string" }, "type": [ "array", "null" ] }, - "software": { - "description": "Software packages or methods used, including version and revision identifiers.", + "steps": { + "description": "Ordered free-text preprocessing operation names, listed in execution order; examples include slice-timing correction, motion correction, distortion correction, spatial normalization, and smoothing. The smoothing kernel itself is `smoothing_fwhm_mm` rather than a phrase in this list, because it is the one preprocessing parameter a synthesis filters on.", "items": { "type": "string" }, @@ -3781,10 +3780,11 @@ "null" ] }, - "steps": { - "description": "Ordered free-text preprocessing operation names, listed in execution order; examples include slice-timing correction, motion correction, distortion correction, spatial normalization, and smoothing. The smoothing kernel itself is `smoothing_fwhm_mm` rather than a phrase in this list, because it is the one preprocessing parameter a synthesis filters on.", + "smoothing_fwhm_mm": { + "description": "Full width at half maximum of the applied spatial smoothing kernel, in millimetres: a list of one value for an isotropic kernel and of three for an anisotropic [x, y, z] one. Record 0 when the source states that no smoothing was applied.", "items": { - "type": "string" + "minimum": 0, + "type": "number" }, "type": [ "array", @@ -3831,12 +3831,13 @@ ] }, "properties": { - "atlas": { - "description": "The atlas, parcellation or template it came from, as named -- AAL, WFU_Pickatlas, Schaefer 400 -- with version or parcel index when given. `definition_method: atlas` records that one was used, not which, which is not enough to pool two studies.", - "type": [ - "string", - "null" - ] + "id": { + "description": "Stable identifier.", + "type": "string" + }, + "name": { + "description": "The region as the source names it -- aSCC, left dlPFC parcel, salience network. Its own words: normalization to an anatomical vocabulary happens downstream and needs the original to map from.", + "type": "string" }, "definition_method": { "anyOf": [ @@ -3849,21 +3850,6 @@ ], "description": "How the region was delimited, which is a statement about independence: one drawn from the study's own prior contrast is not independent of an effect tested inside it. Per region, since one analysis may use an atlas mask and a functional ROI at once. Required, so a source that does not say still carries the slot, as `extraction_status: not_reported`. Open vocabulary: write the source's own wording in the field when nothing here fits, because the value is required and a closed range discards the whole region rather than the one unrecognised word." }, - "description": { - "description": "How the source defines the region, including any geometry it states: \"8 mm spheres centered at [0 36 -6]\". Definitional coordinates have no structured slot and belong here, where the evidence span makes them checkable; result coordinates reach the record through the table parser instead.", - "type": [ - "string", - "null" - ] - }, - "id": { - "description": "Stable identifier.", - "type": "string" - }, - "name": { - "description": "The region as the source names it -- aSCC, left dlPFC parcel, salience network. Its own words: normalization to an anatomical vocabulary happens downstream and needs the original to map from.", - "type": "string" - }, "region_type": { "anyOf": [ { @@ -3877,6 +3863,20 @@ } ], "description": "What kind of thing the region is, which `definition_method` does not settle: an atlas parcel and an anatomical structure can both come from an atlas, and a network is neither." + }, + "atlas": { + "description": "The atlas, parcellation or template it came from, as named -- AAL, WFU_Pickatlas, Schaefer 400 -- with version or parcel index when given. `definition_method: atlas` records that one was used, not which, which is not enough to pool two studies.", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "How the source defines the region, including any geometry it states: \"8 mm spheres centered at [0 36 -6]\". Definitional coordinates have no structured slot and belong here, where the evidence span makes them checkable; result coordinates reach the record through the table parser instead.", + "type": [ + "string", + "null" + ] } }, "required": [ @@ -3981,28 +3981,11 @@ ], "title": "SecondBlock", "type": "string" - }, - "SimilarityDetails": { - "additionalProperties": false, - "description": "Payload for a representational-similarity analysis. The direction of a model-brain association is a continuous ModelTerm on the analysis's ModelEstimation, with the model RDM as the term, tested by the Effect through a `Cell`. Before choosing this family, check that the association is at the RDM level: a similarity regressor entered into a univariate GLM is a parametric modulation, not RSA.", - "properties": { - "competing_models": { - "description": "Other models the reported model was compared against.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "details_type": { - "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", - "enum": [ - "SimilarityDetails" - ], - "type": "string" - }, + }, + "SimilarityDetails": { + "additionalProperties": false, + "description": "Payload for a representational-similarity analysis. The direction of a model-brain association is a continuous ModelTerm on the analysis's ModelEstimation, with the model RDM as the term, tested by the Effect through a `Cell`. Before choosing this family, check that the association is at the RDM level: a similarity regressor entered into a univariate GLM is a parametric modulation, not RSA.", + "properties": { "model": { "description": "The model RDM tested. Unset for a genuinely model-free test, such as within- versus between-category pattern similarity, or dissimilarity against zero -- a single signed cell rather than a comparison.", "type": [ @@ -4023,6 +4006,23 @@ "string", "null" ] + }, + "competing_models": { + "description": "Other models the reported model was compared against.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "details_type": { + "description": "The concrete details subclass carried by this payload. Populated from the class itself, not extracted.", + "enum": [ + "SimilarityDetails" + ], + "type": "string" } }, "required": [ @@ -4069,32 +4069,32 @@ "additionalProperties": false, "description": "A test statistic and its degrees of freedom. Statistic values -- peak magnitudes, p-values, cluster extents -- belong to the reported coordinates.", "properties": { - "degrees_of_freedom_denominator": { - "description": "Denominator degrees of freedom, used alone for t, correlation, and chi_square. Both are unset for z. Float, so corrected degrees of freedom such as a Greenhouse-Geisser adjustment can be recorded as reported.", + "family": { + "anyOf": [ + { + "$ref": "#/$defs/StatisticFamily" + }, + { + "type": "string" + } + ], + "description": "The statistic used, independent of the thresholding procedure. A t-test corrected by permutation is still a `t`." + }, + "degrees_of_freedom_numerator": { + "description": "Numerator degrees of freedom, normally populated only for f and likelihood_ratio. A single-degree-of-freedom statistic leaves this unset, since t(151) is F(1,151).", "minimum": 0, "type": [ "number", "null" ] }, - "degrees_of_freedom_numerator": { - "description": "Numerator degrees of freedom, normally populated only for f and likelihood_ratio. A single-degree-of-freedom statistic leaves this unset, since t(151) is F(1,151).", + "degrees_of_freedom_denominator": { + "description": "Denominator degrees of freedom, used alone for t, correlation, and chi_square. Both are unset for z. Float, so corrected degrees of freedom such as a Greenhouse-Geisser adjustment can be recorded as reported.", "minimum": 0, "type": [ "number", "null" ] - }, - "family": { - "anyOf": [ - { - "$ref": "#/$defs/StatisticFamily" - }, - { - "type": "string" - } - ], - "description": "The statistic used, independent of the thresholding procedure. A t-test corrected by permutation is still a `t`." } }, "required": [ @@ -4121,24 +4121,29 @@ "additionalProperties": false, "description": "A machine-readable statistical image or derivative made available for one analysis, whether hosted with the study, in a repository, or by a data archive.", "properties": { - "coordinate_space": { - "description": "The space this image is in. Recorded per map because a study can share maps in a different space from the one its tables report.", + "id": { + "description": "Stable identifier for this map record, assigned by the pipeline.", + "type": "string" + }, + "map_type": { + "description": "Content type, such as statistic, effect estimate, variance, z-score, t-score, or thresholded label map.", + "type": "string" + }, + "url": { + "description": "Resolvable location of the map or its archive landing page.", + "format": "uri", "type": [ "string", "null" ] }, - "description": { - "description": "Notes about map provenance, processing, or relation to the reported result.", + "coordinate_space": { + "description": "The space this image is in. Recorded per map because a study can share maps in a different space from the one its tables report.", "type": [ "string", "null" ] }, - "id": { - "description": "Stable identifier for this map record, assigned by the pipeline.", - "type": "string" - }, "is_thresholded": { "description": "Whether the available map has already been thresholded.", "type": [ @@ -4146,10 +4151,6 @@ "null" ] }, - "map_type": { - "description": "Content type, such as statistic, effect estimate, variance, z-score, t-score, or thresholded label map.", - "type": "string" - }, "statistic_type": { "description": "Statistical quantity encoded by the map, when applicable.", "type": [ @@ -4157,9 +4158,8 @@ "null" ] }, - "url": { - "description": "Resolvable location of the map or its archive landing page.", - "format": "uri", + "description": { + "description": "Notes about map provenance, processing, or relation to the reported result.", "type": [ "string", "null" @@ -4191,65 +4191,24 @@ "additionalProperties": false, "description": "The publication-level record that contains the groups, tasks, design, acquisitions, preprocessing pipelines, model estimations, analyses, and reported result tables belonging to one study. Conditions belong to the task they are part of, not to the study.", "properties": { - "acquisitions": { - "description": "Image or signal acquisition protocols used in the study.", - "items": { - "anyOf": [ - { - "$ref": "#/$defs/MRI" - }, - { - "$ref": "#/$defs/EEG" - }, - { - "$ref": "#/$defs/FNIRS" - }, - { - "$ref": "#/$defs/PET" - }, - { - "$ref": "#/$defs/OtherModality" - } - ] - }, - "type": [ - "array", - "null" - ] - }, - "analyses": { - "description": "Distinct analyses or inferential tests reported by the study, including those that found nothing (`Analysis.outcome: no_significant_effect`) and those reported without coordinates. Whether an empty list means the paper reports none is `result_reporting`'s to say, not the list's.", - "items": { - "$ref": "#/$defs/Analysis" - }, - "type": [ - "array", - "null" - ] - }, - "assessments": { - "description": "Measures of participants that were taken independently of an acquisition.", - "items": { - "$ref": "#/$defs/Assessment" - }, - "type": [ - "array", - "null" - ] + "id": { + "description": "Stable identifier for the study record, such as a DOI-based or repository-local identifier.", + "type": "string" }, "bibliography": { "$ref": "#/$defs/Bibliography", "description": "The paper's bibliographic facts, the parsed paper's `Bibliography` unchanged: title, authors, journal, year, abstract, PubMed types, the paper's licence." }, - "coordinate_sets": { - "description": "Every group of coordinates the paper reports, in its tables or its text, with what each is for.", - "items": { - "$ref": "#/$defs/CoordinateSet" - }, - "type": [ - "array", - "null" - ] + "identifiers": { + "anyOf": [ + { + "$ref": "#/$defs/ArticleIdentifiers" + }, + { + "type": "null" + } + ], + "description": "Every identifier known for the paper, the parsed paper's header identifiers unchanged." }, "description": { "description": "Brief free-text summary of the study aims and scope.", @@ -4258,6 +4217,16 @@ "null" ] }, + "hypothesis": { + "description": "The study's own statement of what it expected to find, quoted from the paper -- usually the closing paragraph of the Introduction, in the form \"we hypothesized that\" or \"we predicted that\". Verbatim but for citation markers, one entry per prediction, so a study stating several gets several.\nA prediction, not a purpose. \"The aim of this study was to examine whether connectivity differs between patients and controls\" says what was tested and takes no value here; \"we expected connectivity to be lower in patients\" says what was expected and does. Recording an aim as a hypothesis asserts a direction the study never committed to, which is precisely what a reader consults this field to check.\nAbsent when the paper states no prediction, which is common: never paraphrase an aim, an abstract sentence, or a finding into one. Where the only statement is in the Discussion -- \"consistent with our hypothesis that connectivity would be lower\" -- quote that clause, which is still the study's account of what it predicted rather than `Analysis.interpretations`, the source's account of what it found.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "design": { "anyOf": [ { @@ -4269,75 +4238,86 @@ ], "description": "The study's design outside its tasks: how participants were assigned, the arms they were assigned to, and the occasions data were collected on. The narrative design description is this record's own `description`." }, - "devices": { - "description": "The scanners, amplifiers, or recording systems the study collected data on. Declared here once and referenced by identifier.", + "groups": { + "description": "Participant or demographic groups.", "items": { - "$ref": "#/$defs/Device" + "$ref": "#/$defs/Group" }, "type": [ "array", "null" ] }, - "external_datasets": { - "description": "Named external datasets used, cited, or made available by the study.", + "tasks": { + "description": "Task, resting-state, stimulation, passive-viewing, or other meaningful participant paradigms associated with study acquisitions. Each holds the conditions it was made of.", "items": { - "$ref": "#/$defs/ExternalDataset" + "$ref": "#/$defs/Task" }, "type": [ "array", "null" ] }, - "groups": { - "description": "Participant or demographic groups.", + "assessments": { + "description": "Measures of participants that were taken independently of an acquisition.", "items": { - "$ref": "#/$defs/Group" + "$ref": "#/$defs/Assessment" }, "type": [ "array", "null" ] }, - "hypothesis": { - "description": "The study's own statement of what it expected to find, quoted from the paper -- usually the closing paragraph of the Introduction, in the form \"we hypothesized that\" or \"we predicted that\". Verbatim but for citation markers, one entry per prediction, so a study stating several gets several.\nA prediction, not a purpose. \"The aim of this study was to examine whether connectivity differs between patients and controls\" says what was tested and takes no value here; \"we expected connectivity to be lower in patients\" says what was expected and does. Recording an aim as a hypothesis asserts a direction the study never committed to, which is precisely what a reader consults this field to check.\nAbsent when the paper states no prediction, which is common: never paraphrase an aim, an abstract sentence, or a finding into one. Where the only statement is in the Discussion -- \"consistent with our hypothesis that connectivity would be lower\" -- quote that clause, which is still the study's account of what it predicted rather than `Analysis.interpretations`, the source's account of what it found.", + "regions": { + "description": "Brain regions the study delimited and then used -- as connectivity seeds or targets, as the search space an analysis was restricted to, as the levels of a factor comparing regions, or as the signal a model term carries. Declared here once and referenced by identifier wherever used.", "items": { - "type": "string" + "$ref": "#/$defs/Region" }, "type": [ "array", "null" ] }, - "id": { - "description": "Stable identifier for the study record, such as a DOI-based or repository-local identifier.", - "type": "string" - }, - "identifiers": { - "anyOf": [ - { - "$ref": "#/$defs/ArticleIdentifiers" - }, - { - "type": "null" - } - ], - "description": "Every identifier known for the paper, the parsed paper's header identifiers unchanged." + "devices": { + "description": "The scanners, amplifiers, or recording systems the study collected data on. Declared here once and referenced by identifier.", + "items": { + "$ref": "#/$defs/Device" + }, + "type": [ + "array", + "null" + ] }, - "inference_settings": { - "description": "Thresholding and correction schemes used by one or more analyses. Declared here once and referenced by identifier.", + "acquisitions": { + "description": "Image or signal acquisition protocols used in the study.", "items": { - "$ref": "#/$defs/InferenceSettings" + "anyOf": [ + { + "$ref": "#/$defs/MRI" + }, + { + "$ref": "#/$defs/EEG" + }, + { + "$ref": "#/$defs/FNIRS" + }, + { + "$ref": "#/$defs/PET" + }, + { + "$ref": "#/$defs/OtherModality" + } + ] }, "type": [ "array", "null" ] }, - "measures": { - "description": "The scientific quantities the study measured, used by one or more analyses. Declared here once and referenced by identifier.", + "preprocessings": { + "description": "Preprocessing pipelines applied before statistical analysis.", "items": { - "$ref": "#/$defs/Measure" + "$ref": "#/$defs/Preprocessing" }, "type": [ "array", @@ -4354,31 +4334,30 @@ "null" ] }, - "outcome_summary": { - "anyOf": [ - { - "$ref": "#/$defs/OutcomeSummary" - }, - { - "type": "null" - } - ], - "description": "What the study's analyses found, taken together: derived from `result_reporting` and the `Analysis.outcome` of every result analysis, so a study whose every analysis is null can be found and described as such rather than vanishing with its missing coordinates." - }, - "preprocessings": { - "description": "Preprocessing pipelines applied before statistical analysis.", + "inference_settings": { + "description": "Thresholding and correction schemes used by one or more analyses. Declared here once and referenced by identifier.", + "items": { + "$ref": "#/$defs/InferenceSettings" + }, + "type": [ + "array", + "null" + ] + }, + "measures": { + "description": "The scientific quantities the study measured, used by one or more analyses. Declared here once and referenced by identifier.", "items": { - "$ref": "#/$defs/Preprocessing" + "$ref": "#/$defs/Measure" }, "type": [ "array", "null" ] }, - "regions": { - "description": "Brain regions the study delimited and then used -- as connectivity seeds or targets, as the search space an analysis was restricted to, as the levels of a factor comparing regions, or as the signal a model term carries. Declared here once and referenced by identifier wherever used.", + "analyses": { + "description": "Distinct analyses or inferential tests reported by the study, including those that found nothing (`Analysis.outcome: no_significant_effect`) and those reported without coordinates. Whether an empty list means the paper reports none is `result_reporting`'s to say, not the list's.", "items": { - "$ref": "#/$defs/Region" + "$ref": "#/$defs/Analysis" }, "type": [ "array", @@ -4396,6 +4375,17 @@ ], "description": "Whether the paper reports inferential analyses of its own. An empty `analyses` list is otherwise ambiguous: a methods or data paper that tests nothing and a paper the extractor read badly look the same. With this set, `no_analyses_reported` asserts the emptiness, with the sentences that show it as evidence.\nSays nothing about whether the analyses found anything; that is each `Analysis.outcome`, summarised in `outcome_summary`." }, + "outcome_summary": { + "anyOf": [ + { + "$ref": "#/$defs/OutcomeSummary" + }, + { + "type": "null" + } + ], + "description": "What the study's analyses found, taken together: derived from `result_reporting` and the `Analysis.outcome` of every result analysis, so a study whose every analysis is null can be found and described as such rather than vanishing with its missing coordinates." + }, "tables": { "description": "Publication tables from which results or analysis metadata were extracted.", "items": { @@ -4406,10 +4396,20 @@ "null" ] }, - "tasks": { - "description": "Task, resting-state, stimulation, passive-viewing, or other meaningful participant paradigms associated with study acquisitions. Each holds the conditions it was made of.", + "coordinate_sets": { + "description": "Every group of coordinates the paper reports, in its tables or its text, with what each is for.", "items": { - "$ref": "#/$defs/Task" + "$ref": "#/$defs/CoordinateSet" + }, + "type": [ + "array", + "null" + ] + }, + "external_datasets": { + "description": "Named external datasets used, cited, or made available by the study.", + "items": { + "$ref": "#/$defs/ExternalDataset" }, "type": [ "array", @@ -4428,6 +4428,13 @@ "additionalProperties": false, "description": "The design of the study; how participants were assigned, what they were assigned to, and the occasions on which data were collected. The arms and timepoints it holds are referenced by identifier from Condition and Group.", "properties": { + "description": { + "description": "Overall study design in narrative form, including observational or interventional status, cross-sectional or longitudinal structure, allocation, and blinding when applicable.", + "type": [ + "string", + "null" + ] + }, "allocation": { "anyOf": [ { @@ -4442,16 +4449,6 @@ ], "description": "How participants came to be in the arm they were in.\nIts own vocabulary, and not its neighbour's. A study with no arms takes `not_applicable` here -- \"nothing was administered, so allocation does not apply\" -- and says `observational_cohorts` in `assignment_structure`. Writing `observational_cohorts` into this slot was measured on 4 of 5 freshly extracted observational papers: the value is accepted, because the vocabulary is open, and it answers a different question than the one asked. This slot is the only record of whether anything was administered at all." }, - "arms": { - "description": "The arms of the study, active and comparator alike. Every reference to an Arm elsewhere is by identifier, so this list holds the records themselves.", - "items": { - "$ref": "#/$defs/Arm" - }, - "type": [ - "array", - "null" - ] - }, "assignment_structure": { "anyOf": [ { @@ -4480,10 +4477,13 @@ ], "description": "Who was kept unaware of the assignment." }, - "description": { - "description": "Overall study design in narrative form, including observational or interventional status, cross-sectional or longitudinal structure, allocation, and blinding when applicable.", + "arms": { + "description": "The arms of the study, active and comparator alike. Every reference to an Arm elsewhere is by identifier, so this list holds the records themselves.", + "items": { + "$ref": "#/$defs/Arm" + }, "type": [ - "string", + "array", "null" ] }, @@ -4528,6 +4528,24 @@ "additionalProperties": false, "description": "A publication table that supplies coordinates, statistics, or contextual metadata. Raw cell structure and point-level provenance may remain in the repository's coordinate-table representation and be linked with source_path.", "properties": { + "id": { + "description": "Stable table identifier used by Analysis foreign-key references.", + "type": "string" + }, + "table_number": { + "description": "Printed identifier such as Table 2 or Supplementary Table S4.", + "type": [ + "string", + "null" + ] + }, + "title": { + "description": "Table title, when distinct from the caption.", + "type": [ + "string", + "null" + ] + }, "caption": { "description": "Table caption describing the table's contents.", "type": [ @@ -4535,21 +4553,17 @@ "null" ] }, - "column_headings": { - "description": "Ordered leaf column headings retained to aid interpretation of extracted values.", - "items": { - "type": "string" - }, + "footer": { + "description": "Table notes, abbreviations, threshold statements, and footnotes.", "type": [ - "array", + "string", "null" ] }, - "coordinate_count": { - "description": "Number of reported coordinate rows or points in the normalized table representation.", - "minimum": 0, + "source_path": { + "description": "Repository path or external location of the raw or normalized table representation. Set by the ingestion pipeline.", "type": [ - "integer", + "string", "null" ] }, @@ -4560,6 +4574,16 @@ "null" ] }, + "column_headings": { + "description": "Ordered leaf column headings retained to aid interpretation of extracted values.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "description": { "description": "Summary of the table contents and its role in the reported analyses.", "type": [ @@ -4567,17 +4591,14 @@ "null" ] }, - "footer": { - "description": "Table notes, abbreviations, threshold statements, and footnotes.", + "coordinate_count": { + "description": "Number of reported coordinate rows or points in the normalized table representation.", + "minimum": 0, "type": [ - "string", + "integer", "null" ] }, - "id": { - "description": "Stable table identifier used by Analysis foreign-key references.", - "type": "string" - }, "purpose": { "anyOf": [ { @@ -4591,27 +4612,6 @@ } ], "description": "What this table is for. `reported_effect` when its rows are the foci of a tested effect, which is the common case and which is derived from the analyses that name the table; otherwise what the rows are instead -- region or ROI definitions, atlas parcels, connectivity seeds, the peaks of a decomposition's components, coordinates cited from other papers, a stimulus list, demographics, descriptive statistics with no test.\nA coordinate table is not an analysis merely by having coordinates in it, and this is the only field that says which it is. Absence means nothing decided: a table deliberately encoded as a non-analysis and one no pass considered are otherwise the same silence, and the coordinates of the first belong on the entity they locate (a Region's description) rather than on a contrast that never produced them.\nA per-table answer, so it cannot be right for a table whose row groups serve different purposes -- results beside the ROIs they were tested in. Each row group states its own role in a `CoordinateSet`." - }, - "source_path": { - "description": "Repository path or external location of the raw or normalized table representation. Set by the ingestion pipeline.", - "type": [ - "string", - "null" - ] - }, - "table_number": { - "description": "Printed identifier such as Table 2 or Supplementary Table S4.", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "Table title, when distinct from the caption.", - "type": [ - "string", - "null" - ] } }, "required": [ @@ -4640,58 +4640,9 @@ "additionalProperties": false, "description": "A meaningful participant paradigm, such as a task, resting-state protocol, stimulation, or passive viewing, performed in association with an acquisition. A Task record is required wherever the study models conditions, a resting-state protocol included. Acquisitions with no paradigm do not require one, even in a longitudinal or interventional study: a structural comparison across occasions is carried by the Effect's timepoint terms and linked through Analysis.acquisitions.", "properties": { - "acquisitions": { - "description": "The acquisition protocols used to collect data for this task. Exclude structural scans unless they are part of the task itself.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ], - "x-reference": "Acquisition" - }, - "conditions": { - "description": "The conditions this task was made of. These are the different states/modes of the task.", - "items": { - "$ref": "#/$defs/Condition" - }, - "type": [ - "array", - "null" - ] - }, - "description": { - "description": "Complete narrative description of what participants experienced and did.", - "type": [ - "string", - "null" - ] - }, - "design_type": { - "anyOf": [ - { - "$ref": "#/$defs/DesignType" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "How the task's events were modelled in time. Open vocabulary: write the source's own wording in the field when nothing here fits." - }, "id": { "type": "string" }, - "instructions": { - "description": "Task- or paradigm-specific instructions given to participants.", - "type": [ - "string", - "null" - ] - }, "name": { "description": "Short, unique label for the task or paradigm; corresponds conceptually to BIDS TaskName when applicable.", "type": [ @@ -4713,8 +4664,8 @@ "null" ] }, - "performance_measures": { - "description": "Behavioral variables recorded and summaries used to verify task performance.", + "reference_ids": { + "description": "`references` resolved to the works they cite, as DOIs or PMIDs, through the parsed paper's reference list. What links a task to the paper that introduced it and to the adaptations that cite that paper, so a task's lineage can be followed.", "items": { "type": "string" }, @@ -4723,44 +4674,76 @@ "null" ] }, - "presentation_software": { - "description": "Stimulus-presentation software name, version, and operating system when reported.", + "variant_qualifiers": { + "description": "The words of `name` that `paradigm` leaves out, each with the axis it varies the paradigm on. Each is also stated in its own field: a stimulus-content qualifier in `stimulus_content`, a modality qualifier in `stimulus_modality` or `response_modality`, a timing qualifier in `design_type`. Empty when `paradigm` equals `name`.", + "items": { + "$ref": "#/$defs/TaskQualifier" + }, + "type": [ + "array", + "null" + ] + }, + "description": { + "description": "Complete narrative description of what participants experienced and did.", "type": [ "string", "null" ] }, - "reference_ids": { - "description": "`references` resolved to the works they cite, as DOIs or PMIDs, through the parsed paper's reference list. What links a task to the paper that introduced it and to the adaptations that cite that paper, so a task's lineage can be followed.", + "conditions": { + "description": "The conditions this task was made of. These are the different states/modes of the task.", "items": { - "type": "string" + "$ref": "#/$defs/Condition" }, "type": [ "array", "null" ] }, - "references": { - "description": "Works the source cites for this task, exactly as it writes them: an author-year citation or a bracketed reference number, kept in the source's own punctuation. Copy the in-text citation; do not resolve, expand or look it up.\nMultivalued -- a task may cite the paradigm's origin, an adaptation, and the stimulus set separately. Empty when the source cites nothing for the task.", + "design_type": { + "anyOf": [ + { + "$ref": "#/$defs/DesignType" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "How the task's events were modelled in time. Open vocabulary: write the source's own wording in the field when nothing here fits." + }, + "acquisitions": { + "description": "The acquisition protocols used to collect data for this task. Exclude structural scans unless they are part of the task itself.", "items": { "type": "string" }, "type": [ "array", "null" + ], + "x-reference": "Acquisition" + }, + "presentation_software": { + "description": "Stimulus-presentation software name, version, and operating system when reported.", + "type": [ + "string", + "null" ] }, - "response_modality": { - "description": "How participants responded. Multivalued because a paradigm often collects more than one -- a button press for accuracy and a spoken report afterwards.", - "items": { - "anyOf": [ - { - "$ref": "#/$defs/ResponseModality" - }, - { - "type": "string" - } - ] + "instructions": { + "description": "Task- or paradigm-specific instructions given to participants.", + "type": [ + "string", + "null" + ] + }, + "references": { + "description": "Works the source cites for this task, exactly as it writes them: an author-year citation or a bracketed reference number, kept in the source's own punctuation. Copy the in-text citation; do not resolve, expand or look it up.\nMultivalued -- a task may cite the paradigm's origin, an adaptation, and the stimulus set separately. Empty when the source cites nothing for the task.", + "items": { + "type": "string" }, "type": [ "array", @@ -4804,10 +4787,27 @@ "null" ] }, - "variant_qualifiers": { - "description": "The words of `name` that `paradigm` leaves out, each with the axis it varies the paradigm on. Each is also stated in its own field: a stimulus-content qualifier in `stimulus_content`, a modality qualifier in `stimulus_modality` or `response_modality`, a timing qualifier in `design_type`. Empty when `paradigm` equals `name`.", + "response_modality": { + "description": "How participants responded. Multivalued because a paradigm often collects more than one -- a button press for accuracy and a spoken report afterwards.", "items": { - "$ref": "#/$defs/TaskQualifier" + "anyOf": [ + { + "$ref": "#/$defs/ResponseModality" + }, + { + "type": "string" + } + ] + }, + "type": [ + "array", + "null" + ] + }, + "performance_measures": { + "description": "Behavioral variables recorded and summaries used to verify task performance.", + "items": { + "type": "string" }, "type": [ "array", @@ -4825,6 +4825,10 @@ "additionalProperties": false, "description": "One word or phrase of a task's name that varies its paradigm rather than naming it: the `emotional` of an emotional Stroop task, the `auditory` of an auditory flanker.", "properties": { + "qualifier": { + "description": "The qualifier as the name prints it.", + "type": "string" + }, "axis": { "anyOf": [ { @@ -4835,10 +4839,6 @@ } ], "description": "What the qualifier varies." - }, - "qualifier": { - "description": "The qualifier as the name prints it.", - "type": "string" } }, "required": [ @@ -4880,14 +4880,6 @@ "description": "Short occasion label as the source states it, for example baseline, post-treatment, six-month follow-up.", "type": "string" }, - "order": { - "description": "Position of this occasion in the study's sequence, counting from 1. Carries the ordering that `relation_to_intervention` cannot when several occasions share a relation -- the earliest `post_intervention` occasion is the study's endpoint, later ones are follow-ups.", - "minimum": 1, - "type": [ - "integer", - "null" - ] - }, "relation_to_intervention": { "anyOf": [ { @@ -4899,6 +4891,14 @@ ], "description": "Where this occasion sits relative to the intervention. Required, so an occasion the source does not place still carries the slot, as `extraction_status: not_reported`." }, + "order": { + "description": "Position of this occasion in the study's sequence, counting from 1. Carries the ordering that `relation_to_intervention` cannot when several occasions share a relation -- the earliest `post_intervention` occasion is the study's endpoint, later ones are follow-ups.", + "minimum": 1, + "type": [ + "integer", + "null" + ] + }, "time_from_intervention": { "description": "Elapsed time between the intervention and this occasion, in the source's own words and units, for example 24 h or six weeks.", "type": [ @@ -4944,84 +4944,93 @@ "description": "The publication-level record that contains the groups, tasks, design, acquisitions, preprocessing pipelines, model estimations, analyses, and reported result tables belonging to one study. Conditions belong to the task they are part of, not to the study.", "metamodel_version": "1.12.0", "properties": { - "acquisitions": { - "description": "Image or signal acquisition protocols used in the study.", + "id": { + "description": "Stable identifier for the study record, such as a DOI-based or repository-local identifier.", + "type": "string" + }, + "bibliography": { + "$ref": "#/$defs/Bibliography", + "description": "The paper's bibliographic facts, the parsed paper's `Bibliography` unchanged: title, authors, journal, year, abstract, PubMed types, the paper's licence." + }, + "identifiers": { + "anyOf": [ + { + "$ref": "#/$defs/ArticleIdentifiers" + }, + { + "type": "null" + } + ], + "description": "Every identifier known for the paper, the parsed paper's header identifiers unchanged." + }, + "description": { + "description": "Brief free-text summary of the study aims and scope.", + "type": [ + "string", + "null" + ] + }, + "hypothesis": { + "description": "The study's own statement of what it expected to find, quoted from the paper -- usually the closing paragraph of the Introduction, in the form \"we hypothesized that\" or \"we predicted that\". Verbatim but for citation markers, one entry per prediction, so a study stating several gets several.\nA prediction, not a purpose. \"The aim of this study was to examine whether connectivity differs between patients and controls\" says what was tested and takes no value here; \"we expected connectivity to be lower in patients\" says what was expected and does. Recording an aim as a hypothesis asserts a direction the study never committed to, which is precisely what a reader consults this field to check.\nAbsent when the paper states no prediction, which is common: never paraphrase an aim, an abstract sentence, or a finding into one. Where the only statement is in the Discussion -- \"consistent with our hypothesis that connectivity would be lower\" -- quote that clause, which is still the study's account of what it predicted rather than `Analysis.interpretations`, the source's account of what it found.", "items": { - "anyOf": [ - { - "$ref": "#/$defs/MRI" - }, - { - "$ref": "#/$defs/EEG" - }, - { - "$ref": "#/$defs/FNIRS" - }, - { - "$ref": "#/$defs/PET" - }, - { - "$ref": "#/$defs/OtherModality" - } - ] + "type": "string" }, "type": [ "array", "null" ] }, - "analyses": { - "description": "Distinct analyses or inferential tests reported by the study, including those that found nothing (`Analysis.outcome: no_significant_effect`) and those reported without coordinates. Whether an empty list means the paper reports none is `result_reporting`'s to say, not the list's.", + "design": { + "anyOf": [ + { + "$ref": "#/$defs/StudyDesign" + }, + { + "type": "null" + } + ], + "description": "The study's design outside its tasks: how participants were assigned, the arms they were assigned to, and the occasions data were collected on. The narrative design description is this record's own `description`." + }, + "groups": { + "description": "Participant or demographic groups.", "items": { - "$ref": "#/$defs/Analysis" + "$ref": "#/$defs/Group" }, "type": [ "array", "null" ] }, - "assessments": { - "description": "Measures of participants that were taken independently of an acquisition.", + "tasks": { + "description": "Task, resting-state, stimulation, passive-viewing, or other meaningful participant paradigms associated with study acquisitions. Each holds the conditions it was made of.", "items": { - "$ref": "#/$defs/Assessment" + "$ref": "#/$defs/Task" }, "type": [ "array", "null" ] }, - "bibliography": { - "$ref": "#/$defs/Bibliography", - "description": "The paper's bibliographic facts, the parsed paper's `Bibliography` unchanged: title, authors, journal, year, abstract, PubMed types, the paper's licence." - }, - "coordinate_sets": { - "description": "Every group of coordinates the paper reports, in its tables or its text, with what each is for.", + "assessments": { + "description": "Measures of participants that were taken independently of an acquisition.", "items": { - "$ref": "#/$defs/CoordinateSet" + "$ref": "#/$defs/Assessment" }, "type": [ "array", "null" ] }, - "description": { - "description": "Brief free-text summary of the study aims and scope.", + "regions": { + "description": "Brain regions the study delimited and then used -- as connectivity seeds or targets, as the search space an analysis was restricted to, as the levels of a factor comparing regions, or as the signal a model term carries. Declared here once and referenced by identifier wherever used.", + "items": { + "$ref": "#/$defs/Region" + }, "type": [ - "string", + "array", "null" ] }, - "design": { - "anyOf": [ - { - "$ref": "#/$defs/StudyDesign" - }, - { - "type": "null" - } - ], - "description": "The study's design outside its tasks: how participants were assigned, the arms they were assigned to, and the occasions data were collected on. The narrative design description is this record's own `description`." - }, "devices": { "description": "The scanners, amplifiers, or recording systems the study collected data on. Declared here once and referenced by identifier.", "items": { @@ -5032,51 +5041,52 @@ "null" ] }, - "external_datasets": { - "description": "Named external datasets used, cited, or made available by the study.", + "acquisitions": { + "description": "Image or signal acquisition protocols used in the study.", "items": { - "$ref": "#/$defs/ExternalDataset" + "anyOf": [ + { + "$ref": "#/$defs/MRI" + }, + { + "$ref": "#/$defs/EEG" + }, + { + "$ref": "#/$defs/FNIRS" + }, + { + "$ref": "#/$defs/PET" + }, + { + "$ref": "#/$defs/OtherModality" + } + ] }, "type": [ "array", "null" ] }, - "groups": { - "description": "Participant or demographic groups.", + "preprocessings": { + "description": "Preprocessing pipelines applied before statistical analysis.", "items": { - "$ref": "#/$defs/Group" + "$ref": "#/$defs/Preprocessing" }, "type": [ "array", "null" ] }, - "hypothesis": { - "description": "The study's own statement of what it expected to find, quoted from the paper -- usually the closing paragraph of the Introduction, in the form \"we hypothesized that\" or \"we predicted that\". Verbatim but for citation markers, one entry per prediction, so a study stating several gets several.\nA prediction, not a purpose. \"The aim of this study was to examine whether connectivity differs between patients and controls\" says what was tested and takes no value here; \"we expected connectivity to be lower in patients\" says what was expected and does. Recording an aim as a hypothesis asserts a direction the study never committed to, which is precisely what a reader consults this field to check.\nAbsent when the paper states no prediction, which is common: never paraphrase an aim, an abstract sentence, or a finding into one. Where the only statement is in the Discussion -- \"consistent with our hypothesis that connectivity would be lower\" -- quote that clause, which is still the study's account of what it predicted rather than `Analysis.interpretations`, the source's account of what it found.", + "model_estimations": { + "description": "Model estimation records used by one or more analyses.", "items": { - "type": "string" + "$ref": "#/$defs/ModelEstimation" }, "type": [ "array", "null" ] }, - "id": { - "description": "Stable identifier for the study record, such as a DOI-based or repository-local identifier.", - "type": "string" - }, - "identifiers": { - "anyOf": [ - { - "$ref": "#/$defs/ArticleIdentifiers" - }, - { - "type": "null" - } - ], - "description": "Every identifier known for the paper, the parsed paper's header identifiers unchanged." - }, "inference_settings": { "description": "Thresholding and correction schemes used by one or more analyses. Declared here once and referenced by identifier.", "items": { @@ -5097,57 +5107,37 @@ "null" ] }, - "model_estimations": { - "description": "Model estimation records used by one or more analyses.", + "analyses": { + "description": "Distinct analyses or inferential tests reported by the study, including those that found nothing (`Analysis.outcome: no_significant_effect`) and those reported without coordinates. Whether an empty list means the paper reports none is `result_reporting`'s to say, not the list's.", "items": { - "$ref": "#/$defs/ModelEstimation" + "$ref": "#/$defs/Analysis" }, "type": [ "array", "null" ] }, - "outcome_summary": { + "result_reporting": { "anyOf": [ { - "$ref": "#/$defs/OutcomeSummary" + "$ref": "#/$defs/ResultReporting" }, { "type": "null" } ], - "description": "What the study's analyses found, taken together: derived from `result_reporting` and the `Analysis.outcome` of every result analysis, so a study whose every analysis is null can be found and described as such rather than vanishing with its missing coordinates." - }, - "preprocessings": { - "description": "Preprocessing pipelines applied before statistical analysis.", - "items": { - "$ref": "#/$defs/Preprocessing" - }, - "type": [ - "array", - "null" - ] - }, - "regions": { - "description": "Brain regions the study delimited and then used -- as connectivity seeds or targets, as the search space an analysis was restricted to, as the levels of a factor comparing regions, or as the signal a model term carries. Declared here once and referenced by identifier wherever used.", - "items": { - "$ref": "#/$defs/Region" - }, - "type": [ - "array", - "null" - ] + "description": "Whether the paper reports inferential analyses of its own. An empty `analyses` list is otherwise ambiguous: a methods or data paper that tests nothing and a paper the extractor read badly look the same. With this set, `no_analyses_reported` asserts the emptiness, with the sentences that show it as evidence.\nSays nothing about whether the analyses found anything; that is each `Analysis.outcome`, summarised in `outcome_summary`." }, - "result_reporting": { + "outcome_summary": { "anyOf": [ { - "$ref": "#/$defs/ResultReporting" + "$ref": "#/$defs/OutcomeSummary" }, { "type": "null" } ], - "description": "Whether the paper reports inferential analyses of its own. An empty `analyses` list is otherwise ambiguous: a methods or data paper that tests nothing and a paper the extractor read badly look the same. With this set, `no_analyses_reported` asserts the emptiness, with the sentences that show it as evidence.\nSays nothing about whether the analyses found anything; that is each `Analysis.outcome`, summarised in `outcome_summary`." + "description": "What the study's analyses found, taken together: derived from `result_reporting` and the `Analysis.outcome` of every result analysis, so a study whose every analysis is null can be found and described as such rather than vanishing with its missing coordinates." }, "tables": { "description": "Publication tables from which results or analysis metadata were extracted.", @@ -5159,10 +5149,20 @@ "null" ] }, - "tasks": { - "description": "Task, resting-state, stimulation, passive-viewing, or other meaningful participant paradigms associated with study acquisitions. Each holds the conditions it was made of.", + "coordinate_sets": { + "description": "Every group of coordinates the paper reports, in its tables or its text, with what each is for.", "items": { - "$ref": "#/$defs/Task" + "$ref": "#/$defs/CoordinateSet" + }, + "type": [ + "array", + "null" + ] + }, + "external_datasets": { + "description": "Named external datasets used, cited, or made available by the study.", + "items": { + "$ref": "#/$defs/ExternalDataset" }, "type": [ "array", diff --git a/tests/test_declared_order.py b/tests/test_declared_order.py new file mode 100644 index 0000000..6f695ed --- /dev/null +++ b/tests/test_declared_order.py @@ -0,0 +1,40 @@ +"""JSON Schema properties follow the order the LinkML declares its slots.""" + +import json + +import pytest + +pytest.importorskip("linkml", reason="regeneration needs the `generate` extra") + + +def _documents(generator): + rendered = generator.render() + for contract in generator.CONTRACTS: + view = generator._view(contract) + for name in contract.roots: + path = generator.PACKAGE / "jsonschema" / f"{name}.schema.json" + yield name, view, json.loads(rendered[path]) + + +def test_group_properties_follow_declared_order(generator): + documents = {name: (view, doc) for name, view, doc in _documents(generator)} + view, document = documents["extraction-record"] + declared = [slot.name for slot in view.class_induced_slots("Group")] + assert list(document["$defs"]["Group"]["properties"]) == declared + + +def test_every_class_follows_declared_order(generator): + checked = 0 + for name, view, document in _documents(generator): + for cls, definition in generator._class_definitions(document, view): + properties = definition.get("properties") + if not properties: + continue + declared = [slot.name for slot in view.class_induced_slots(cls)] + assert list(properties) == declared, f"{name}: {cls}" + checked += 1 + assert checked + + +def test_generation_is_deterministic(generator): + assert generator.render() == generator.render() diff --git a/tools/generate_models.py b/tools/generate_models.py index ce30a8f..0941732 100644 --- a/tools/generate_models.py +++ b/tools/generate_models.py @@ -114,6 +114,19 @@ def _annotate_unique_keys(document: dict, view) -> None: definition["x-unique-keys"] = list(keys.values()) +def _sort_keys(node, declared_order: bool = False): + """Sort every object's keys, except a `properties` object, which keeps the generator's order.""" + if isinstance(node, list): + return [_sort_keys(item) for item in node] + if not isinstance(node, dict): + return node + keys = list(node) if declared_order else sorted(node) + return { + key: _sort_keys(node[key], declared_order=key == "properties" and not declared_order) + for key in keys + } + + def render() -> dict[Path, str]: """Every generated file, path -> contents.""" from linkml.generators.jsonschemagen import JsonSchemaGenerator @@ -128,12 +141,12 @@ def render() -> dict[Path, str]: out[PACKAGE / "models" / f"{contract.module}.py"] = _header(contract) + models for name, top in contract.roots.items(): document = json.loads( - JsonSchemaGenerator(view.schema, top_class=top).serialize() + JsonSchemaGenerator(view.schema, top_class=top).generate().to_json() ) _annotate_references(document, view) _annotate_unique_keys(document, view) out[PACKAGE / "jsonschema" / f"{name}.schema.json"] = ( - json.dumps(document, indent=2, sort_keys=True, ensure_ascii=False) + "\n" + json.dumps(_sort_keys(document), indent=2, ensure_ascii=False) + "\n" ) return out