exadata-validator 0.0.1__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. exadata_validator-1.2.0/.gitignore +156 -0
  2. exadata_validator-1.2.0/AGENTS.md +47 -0
  3. {exadata_validator-0.0.1 → exadata_validator-1.2.0}/PKG-INFO +13 -16
  4. {exadata_validator-0.0.1 → exadata_validator-1.2.0}/README.md +8 -8
  5. exadata_validator-1.2.0/pyproject.toml +31 -0
  6. exadata_validator-1.2.0/tests/conftest.py +26 -0
  7. exadata_validator-1.2.0/tests/data/fail/data_model_longitudinal_v_1_0/CDEsMetadata.json +45 -0
  8. exadata_validator-1.2.0/tests/data/fail/data_model_longitudinal_v_1_0/dataset.csv +5 -0
  9. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/CDEsMetadata.json +88 -0
  10. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/column_not_present_in_cdes.csv +6 -0
  11. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/dataset_exceeds_max.csv +6 -0
  12. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/dataset_exceeds_min.csv +6 -0
  13. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/dataset_is_not_unique.csv +6 -0
  14. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/duplication_column_row_id.csv +6 -0
  15. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/invalid_enum.csv +6 -0
  16. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/invalid_type1.csv +6 -0
  17. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/missing_column_dataset.csv +6 -0
  18. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/missing_column_row_id.csv +6 -0
  19. exadata_validator-1.2.0/tests/data/fail/data_model_v_1_0/missing_column_subjectcode.csv +6 -0
  20. exadata_validator-1.2.0/tests/data/success/data_model1_v_1_0/CDEsMetadata.json +88 -0
  21. exadata_validator-1.2.0/tests/data/success/data_model1_v_1_0/dataset10.csv +6 -0
  22. exadata_validator-1.2.0/tests/data/success/data_model1_v_1_0/dataset20.csv +6 -0
  23. exadata_validator-1.2.0/tests/data/success/data_model_longitudinal_v_1_0/CDEsMetadata.json +45 -0
  24. exadata_validator-1.2.0/tests/data/success/data_model_longitudinal_v_1_0/dataset.csv +4 -0
  25. exadata_validator-1.2.0/tests/data/success/data_model_v_1_0/CDEsMetadata.json +90 -0
  26. exadata_validator-1.2.0/tests/data/success/data_model_v_1_0/dataset.csv +6 -0
  27. exadata_validator-1.2.0/tests/data/success/data_model_v_1_0/dataset1.csv +6 -0
  28. exadata_validator-1.2.0/tests/data/success/data_model_v_1_0/dataset10.csv +6 -0
  29. exadata_validator-1.2.0/tests/data/success/data_model_v_1_0/dataset123.csv +6 -0
  30. exadata_validator-1.2.0/tests/data/success/data_model_v_1_0/dataset2.csv +6 -0
  31. exadata_validator-1.2.0/tests/test_commands.py +158 -0
  32. exadata_validator-1.2.0/tests/test_dataelements.py +28 -0
  33. exadata_validator-1.2.0/tests/test_duckdb_validator.py +57 -0
  34. exadata_validator-1.2.0/uv.lock +147 -0
  35. exadata_validator-1.2.0/validator/__main__.py +4 -0
  36. {exadata_validator-0.0.1/data_validator → exadata_validator-1.2.0/validator}/commands.py +6 -6
  37. {exadata_validator-0.0.1/data_validator → exadata_validator-1.2.0/validator}/dataelements.py +2 -2
  38. {exadata_validator-0.0.1/data_validator → exadata_validator-1.2.0/validator}/duckdb_validator.py +9 -14
  39. {exadata_validator-0.0.1/data_validator → exadata_validator-1.2.0/validator}/reporting.py +0 -30
  40. {exadata_validator-0.0.1/data_validator → exadata_validator-1.2.0/validator}/usecases.py +10 -12
  41. exadata_validator-0.0.1/data_validator/__main__.py +0 -4
  42. exadata_validator-0.0.1/pyproject.toml +0 -27
  43. {exadata_validator-0.0.1/data_validator → exadata_validator-1.2.0/validator}/__init__.py +0 -0
  44. {exadata_validator-0.0.1/data_validator → exadata_validator-1.2.0/validator}/exceptions.py +0 -0
