Skip to content

Commit cbe252d

Browse files
authored
Merge pull request #45 from cdisc-org/sdtm-define-xml
Added Readme and Requirements files
2 parents 8aaa836 + 0769714 commit cbe252d

3 files changed

Lines changed: 169 additions & 19 deletions

File tree

src/define-xml/README.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# USDM to Define-JSON Processor
2+
3+
This script transforms a [USDM](https://www.cdisc.org/standards/foundational/usdm) (Unified Study Data Model) JSON file into a Define-JSON structure.
4+
5+
---
6+
7+
## Prerequisites
8+
9+
### Python version
10+
11+
Python 3.9 or later is recommended.
12+
13+
### Required packages
14+
15+
Install dependencies with:
16+
17+
```bash
18+
pip install jsonata cdisc-library-client python-dotenv pyyaml openpyxl
19+
```
20+
21+
| Package | Purpose |
22+
|---|---|
23+
| `jsonata` | JSONata expression evaluation for USDM navigation |
24+
| `cdisc-library-client` | CDISC Library REST API client |
25+
| `python-dotenv` | Load API key from a `.env` file |
26+
| `pyyaml` | Parse the Define-JSON YAML schema for validation |
27+
| `openpyxl` | Write the Excel validation report |
28+
29+
### CDISC Library API key
30+
31+
The script requires a valid CDISC Library API key. Provide it in one of two ways:
32+
33+
1. **Environment variable** – set `CDISC_API_KEY` in your shell or system environment.
34+
2. **`.env` file** – create a `.env` file in the working directory containing:
35+
```
36+
CDISC_API_KEY=your_api_key_here
37+
```
38+
3. **Command-line argument** – pass `--cdisc_api_key <key>` at runtime.
39+
40+
---
41+
42+
## Usage
43+
44+
Run the script from the `src/define-xml/` directory:
45+
46+
```
47+
python create_define_json.py --usdm_file <path> --output_template <path> --sdtmct <date> [options]
48+
```
49+
50+
### Arguments
51+
52+
| Argument | Required | Default | Description |
53+
|---|---|---|---|
54+
| `--usdm_file` | **Yes** || Path to the input USDM JSON file |
55+
| `--output_template` | **Yes** || Path for the output Define-JSON file |
56+
| `--sdtmct` | **Yes** || SDTM Controlled Terminology date (`yyyy-mm-dd`) |
57+
| `--sdtmig` | No | `3.4` | SDTM Implementation Guide version |
58+
| `--studyversion` | No | `0` | Index of the study version within the USDM file |
59+
| `--studydesign` | No | `0` | Index of the study design within the USDM file |
60+
| `--docversion` | No | `0` | Index of the document version within the USDM file |
61+
| `--cdisc_api_key` | No | env var | CDISC Library API key (overrides `CDISC_API_KEY` env var) |
62+
| `--cosmosversion` | No | `v2` | CDISC Cosmos API version |
63+
| `--validate [schema]` | No | `define.yaml` | Validate output against a YAML schema file. If omitted the default `define.yaml` is used. Requires `--validation_report` when specified. |
64+
| `--validation_report` | Conditional || Path to the Excel validation report (required when `--validate` is used) |
65+
| `--debug` | No | `false` | Save intermediate dictionaries as JSON debug files |
66+
67+
---
68+
69+
## Examples
70+
71+
### Minimal run (no validation)
72+
73+
```bash
74+
python create_define_json.py \
75+
--usdm_file ..\..\data\protocol\LZZT\usdm\pilot_LLZT_protocol.json \
76+
--output_template define.json \
77+
--sdtmct 2025-03-28
78+
```
79+
80+
### With schema validation and debug output
81+
82+
```bash
83+
python create_define_json.py \
84+
--usdm_file ..\..\data\protocol\LZZT\usdm\pilot_LLZT_protocol.json \
85+
--output_template define.json \
86+
--sdtmct 2025-03-28 \
87+
--validate \
88+
--validation_report validation_report.xlsx \
89+
--debug
90+
```
91+
92+
### Specifying a custom SDTMIG version and API key
93+
94+
```bash
95+
python create_define_json.py \
96+
--usdm_file ..\..\data\protocol\LZZT\usdm\pilot_LLZT_protocol.json \
97+
--output_template define.json \
98+
--sdtmct 2025-03-28 \
99+
--sdtmig 3.3 \
100+
--cdisc_api_key <your_api_key> \
101+
--validate \
102+
--validation_report validation_report.xlsx
103+
```
104+
105+
---
106+
107+
## Output files
108+
109+
| File | Description |
110+
|---|---|
111+
| `<output_template>` (e.g. `define.json`) | Define-JSON with itemGroups, slices, conditions, whereClauses, and codeLists |
112+
| `<validation_report>` (e.g. `validation_report.xlsx`) | Excel report listing schema validation results (pass/fail per field) |
113+
114+
### Debug files (created with `--debug`)
115+
116+
When `--debug` is enabled the following intermediate JSON files are written to the working directory:
117+
118+
| File | Contents |
119+
|---|---|
120+
| `debug_datasets_dict.json` | Variable metadata per dataset |
121+
| `debug_bc_dict.json` | Biomedical concept VLM metadata |
122+
| `debug_vlm_lookup.json` | VLM entries keyed by variable name |
123+
| `debug_vlm_items_by_variable.json` | VLM items grouped for slice creation |
124+
| `debug_test_dict.json` | TEST/TESTCD concept mappings |
125+
| `debug_code_lists_map.json` | Deduplicated codelists by short name |
126+
| `debug_condition_lookup.json` | Condition deduplication cache |
127+
| `debug_dataset_data.json` | Raw dataset data accumulated during processing |
128+
129+
---
130+
131+
## Schema validation
132+
133+
The `--validate` flag runs the output JSON against the `define.yaml` schema (or a custom schema file you provide). The validation results are written to the Excel file specified by `--validation_report`. The script exits with code `1` if validation fails, making it suitable for use in CI pipelines.
134+
135+
---
136+
137+
## Notes
138+
139+
- The script requires an active internet connection to call the CDISC Library API.
140+
- Windows terminals that default to `cp1252` encoding are handled automatically; Unicode status symbols are displayed correctly.
141+
- The `--studyversion`, `--studydesign`, and `--docversion` arguments are zero-based indices into the corresponding arrays in the USDM JSON. Most studies use the default value of `0`.

src/define-xml/create_define_json.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""
2-
USDM to Define-360i JSON Processor
2+
USDM to Define JSON Processor
33
===================================
44
55
This module processes USDM (Unified Study Data Model) JSON files and generates
6-
Define-360i JSON structures with value-level metadata organized in "slices".
6+
Define-JSON structures with value-level metadata organized in "slices".
77
88
The major difference from the standard Define-JSON format is that variable-level
99
metadata (VLM) is organized under "slices" within each itemGroup rather than as
@@ -16,26 +16,25 @@
1616
import argparse
1717
import hashlib
1818
import yaml
19-
import copy
2019
from jsonata import Jsonata
2120
from cdisc_library_client import CDISCLibraryClient
2221
from collections import defaultdict
2322
from dotenv import load_dotenv
2423
from datetime import datetime
2524

2625

27-
class USDMDefine360iProcessor:
26+
class USDMDefineJSONProcessor:
2827
"""
29-
Processes USDM JSON files and generates Define-360i JSON structure.
28+
Processes USDM JSON files and generates Define-JSON structure.
3029
31-
This class transforms USDM data into Define-360i format where VLM is
30+
This class transforms USDM data into Define-JSON format where VLM is
3231
organized as "slices" within itemGroups instead of separate DataSpecialization
3332
itemGroups.
3433
3534
Attributes:
3635
client (CDISCLibraryClient): CDISC Library API client
3736
usdm_data (dict): Loaded USDM JSON data
38-
template (dict): Output Define-360i JSON structure
37+
template (dict): Output Define-JSON structure
3938
datasets_dict (dict): Dataset variables and restricted values
4039
bc_dict (dict): Biomedical concepts with VLM metadata
4140
vlm_lookup (dict): VLM metadata keyed by variable name
@@ -60,7 +59,7 @@ def __init__(self, usdm_file, output_template, sdtmig, sdtmct,
6059
6160
Args:
6261
usdm_file (str): Path to USDM JSON file
63-
output_template (str): Path to output Define-360i JSON file
62+
output_template (str): Path to output Define-JSON file
6463
sdtmig (str): SDTM Implementation Guide version
6564
sdtmct (str): SDTM Controlled Terminology date in yyyy-mm-dd format
6665
studyversion (int): Study version index in USDM
@@ -101,7 +100,7 @@ def __init__(self, usdm_file, output_template, sdtmig, sdtmct,
101100
'creationDateTime': '',
102101
'odmVersion': '1.3.2',
103102
'fileType': 'Snapshot',
104-
'originator': 'Define-360i Processor',
103+
'originator': 'Define-JSON Processor',
105104
'context': 'Other',
106105
'defineVersion': '2.1.0',
107106
'studyOID': '',
@@ -2317,7 +2316,7 @@ def save_debug_files(self, prefix="debug"):
23172316

23182317
def save_output(self):
23192318
"""
2320-
Save the generated Define-360i JSON template to file.
2319+
Save the generated Define-JSON template to file.
23212320
23222321
Assembles all processed components (itemGroups, conditions,
23232322
whereClauses, codeLists) into the template and writes to output file.
@@ -2332,7 +2331,7 @@ def save_output(self):
23322331

23332332
def validate_against_schema(self, schema_file, excel_output=None):
23342333
"""
2335-
Validate the generated Define-360i file against a YAML schema.
2334+
Validate the generated Define-JSON file against a YAML schema.
23362335
23372336
This method loads the YAML schema file and validates the generated
23382337
JSON output against it using LinkML validation if available, or
@@ -2765,7 +2764,7 @@ def process(self):
27652764
2. Build VLM lookup tables
27662765
3. Update datasets dictionary with VLM data
27672766
4. Populate study metadata
2768-
5. Process datasets to generate Define-360i structures
2767+
5. Process datasets to generate Define-JSON structures
27692768
6. Add CDISC standards information
27702769
7. Save output to JSON file
27712770
8. Save debug files if debug mode is enabled
@@ -2899,7 +2898,7 @@ def _process_variable_codelist(self, var, dataset, restriction_codes=None):
28992898
29002899
Fetches controlled terminology from CDISC Library, applies restrictions
29012900
if specified, and creates deduplicated codelist entries. Returns single
2902-
OID string instead of list for 360i format.
2901+
OID string instead of list for format.
29032902
29042903
Args:
29052904
var (dict): Variable metadata from SDTMIG
@@ -3086,7 +3085,7 @@ def populate_study_elements(self):
30863085
version_display = self.studyversion + 1
30873086
design_display = self.studydesign + 1
30883087

3089-
self.template['fileOID'] = f"ODM.DEFINE-360i.{study_name}.Version{version_display}.Design{design_display}"
3088+
self.template['fileOID'] = f"ODM.DEFINE-JSON.{study_name}.Version{version_display}.Design{design_display}"
30903089
self.template['creationDateTime'] = time_str
30913090
self.template['studyOID'] = f"ODM.{study_name}.Version{version_display}.Design{design_display}"
30923091
self.template['studyName'] = study_name
@@ -3101,7 +3100,7 @@ def main():
31013100
"""
31023101
Main entry point for command-line execution.
31033102
3104-
Parses command-line arguments and orchestrates the USDM to Define-360i
3103+
Parses command-line arguments and orchestrates the USDM to Define-JSON
31053104
transformation process.
31063105
"""
31073106
# Reconfigure stdout to UTF-8 so Unicode symbols (✓, ✅, ❌, ⚠️) don't
@@ -3111,7 +3110,7 @@ def main():
31113110
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
31123111

31133112
parser = argparse.ArgumentParser(
3114-
description="Process USDM JSON file to generate Define-360i JSON with slices.",
3113+
description="Process USDM JSON file to generate Define-JSON with slices.",
31153114
epilog="Example: python create_define_json.py --usdm_file data/study.json --output_template output.json --sdtmct 2025-03-28 --validate define.yaml --validation_report validation_report.xlsx"
31163115
)
31173116
parser.add_argument(
@@ -3122,7 +3121,7 @@ def main():
31223121
parser.add_argument(
31233122
"--output_template",
31243123
required=True,
3125-
help="Path to output Define-360i JSON file"
3124+
help="Path to output Define-JSON file"
31263125
)
31273126
parser.add_argument(
31283127
"--sdtmct",
@@ -3185,7 +3184,7 @@ def main():
31853184
if args.validate and not args.validation_report:
31863185
parser.error("--validation_report is required when --validate is used")
31873186

3188-
processor = USDMDefine360iProcessor(
3187+
processor = USDMDefineJSONProcessor(
31893188
usdm_file=args.usdm_file,
31903189
output_template=args.output_template,
31913190
sdtmig=args.sdtmig,
@@ -3201,7 +3200,7 @@ def main():
32013200
processor.output_template = args.output_template
32023201
processor.process()
32033202

3204-
print(f"\n✅ Define-360i JSON file created successfully: {args.output_template}")
3203+
print(f"\n✅ Define-JSON file created successfully: {args.output_template}")
32053204

32063205
# Validate if requested
32073206
if args.validate:

src/define-xml/requirements.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
cdisc_library_client==0.1.6
2+
jsonata==0.2.5
3+
jsonschema==4.26.0
4+
linkml==1.10.0
5+
linkml_runtime==1.10.0
6+
openpyxl==3.1.5
7+
pandas==3.0.1
8+
python-dotenv==1.2.2
9+
PyYAML==6.0.2
10+
PyYAML==6.0.3

0 commit comments

Comments
 (0)