Mapping Files
Mapping files describe how project-specific CSV, REDCap, or CDISC-ODM
fields become Beacon v2 records. They can also augment the built-in cBioPortal
mapping or define reviewed terminology decisions for SDTM Dataset-JSON and
Dataset-XML. Built-in BFF routes can use a small optional mapping to describe
their synthesized dataset and cohort. The examples below cover the full
Mapping V2 contract and its smaller profiles.
A runnable notebook is available in Google Colab, with a local copy in the repository.
The notebook clones the latest main branch and reports the Convert-Pheno
version and Git commit used.
Files Requiredβ
A REDCap conversion normally uses:
- The data export in CSV format
- The REDCap data dictionary in CSV format
- A Convert-Pheno mapping file in YAML or JSON format
The data dictionary explains REDCap field types, labels, choices, notes, and ranges. The mapping file makes the project-specific semantic decisions.
CSV needs only the source file and mapping. REDCap-origin ODM uses the same
redcap mapping profile and dictionary as REDCap CSV. Standard or OpenClinica
ODM uses source.profile: cdisc-odm and resolves labels, data types, and coded
values from metadata embedded in the XML, without a REDCap dictionary.
cBioPortal uses source.profile: cbioportal. Its package metadata already
defines patients, samples, study identity, and case-list links, so a mapping is
optional and is used only for additional project-specific clinical columns.
Dataset-JSON and Dataset-XML use source.profile: sdtm. Their structural SDTM
mapping is built in, so the optional mapping contains only terminology rules.
Dataset-XML still requires Define-XML independently of this optional mapping.
OMOP, PXF, FHIR, openEHR, i2b2, PCORnet, and Sentinel already have structural
conversion rules in the software. Their optional mapping is a metadata overlay:
it can name synthesized datasets and cohorts, but cannot replace individual
or biosample field mappings.
Recipe: CSV To Validated BFFβ
Suppose a project gives you this semicolon-delimited clinical.csv:
participant_id;sex;diagnosis
P001;Female;ASTHMA_LOCAL
P002;Male;
The values are deliberately simple, but the important detail is realistic:
ASTHMA_LOCAL is a project label, not an ontology term. A first Mapping V2 file
can translate that label while asking Convert-Pheno to find the actual NCIT
concept:
mappingVersion: 2
source:
profile: csv
target:
model: beacon
schemaVersion: '2.0.0'
project:
id: csv-bff-recipe
version: '1.0'
defaults:
ontology: ncit
records: {}
beacon:
individuals:
id:
sourceFields: [participant_id]
primaryKey: participant_id
missingValue: NA
sex:
sourceField: sex
query: {from: value}
diseases:
rules:
- sourceField: diagnosis
when: {nonEmpty: true}
diseaseCode:
query:
from: value
aliases:
ASTHMA_LOCAL: Asthma
Now convert the CSV and ask for a terminology audit in the same run:
mkdir -p output
convert-pheno \
-icsv clinical.csv \
--separator ';' \
--mapping-file mapping.yaml \
--search exact \
--term-audit output/terminology.tsv \
-obff output/individuals.json
individuals.json is the BFF result. terminology.tsv explains how each
ontology decision was made. In this example, Female, Male, and Asthma should be
exact database matches. If a term is unresolved, correct the alias or mapping
and run the command again; do not lower the search threshold merely to obtain a
result.
Advanced: tool-assisted terminology validation
An LLM can help draft a mapping if it can query the same source metadata, terminology databases, audit report, and target validator used by the person reviewing the result. A term proposed from model memory alone is not evidence.
For example, when the LLM proposes a direct NCIT term, it can verify the pair
against the database bundle selected by share/db/manifest.json:
sqlite3 share/db/v0/ncit.db \
"SELECT 'NCIT:' || id, label FROM NCIT_table WHERE id = 'C28397' COLLATE NOCASE;"
Record which ontology release you checked. Its version and checksum are in
share/db/manifest.json.
Two checks remain separate:
- Does the code exist, and does its label match? Check the ontology database.
- Does it mean the same thing as the source value? Check the source documentation and ask a domain expert when the meaning is unclear.
If the audit says configured or direct_mapping, Convert-Pheno copied the
term from the mapping file. Verify that term separately against the selected
database and record the result.
Finally, validate the generated BFF with
bff-tools:
bff-tools validate -i output/individuals.json -nc -ne
Review every reported path. With some Beacon v2 schema releases, a disease may
produce an ageOfOnset oneOf message because the schema's object alternatives
overlap. That specific message is a known schema ambiguity, not evidence of an
incorrect ontology lookup. Retain it in the validation record, but do not use
it to dismiss any other validation issue.
Tooling can automate repeated conversion and validation runs. A person still has to resolve ambiguous clinical meanings and approve the mapping. For restricted data, use an approved environment and disclose only the source information needed for the task.
Mapping V2 At A Glanceβ
Two independent versions are declared at the top of every mapping:
| Key | Meaning |
|---|---|
mappingVersion: 2 | Version of the Convert-Pheno mapping language |
target.schemaVersion: '2.0.0' | Beacon v2 schema version targeted by the mapping |
project.version | Version assigned by your project to this mapping |
Mapping V2 is intentionally not compatible with the pre-V2 layout. Removed
constructs such as fieldTermLabels, valueTermLabels, targetFields, and
baselineFieldsToPropagate are not accepted. This prevents old configuration
from being interpreted with new semantics.
Compact REDCap mapping
mappingVersion: 2
source:
profile: redcap
target:
model: beacon
schemaVersion: '2.0.0'
project:
id: my_study
version: '1.0'
defaults:
ontology: ncit
records:
visitId:
sourceField: redcap_event_name
baseline:
strategy: firstNonNull
sourceFields: [sex, diagnosis]
beacon:
individuals:
id:
sourceFields: [record_id, redcap_event_name]
primaryKey: record_id
separator: ':'
sex:
sourceField: sex
query: { from: value }
diseases:
rules:
- sourceField: diagnosis
when: { nonEmpty: true }
diseaseCode:
query:
from: value
aliases:
UC: Ulcerative Colitis
CD: Crohn Disease
ageOfOnset: { sourceField: age_at_diagnosis }
info:
sourceFields: [record_id, redcap_event_name]
The top-level sections have distinct responsibilities:
| Section | Required for | Purpose |
|---|---|---|
source | All profiles | Declares the normalized source profile |
target | All profiles | Declares the target model and Beacon schema version |
project | All profiles | Identifies and versions the project mapping |
defaults | Tabular and sdtm profiles | Sets the default ontology for term lookup |
records | Tabular profiles | Defines visit and longitudinal baseline behavior |
beacon | Tabular or metadata-only profiles | Contains entity rules or dataset/cohort defaults |
terminology | sdtm | Contains DOMAIN.FIELD terminology rules |
Supported default ontologies are ncit, icd10, ohdsi, cdisc, omim,
and hpo. Individual term rules may select another supported ontology.
The route still determines how the input is parsed. source.profile describes
the record and metadata contract consumed by the mapping:
| Profile | Use |
|---|---|
csv | CSV input |
redcap | REDCap CSV or a REDCap-origin ODM export with an external dictionary |
cdisc-odm | Standard or vendor ODM with embedded MetaDataVersion, ItemDef, and CodeList metadata |
cbioportal | cBioPortal patient and sample clinical tables discovered from a study package |
sdtm | Dataset-JSON or Dataset-XML terminology enrichment; structural mapping remains built in |
omop, pxf, fhir, openehr, i2b2, pcornet, sentinel | Optional dataset and cohort metadata for the matching built-in BFF route |
ODM mappings use stable ItemOID values as source fields. A REDCap ODM export
can reuse its corresponding REDCap mapping. Other ODM documents need rules
tailored to their own item identifiers, but do not need a fabricated REDCap
dictionary when the required metadata is embedded.
Compact SDTM Terminology Mappingβ
For Dataset-JSON and Dataset-XML, omit records and beacon. Key each
terminology decision by the SDTM domain and variable:
mappingVersion: 2
source: { profile: sdtm }
target: { model: beacon, schemaVersion: '2.0.0' }
project: { id: my_study, version: '1.0' }
defaults: { ontology: ncit }
terminology:
AE.AESEV:
query:
from: value
aliases:
MILD_GRADE: Mild
MH.MHDECOD:
terms:
Asthma: { id: 'NCIT:C28397', label: Asthma }
The alias key is the source value; its value is the reviewed database label.
Aliases are selective: this example translates MILD_GRADE, while an unlisted
value such as SEVERE is queried unchanged. Case-only aliases are unnecessary
because lookup is case-insensitive. terms bypasses lookup for known values.
Define-XML nci:ExtCodeID identifiers take precedence over a label query and
are looked up exactly to obtain the canonical NCIT display. See
Terminology Search for the full precedence and audit fields.
Compact Dataset And Cohort Metadataβ
Built-in routes do not need field-level mapping rules. To identify the BFF dataset produced from an OMOP export, for example, provide only project and entity metadata:
mappingVersion: 2
source: { profile: omop }
target: { model: beacon, schemaVersion: '2.0.0' }
project:
id: study-123
version: '1.0'
description: Example study
beacon:
datasets:
defaults:
name: Example dataset
externalUrl: https://example.org/studies/study-123
cohorts:
defaults:
name: Main cohort
cohortType: study-defined
Use it only when requesting entity-aware BFF output:
convert-pheno \
-iomop omop-export/ \
--mapping-file study-metadata.yaml \
-obff --entities individuals datasets cohorts \
--out-dir bff-out/
project.id becomes datasets.id and, unless explicitly overridden,
datasets.name; it also supplies the default cohort ID <project.id>-cohort.
beacon.datasets.defaults and beacon.cohorts.defaults can override those
values and add the other properties accepted by the Beacon v2 entities.
The mapping is optional. Without it, source-derived metadata is used where
available, followed by the existing generic dataset-1 and cohort-1
defaults. Project metadata is not currently copied into individual or
biosample records.
Reading A Ruleβ
Each repeated section contains ordered rules. A rule keeps its source selector and Beacon target properties together:
diseases:
rules:
- sourceField: diagnosis
when: { nonEmpty: true }
diseaseCode:
query:
from: value
aliases:
UC: Ulcerative Colitis
Here, diagnosis is the source column. Empty cells are skipped. The recorded
value is used as the ontology query, after applying the optional alias.
Source selectors can make that behavior explicit:
| Key | Meaning |
|---|---|
sourceField | Source column or ODM ItemOID evaluated by the rule |
optional | Allows the field to be absent from compatible exports |
when.nonEmpty | Requires a non-empty value |
when.values | Accepts only the listed values |
when.notValues | Rejects the listed values |
Repeated ODM item groups are evaluated occurrence by occurrence. Companion
sourceField values are taken from the same occurrence; ambiguous repeated
values in scalar sections are rejected rather than flattened.
Ontology queries can come from different places:
| Form | Use |
|---|---|
query: { from: value } | Search using the recorded value |
query: { from: field } | Search using the source column name |
query: { from: fieldNote } | Search using the REDCap dictionary field note |
query: Hemoglobin Measurement | Search a fixed label |
term: { id: 'NCIT:C...', label: ... } | Use a known ontology term without a database search |
terms | Select reviewed terms by source value without a database search |
The location of from changes its meaning. Inside query, it selects the text
used for ontology lookup. A direct value mapping such as
id: { from: sourceValue } copies a scalar value and does not query an
ontology database.
Use exact terms when the identifier is already curated. Otherwise, use a
query and review terminology resolution with --term-audit.
Scalar targets do not perform terminology lookup:
| Form | Result |
|---|---|
{ from: sourceValue } | Uses the current rule value |
{ from: individualId } | Uses the generated BFF individual ID |
{ sourceField: age } | Uses another named source field |
{ literal: value } | Uses a fixed string, number, or boolean |
How Defaults Are Reusedβ
defaults has a specific scope according to where it appears:
| Location | Applies to |
|---|---|
Top-level defaults.ontology | Ontology queries that do not select another ontology |
beacon.individuals.<section>.defaults | Every rule in that repeated individual section only |
beacon.biosamples.defaults | Every rule under beacon.biosamples.rules |
beacon.datasets.defaults and beacon.cohorts.defaults | Metadata for the synthesized entity, not rule inheritance |
A collection default is not an output record. Before conversion, Convert-Pheno copies it into each sibling rule and then applies the values written on that rule. This lets each rule declare only what differs:
treatments:
defaults:
routeOfAdministration: { query: Oral Route of Administration }
doseIntervals:
quantity:
unit: { query: Milligram }
rules:
- sourceField: aspirin_status
treatmentCode: { query: aspirin }
doseIntervals:
quantity:
value: { sourceField: aspirin_dose }
- sourceField: infliximab_status
treatmentCode: { query: infliximab }
routeOfAdministration: { query: Intravenous Route of Administration }
doseIntervals:
quantity:
value: { sourceField: infliximab_dose }
Both rules inherit Milligram. The aspirin rule also inherits the oral route;
the infliximab rule replaces only that route with the intravenous route.
The merge is recursive: rule values override matching defaults, while
unmentioned defaults remain in place. Arrays are replaced rather than joined,
and setting an optional property to null removes its inherited value.
Defaults apply only to target properties and only within their section;
sourceField, optional, and when always remain visible on each rule.
Longitudinal Valuesβ
records.baseline can make a value recorded only at baseline available to
later rows for the same primaryKey:
records:
baseline:
strategy: firstNonNull
sourceFields: [sex, diagnosis]
Rows must be ordered so the first non-empty value appears before rows that need
it. Propagation affects target mapping only. The raw row preserved under
info.REDCap_columns or info.CSV_columns remains unchanged, so provenance
continues to reflect the input file. Generic ODM uses the occurrence-aware
info.CDISC_ODM block instead.
Other Beacon Entitiesβ
The individual mapping supports scalar demographic terms plus repeated
diseases, exposures, interventionsOrProcedures, measures,
phenotypicFeatures, and treatments rules. Target property names follow the
Beacon v2 camelCase schema.
beacon.biosamples contains executable mapping rules, not just metadata. A
biosample is emitted when its source condition matches and an ID is available:
beacon:
biosamples:
defaults:
id: { from: sourceValue }
individualId: { from: individualId }
biosampleStatus:
term: { id: 'NCIT:C126101', label: Not Available }
rules:
- sourceField: sample_id
when: { nonEmpty: true }
sampleOriginType: { query: Whole Blood }
collectionDate: { sourceField: collection_date }
Rules can also populate sampleOriginDetail, obtentionProcedure, notes,
measurements, and selected info fields. Request biosamples explicitly in
entity-aware BFF output.
beacon.datasets.defaults and beacon.cohorts.defaults provide metadata for
entities synthesized from the converted individuals. Full authoring mappings
and compact metadata overlays use the same entity-default contract.
Validation And Provenanceβ
Before conversion, Convert-Pheno rejects duplicate keys, validates the mapping
against share/schema/mapping-v2.json, checks mapping and Beacon versions, and
verifies the selected source profile and referenced fields.
Generated BFF retains source content under its format-specific info block by
default. Use --no-source-info to omit that copy and --term-audit FILE
to record direct terms, identifier lookups, label searches, and fallbacks.
Run And Reviewβ
Convert REDCap directly to Phenopackets:
convert-pheno -iredcap redcap.csv \
--redcap-dictionary dictionary.csv \
--mapping-file mapping.yaml \
-opxf phenopackets.json
Write individuals and mapped biosamples as separate BFF files:
convert-pheno -iredcap redcap.csv \
--redcap-dictionary dictionary.csv \
--mapping-file mapping.yaml \
-obff \
--entities individuals biosamples \
--out-dir bff-output/
For standard or OpenClinica ODM with embedded metadata, select the ODM profile and omit the REDCap dictionary:
convert-pheno -icdisc-odm study.xml \
--mapping-file odm-mapping.yaml \
-obff individuals.json
For terminology review, add --term-audit terminology.tsv. Exact search
is the default; similarity modes and audit columns are documented under
Terminology Search.
Continue with REDCap, CSV, Dataset-JSON, or Dataset-XML.