@@ -0,0 +1,156 @@
1
+ # Intelij
2
+ .idea/
3
+
4
+
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ build/
16
+ develop-eggs/
17
+ dist/
18
+ downloads/
19
+ eggs/
20
+ .eggs/
21
+ lib/
22
+ lib64/
23
+ parts/
24
+ sdist/
25
+ var/
26
+ wheels/
27
+ pip-wheel-metadata/
28
+ share/python-wheels/
29
+ *.egg-info/
30
+ .installed.cfg
31
+ *.egg
32
+ MANIFEST
33
+
34
+ # PyInstaller
35
+ # Usually these files are written by a python script from a template
36
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
37
+ *.manifest
38
+ *.spec
39
+
40
+ # Installer logs
41
+ pip-log.txt
42
+ pip-delete-this-directory.txt
43
+
44
+ # Unit test / coverage reports
45
+ htmlcov/
46
+ .tox/
47
+ .nox/
48
+ .coverage
49
+ .coverage.*
50
+ .cache
51
+ nosetests.xml
52
+ coverage.xml
53
+ *.cover
54
+ *.py,cover
55
+ .hypothesis/
56
+ .pytest_cache/
57
+
58
+ # Translations
59
+ *.mo
60
+ *.pot
61
+
62
+ # Django stuff:
63
+ *.log
64
+ local_settings.py
65
+ db.sqlite3
66
+ db.sqlite3-journal
67
+
68
+ # Flask stuff:
69
+ instance/
70
+ .webassets-cache
71
+
72
+ # Scrapy stuff:
73
+ .scrapy
74
+
75
+ # Sphinx documentation
76
+ docs/_build/
77
+
78
+ # PyBuilder
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ #Pipfile.lock
97
+
98
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
99
+ __pypackages__/
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ #temp files
132
+ *~
133
+ *.swp
134
+ *.swo
135
+ *#*
136
+
137
+ # vim stuff
138
+ .vim/
139
+ pyrightconfig.json
140
+ .vimspector.json
141
+
142
+ # macos
143
+ **/.DS_Store
144
+
145
+ # mip deployment config file
146
+ .deployment.toml
147
+ /configs
148
+ /smpc_configs
149
+ .stored_data/
150
+ *.db
151
+ /tests/test_data/.data_paths/
152
+ /tests/test_data/localworker*/
153
+ /logs/
154
+
155
+ # LLMs
156
+ .codex
@@ -0,0 +1,47 @@
1
+ # Agent Instructions: exadata-validator
2
+
3
+ This directory is a separate uv-managed package for validating data-model folders
4
+ using DuckDB. Follow the root `AGENTS.md` first, then these package-specific
5
+ rules.
6
+
7
+ ## Package Overview
8
+
9
+ `exadata-validator` exposes a CLI through `validator.commands:entry` and can
10
+ also run as `python -m validator`.
11
+
12
+ ## Commands
13
+
14
+ Run commands from `exadata-validator/`.
15
+
16
+ Install:
17
+
18
+ ```bash
19
+ uv sync --all-groups
20
+ ```
21
+
22
+ Test:
23
+
24
+ ```bash
25
+ uv run pytest -q
26
+ ```
27
+
28
+ Build:
29
+
30
+ ```bash
31
+ uv build
32
+ ```
33
+
34
+ Smoke test an installed package as CI does:
35
+
36
+ ```bash
37
+ python -m validator validate-data-model tests/data/success/data_model_longitudinal_v_1_0
38
+ ```
39
+
40
+ ## Rules
41
+
42
+ - Keep dependency and lockfile changes local to this package unless the root
43
+ package also intentionally changes.
44
+ - Preserve the CLI contract documented in `README.md`.
45
+ - Add or update tests under `tests/` for validation behavior changes.
46
+ - Treat CSV/CDE validation semantics as public behavior for package users.
47
+ - Do not mix Exaflow service deployment concerns into this package.
@@ -1,20 +1,18 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: exadata-validator
3
- Version: 0.0.1
3
+ Version: 1.2.0
4
4
  Summary: Validate data-model folders using DuckDB.
5
5
  Author: Exaflow Team
6
- Requires-Python: >=3.10,<3.11
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: Programming Language :: Python :: 3.10
9
- Requires-Dist: click (>=8.1,<8.2)
10
- Requires-Dist: duckdb (>=1.1,<1.2)
6
+ Requires-Python: <3.11,>=3.10
7
+ Requires-Dist: click<8.2.0,>=8.1.0
8
+ Requires-Dist: duckdb<1.2.0,>=1.1.0
11
9
  Description-Content-Type: text/markdown
12
10
 
13
11
  # exadata-validator
14
12
 
15
13
  `exadata-validator` validates data-model folders using DuckDB.
16
14
 
17
- ## Install With pip
15
+ ## Install with pip
18
16
 
19
17
  ```bash
20
18
  python -m venv .venv
@@ -28,21 +26,21 @@ Validate a data model folder:
28
26
  exadata-validator validate-data-model /path/to/data_model_folder
29
27
  ```
30
28
 
31
- ## Develop With Poetry
32
-
33
- From the repository root:
29
+ Run as a Python module:
34
30
 
35
31
  ```bash
36
- cd data-validator/exaflow-data-validator
37
- poetry install
32
+ python -m validator validate-data-model /path/to/data_model_folder
38
33
  ```
39
34
 
40
- Then run the same CLI through Poetry:
35
+ Useful options:
41
36
 
42
37
  ```bash
43
- poetry run exadata-validator validate-data-model /path/to/data_model_folder
38
+ exadata-validator validate-data-model /path/to/data_model_folder --fail-fast
39
+ exadata-validator validate-data-model /path/to/data_model_folder --format html --output report.html
44
40
  ```
45
41
 
42
+ By default the command collects and reports all validation errors in text format. If `--format html` is used without `--output`, the report is written under `/tmp`.
43
+
46
44
  Use `exadata-validator validate-data-model --help` for reporting, output, and threading options.
47
45
 
48
46
  ## Folder Layout
@@ -58,4 +56,3 @@ Use `exadata-validator validate-data-model --help` for reporting, output, and th
58
56
 
59
57
  - CSV validation queries files directly with DuckDB and uses fused aggregate checks to reduce scan overhead.
60
58
  - Folder-level dataset uniqueness is enforced across all CSV files via SQL using normalized codes (`trim + lower`).
61
-
@@ -2,7 +2,7 @@
2
2
 
3
3
  `exadata-validator` validates data-model folders using DuckDB.
4
4
 
5
- ## Install With pip
5
+ ## Install with pip
6
6
 
7
7
  ```bash
8
8
  python -m venv .venv
@@ -16,21 +16,21 @@ Validate a data model folder:
16
16
  exadata-validator validate-data-model /path/to/data_model_folder
17
17
  ```
18
18
 
19
- ## Develop With Poetry
20
-
21
- From the repository root:
19
+ Run as a Python module:
22
20
 
23
21
  ```bash
24
- cd data-validator/exaflow-data-validator
25
- poetry install
22
+ python -m validator validate-data-model /path/to/data_model_folder
26
23
  ```
27
24
 
28
- Then run the same CLI through Poetry:
25
+ Useful options:
29
26
 
30
27
  ```bash
31
- poetry run exadata-validator validate-data-model /path/to/data_model_folder
28
+ exadata-validator validate-data-model /path/to/data_model_folder --fail-fast
29
+ exadata-validator validate-data-model /path/to/data_model_folder --format html --output report.html
32
30
  ```
33
31
 
32
+ By default the command collects and reports all validation errors in text format. If `--format html` is used without `--output`, the report is written under `/tmp`.
33
+
34
34
  Use `exadata-validator validate-data-model --help` for reporting, output, and threading options.
35
35
 
36
36
  ## Folder Layout
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "exadata-validator"
3
+ version = "1.2.0"
4
+ description = "Validate data-model folders using DuckDB."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Exaflow Team" },
8
+ ]
9
+ requires-python = ">=3.10,<3.11"
10
+ dependencies = [
11
+ "click>=8.1.0,<8.2.0",
12
+ "duckdb>=1.1.0,<1.2.0",
13
+ ]
14
+
15
+ [project.scripts]
16
+ exadata-validator = "validator.commands:entry"
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "pytest>=8.4.0,<8.5.0",
21
+ ]
22
+
23
+ [build-system]
24
+ requires = ["hatchling"]
25
+ build-backend = "hatchling.build"
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["validator"]
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
@@ -0,0 +1,26 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+
6
+ TEST_DIR = Path(__file__).resolve().parent
7
+ DATA_DIR = TEST_DIR / "data"
8
+
9
+ SUCCESS_MODEL_DIR = DATA_DIR / "success" / "data_model_v_1_0"
10
+ FAIL_MODEL_DIR = DATA_DIR / "fail" / "data_model_v_1_0"
11
+
12
+ SUCCESS_METADATA_PATH = SUCCESS_MODEL_DIR / "CDEsMetadata.json"
13
+ FAIL_METADATA_PATH = FAIL_MODEL_DIR / "CDEsMetadata.json"
14
+
15
+ LONGITUDINAL_SUCCESS_DIR = DATA_DIR / "success" / "data_model_longitudinal_v_1_0"
16
+ LONGITUDINAL_FAIL_DIR = DATA_DIR / "fail" / "data_model_longitudinal_v_1_0"
17
+
18
+
19
+ @pytest.fixture
20
+ def success_metadata():
21
+ return json.loads(SUCCESS_METADATA_PATH.read_text(encoding="utf-8"))
22
+
23
+
24
+ @pytest.fixture
25
+ def fail_metadata():
26
+ return json.loads(FAIL_METADATA_PATH.read_text(encoding="utf-8"))
@@ -0,0 +1,45 @@
1
+ {
2
+ "code": "data_model_longitudinal",
3
+ "label": "The Data Model Longitudinal",
4
+ "version": "1.0",
5
+ "longitudinal": true,
6
+ "variables": [
7
+ {
8
+ "isCategorical": true,
9
+ "code": "dataset",
10
+ "sql_type": "text",
11
+ "description": "",
12
+ "enumerations": [
13
+ {"code": "dataset_longitudinal", "label": "Dataset Longitudinal"}
14
+ ],
15
+ "label": "Dataset",
16
+ "methodology": "",
17
+ "type": "nominal"
18
+ },
19
+ {
20
+ "isCategorical": false,
21
+ "code": "subjectid",
22
+ "sql_type": "text",
23
+ "description": "",
24
+ "label": "subjectid",
25
+ "methodology": "",
26
+ "type": "nominal"
27
+ },
28
+ {
29
+ "isCategorical": true,
30
+ "label": "visitid",
31
+ "code": "visitid",
32
+ "sql_type": "text",
33
+ "enumerations": [
34
+ {"code": "BL", "label": "Base line"},
35
+ {"code": "FL1", "label": "Follow up 1"},
36
+ {"code": "FL2", "label": "Follow up 2"}
37
+ ],
38
+ "description": "",
39
+ "methodology": "",
40
+ "type": "nominal"
41
+ }
42
+ ],
43
+ "groups": [
44
+ ]
45
+ }
@@ -0,0 +1,5 @@
1
+ subjectid,visitid,dataset
2
+ "subjectid1","BL","dataset_longitudinal"
3
+ "subjectid2","FL1","dataset_longitudinal"
4
+ "subjectid2","FL1","dataset_longitudinal"
5
+ "subjectid2","FL2","dataset_longitudinal"
@@ -0,0 +1,88 @@
1
+ {
2
+ "code": "data_model",
3
+ "label": "The Data Model",
4
+ "version": "1.0",
5
+ "variables": [
6
+ {
7
+ "isCategorical": false,
8
+ "code": "var1",
9
+ "sql_type": "text",
10
+ "description": "",
11
+ "label": "Variable 1",
12
+ "methodology": "",
13
+ "type": "nominal"
14
+ },
15
+ {
16
+ "isCategorical": false,
17
+ "label": "subjectcode",
18
+ "code": "subjectcode",
19
+ "sql_type": "text",
20
+ "description": "",
21
+ "methodology": "",
22
+ "type": "nominal"
23
+ },
24
+ {
25
+ "isCategorical": true,
26
+ "code": "var2",
27
+ "sql_type": "text",
28
+ "description": "",
29
+ "enumerations": [
30
+ {"code": "l1", "label": "Level1"},
31
+ {"code": "l2", "label": "Level2"}
32
+ ],
33
+ "label": "Variable 2",
34
+ "methodology": "",
35
+ "type": "nominal"
36
+ },
37
+ {
38
+ "isCategorical": true,
39
+ "code": "dataset",
40
+ "sql_type": "text",
41
+ "description": "",
42
+ "enumerations": [
43
+ {"code": "valid_dataset", "label": "Valid dataset"},
44
+ {"code": "dataset_is_not_unique", "label": "Dataset with more one value"}
45
+ ],
46
+ "label": "Dataset",
47
+ "methodology": "",
48
+ "type": "nominal"
49
+ }
50
+ ],
51
+ "groups": [
52
+ {
53
+ "name": "group",
54
+ "label": "The Group",
55
+ "variables": [
56
+ {
57
+ "isCategorical": false,
58
+ "code": "var3",
59
+ "sql_type": "real",
60
+ "minValue": 5,
61
+ "maxValue": 60,
62
+ "description": "",
63
+ "label": "Variable 3",
64
+ "methodology": "",
65
+ "type": "nominal"
66
+ }
67
+ ],
68
+ "groups": [
69
+ {
70
+ "name": "inner_group",
71
+ "label": "The Inner Group",
72
+ "variables": [
73
+ {
74
+ "isCategorical": false,
75
+ "code": "var4",
76
+ "sql_type": "int",
77
+ "units": "years",
78
+ "description": "",
79
+ "label": "Variable 4",
80
+ "methodology": "",
81
+ "type": "nominal"
82
+ }
83
+ ]
84
+ }
85
+ ]
86
+ }
87
+ ]
88
+ }
@@ -0,0 +1,6 @@
1
+ row_id,var1,var3,dataset,non_existing_col
2
+ 1,l1,,valid_dataset,1
3
+ 2,l2,22,valid_dataset,2
4
+ 3,l1,23,valid_dataset,3
5
+ 4,l1,24,valid_dataset,4
6
+ 5,l2,25,valid_dataset,5
@@ -0,0 +1,6 @@
1
+ subjectcode,var2,var3,var4,dataset
2
+ 1,l1,100,,valid_dataset
3
+ 2,l2,12,22,valid_dataset
4
+ 3,l1,13,23,valid_dataset
5
+ 4,l1,14,24,valid_dataset
6
+ 5,l2,15,25,valid_dataset
@@ -0,0 +1,6 @@
1
+ subjectcode,var2,var3,var4,dataset
2
+ 1,l1,0,,valid_dataset
3
+ 2,l2,12,22,valid_dataset
4
+ 3,l1,13,23,valid_dataset
5
+ 4,l1,14,24,valid_dataset
6
+ 5,l2,15,25,valid_dataset
@@ -0,0 +1,6 @@
1
+ subjectcode,var1,var3,dataset
2
+ 1,l1,,valid_dataset
3
+ 2,l2,22,valid_dataset
4
+ 3,l1,23,valid_dataset
5
+ 4,l1,24,valid_dataset
6
+ 5,l2,25,dataset_is_not_unique
@@ -0,0 +1,6 @@
1
+ subjectcode,var1,var3,dataset
2
+ 1,l1,,valid_dataset
3
+ 2,l2,22,valid_dataset
4
+ 3,l1,23,valid_dataset
5
+ 4,l1,24,valid_dataset
6
+ 1,l2,25,valid_dataset
@@ -0,0 +1,6 @@
1
+ subjectcode,var2,var3,dataset
2
+ 1,l3,,valid_dataset
3
+ 2,l2,22,valid_dataset
4
+ 3,l1,23,valid_dataset
5
+ 4,l1,24,valid_dataset
6
+ 5,l2,25,valid_dataset
@@ -0,0 +1,6 @@
1
+ subjectcode,var1,var3,dataset
2
+ 1,l10,,valid_dataset
3
+ 2,l20,invalid,valid_dataset
4
+ 3,l10,23,valid_dataset
5
+ 4,l10,24,valid_dataset
6
+ 5,l20,25,valid_dataset
@@ -0,0 +1,6 @@
1
+ subjectcode,var1,var3
2
+ 1,l1,
3
+ 2,l2,22
4
+ 3,l1,23
5
+ 4,l1,24
6
+ 5,l2,25
@@ -0,0 +1,6 @@
1
+ subjectcode,var1,var3,dataset
2
+ 1,l1,,valid_dataset
3
+ 2,l2,22,valid_dataset
4
+ 2,l1,23,valid_dataset
5
+ 2,l1,24,valid_dataset
6
+ 3,l2,25,valid_dataset
@@ -0,0 +1,6 @@
1
+ row_id,var1,var3,dataset
2
+ 1,l1,,valid_dataset
3
+ 2,l2,22,valid_dataset
4
+ 3,l1,23,valid_dataset
5
+ 4,l1,24,valid_dataset
6
+ 5,l2,25,valid_dataset
@@ -0,0 +1,88 @@
1
+ {
2
+ "code": "data_model1",
3
+ "label": "The Data Model",
4
+ "version": "1.0",
5
+ "variables": [
6
+ {
7
+ "isCategorical": false,
8
+ "code": "var1",
9
+ "sql_type": "text",
10
+ "description": "",
11
+ "label": "Variable 1",
12
+ "methodology": "",
13
+ "type": "nominal"
14
+ },
15
+ {
16
+ "isCategorical": false,
17
+ "label": "subjectcode",
18
+ "code": "subjectcode",
19
+ "sql_type": "text",
20
+ "description": "",
21
+ "methodology": "",
22
+ "type": "nominal"
23
+ },
24
+ {
25
+ "isCategorical": true,
26
+ "code": "var2",
27
+ "sql_type": "text",
28
+ "description": "",
29
+ "enumerations": [
30
+ {"code": "l10", "label": "Level10"},
31
+ {"code": "l20", "label": "Level20"}
32
+ ],
33
+ "label": "Variable 2",
34
+ "methodology": "",
35
+ "type": "nominal"
36
+ },
37
+ {
38
+ "isCategorical": true,
39
+ "code": "dataset",
40
+ "sql_type": "text",
41
+ "description": "",
42
+ "enumerations": [
43
+ {"code": "dataset10", "label": "Dataset 10"},
44
+ {"code": "dataset20", "label": "Dataset 20"}
45
+ ],
46
+ "label": "Dataset",
47
+ "methodology": "",
48
+ "type": "nominal"
49
+ }
50
+ ],
51
+ "groups": [
52
+ {
53
+ "name": "group",
54
+ "label": "The Group",
55
+ "variables": [
56
+ {
57
+ "isCategorical": false,
58
+ "code": "var3",
59
+ "sql_type": "real",
60
+ "minValue": 0,
61
+ "maxValue": 100,
62
+ "description": "",
63
+ "label": "Variable 3",
64
+ "methodology": "",
65
+ "type": "nominal"
66
+ }
67
+ ],
68
+ "groups": [
69
+ {
70
+ "name": "inner_group",
71
+ "label": "The Inner Group",
72
+ "variables": [
73
+ {
74
+ "isCategorical": false,
75
+ "code": "var4",
76
+ "sql_type": "int",
77
+ "units": "years",
78
+ "description": "",
79
+ "label": "Variable 4",
80
+ "methodology": "",
81
+ "type": "nominal"
82
+ }
83
+ ]
84
+ }
85
+ ]
86
+ }
87
+ ]
88
+ }
@@ -0,0 +1,6 @@
1
+ subjectcode,var1,var3,dataset
2
+ 1,l10,,dataset10
3
+ 1,l20,22,dataset10
4
+ 2,l10,23,dataset10
5
+ 4,l10,24,dataset10
6
+ 5,l20,25,dataset10
@@ -0,0 +1,6 @@
1
+ subjectcode,var2,var3,var4,dataset
2
+ 1,l10,11,,dataset20
3
+ 2,l20,12,22,dataset20
4
+ 2,l10,13,23,dataset20
5
+ 4,l10,14,24,dataset20
6
+ 4,l20,15,25,dataset20