From 83245b1f9b59d76e458acd9fee7610cc1d92095f Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 26 Feb 2020 15:38:00 -0500 Subject: [PATCH 01/95] Fix inheriting database properties in phenopacket diseases from phenopacket disease schema object --- chord_metadata_service/phenopackets/schemas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chord_metadata_service/phenopackets/schemas.py b/chord_metadata_service/phenopackets/schemas.py index a3061f1c1..46610d4fe 100644 --- a/chord_metadata_service/phenopackets/schemas.py +++ b/chord_metadata_service/phenopackets/schemas.py @@ -637,9 +637,9 @@ def _tag_with_database_attrs(schema: dict, db_attrs: dict): "items": { **PHENOPACKET_DISEASE_SCHEMA, "search": { - **PHENOPACKET_DISEASE_SCHEMA.get("search", {}), + **PHENOPACKET_DISEASE_SCHEMA["search"], "database": { - **PHENOPACKET_DISEASE_SCHEMA.get("database", {}), + **PHENOPACKET_DISEASE_SCHEMA["search"]["database"], "relationship": { "type": "MANY_TO_ONE", "foreign_key": "disease_id" # TODO: No hard-code, from M2M From 74ff115940be03ae362a32f617d0c314c21a9e0b Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Fri, 28 Feb 2020 15:13:38 -0500 Subject: [PATCH 02/95] Update sphinx config file version --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 762243477..44b0bcde2 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,7 +2,7 @@ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required -version: 1 +version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: From e0d44993f9520aad8c04eb5a2e54887e7b6c9c40 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Fri, 28 Feb 2020 15:18:38 -0500 Subject: [PATCH 03/95] Bump version and switch to a new way of setting version numbers --- chord_metadata_service/__init__.py | 13 ++++++++----- chord_metadata_service/package.cfg | 4 ++++ docs/conf.py | 19 ++++++++++++++++--- docs/requirements.txt | 2 -- requirements.txt | 15 +++++++++++++++ setup.py | 11 ++++++++--- 6 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 chord_metadata_service/package.cfg delete mode 100644 docs/requirements.txt diff --git a/chord_metadata_service/__init__.py b/chord_metadata_service/__init__.py index 74da2e0cd..5847e6fef 100644 --- a/chord_metadata_service/__init__.py +++ b/chord_metadata_service/__init__.py @@ -1,13 +1,16 @@ #!/usr/bin/env python3 +import configparser import os -from chord_lib.utils import get_own_version -from pathlib import Path - from . import metadata from . import patients -name = "chord_metadata_service" -__version__ = get_own_version(os.path.join(Path(os.path.dirname(os.path.realpath(__file__))).parent, "setup.py"), name) + +config = configparser.ConfigParser() +config.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), "package.cfg")) + + +name = config["package"]["name"] +__version__ = config["package"]["version"] __all__ = ["name", "__version__", "metadata", "patients"] diff --git a/chord_metadata_service/package.cfg b/chord_metadata_service/package.cfg new file mode 100644 index 000000000..7dffac0fc --- /dev/null +++ b/chord_metadata_service/package.cfg @@ -0,0 +1,4 @@ +[package] +name = chord_metadata_service +version = 0.5.2 +authors = Ksenia Zaytseva, David Lougheed, Simon Chénard diff --git a/docs/conf.py b/docs/conf.py index b009b01e2..fe3296dd3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,22 +10,35 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # +import configparser +import datetime import os import sys import django + +from pathlib import Path + sys.path.insert(0, os.path.abspath('..')) os.environ['DJANGO_SETTINGS_MODULE'] = 'chord_metadata_service.metadata.settings' django.setup() +config = configparser.ConfigParser() +config.read(os.path.join(Path(os.path.dirname(os.path.realpath(__file__))).parent, + "chord_metadata_service", + "package.cfg")) + # -- Project information ----------------------------------------------------- project = 'Metadata service' -copyright = '2020, Ksenia Zaytseva, David Lougheed, Simon Chénard' -author = 'Ksenia Zaytseva, David Lougheed, Simon Chénard' +author = config["package"]["authors"] +# noinspection PyShadowingBuiltins +copyright = f"{datetime.datetime.now().year} {author}" # The full version, including alpha/beta/rc tags -release = '0.5.1' +release = config["package"]["version"] + +version = ".".join(release.split(".")[:2]) # -- General configuration --------------------------------------------------- diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index ead0ef82a..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -Sphinx==2.4.2 -sphinx_rtd_theme diff --git a/requirements.txt b/requirements.txt index 4a80cafaf..c7b7daa98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ +alabaster==0.7.12 attrs==19.3.0 +Babel==2.8.0 certifi==2019.11.28 chardet==3.0.4 chord-lib==0.5.0 @@ -11,9 +13,11 @@ django-filter==2.2.0 django-nose==1.4.6 djangorestframework==3.10.3 djangorestframework-camel-case==1.1.2 +docutils==0.16 elasticsearch==7.1.0 fhirclient==3.2.0 idna==2.9 +imagesize==1.2.0 importlib-metadata==1.5.0 isodate==0.6.0 itypes==1.1.0 @@ -24,7 +28,9 @@ MarkupSafe==1.1.1 more-itertools==8.2.0 nose==1.3.7 openapi-codec==1.3.2 +packaging==20.1 psycopg2-binary==2.8.4 +Pygments==2.5.2 pyparsing==2.4.6 pyrsistent==0.15.7 python-dateutil==2.8.1 @@ -36,6 +42,15 @@ redis==3.4.1 requests==2.23.0 simplejson==3.17.0 six==1.14.0 +snowballstemmer==2.0.0 +Sphinx==2.4.2 +sphinx-rtd-theme==0.4.3 +sphinxcontrib-applehelp==1.0.1 +sphinxcontrib-devhelp==1.0.1 +sphinxcontrib-htmlhelp==1.0.3 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.2 +sphinxcontrib-serializinghtml==1.1.3 sqlparse==0.3.0 strict-rfc3339==0.7 uritemplate==3.0.1 diff --git a/setup.py b/setup.py index 4463e5687..51674a9f1 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,18 @@ #!/usr/bin/env python +import configparser +import os import setuptools with open("README.md", "r") as rf: long_description = rf.read() +config = configparser.ConfigParser() +config.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), "chord_metadata_service", "package.cfg")) + setuptools.setup( - name="chord_metadata_service", - version="0.5.1", + name=config["package"]["name"], + version=config["package"]["version"], python_requires=">=3.6", install_requires=[ @@ -29,7 +34,7 @@ "uritemplate>=3.0,<4.0", ], - author="Ksenia Zaytseva, David Lougheed, Simon Chénard", + author=config["package"]["authors"], description="An implementation of a variant store for the CHORD project.", long_description=long_description, From 03feeae4337a8b6e46670c69bc031d7b84bb70d4 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Fri, 28 Feb 2020 15:25:50 -0500 Subject: [PATCH 04/95] Add package cfg to manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 0060572f2..5e497cd7e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include chord_metadata_service/chord/workflows/phenopackets_json.wdl include chord_metadata_service/dats/* +include chord_metadata_service/package.cfg From 61afcc5a0e4358bd4467a00fd7b17ed5950902ac Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 2 Mar 2020 15:10:23 -0500 Subject: [PATCH 05/95] Code cleanup --- chord_metadata_service/patients/models.py | 39 +++++++------------ chord_metadata_service/phenopackets/models.py | 3 +- chord_metadata_service/phenopackets/views.py | 3 -- chord_metadata_service/restapi/views.py | 3 -- 4 files changed, 16 insertions(+), 32 deletions(-) delete mode 100644 chord_metadata_service/phenopackets/views.py delete mode 100644 chord_metadata_service/restapi/views.py diff --git a/chord_metadata_service/patients/models.py b/chord_metadata_service/patients/models.py index 66d599ab5..046b4532e 100644 --- a/chord_metadata_service/patients/models.py +++ b/chord_metadata_service/patients/models.py @@ -27,36 +27,27 @@ class Individual(models.Model, IndexableMixin): ('OTHER_KARYOTYPE', 'OTHER_KARYOTYPE'), ) - id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the individual.') + id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the individual.') # TODO check for CURIE - alternate_ids = ArrayField(models.CharField(max_length=200), - blank=True, null=True, - help_text='A list of alternative identifiers for the individual.') - date_of_birth = models.DateField(null=True, blank=True, - help_text='A timestamp either exact or imprecise.') + alternate_ids = ArrayField(models.CharField(max_length=200), blank=True, null=True, + help_text='A list of alternative identifiers for the individual.') + date_of_birth = models.DateField(null=True, blank=True, help_text='A timestamp either exact or imprecise.') # An ISO8601 string represent age - age = JSONField(blank=True, null=True, - help_text='The age or age range of the individual.') + age = JSONField(blank=True, null=True, help_text='The age or age range of the individual.') sex = models.CharField(choices=SEX, max_length=200, blank=True, null=True, - help_text='Observed apparent sex of the individual.') - karyotypic_sex = models.CharField(choices=KARYOTYPIC_SEX, max_length=200, - default='UNKNOWN_KARYOTYPE', - help_text='The karyotypic sex of the individual.') - taxonomy = JSONField(blank=True, null=True, help_text='Ontology resource ' - 'representing the species (e.g., NCBITaxon:9615).') + help_text='Observed apparent sex of the individual.') + karyotypic_sex = models.CharField(choices=KARYOTYPIC_SEX, max_length=200, default='UNKNOWN_KARYOTYPE', + help_text='The karyotypic sex of the individual.') + taxonomy = JSONField(blank=True, null=True, + help_text='Ontology resource representing the species (e.g., NCBITaxon:9615).') # FHIR specific - active = models.BooleanField(default=False, - help_text='Whether this patient\'s record is in active use.') - deceased = models.BooleanField(default=False, - help_text='Indicates if the individual is deceased or not.') + active = models.BooleanField(default=False, help_text='Whether this patient\'s record is in active use.') + deceased = models.BooleanField(default=False, help_text='Indicates if the individual is deceased or not.') # mCode specific - race = models.CharField(max_length=200, blank=True, - help_text='A code for the person\'s race.') - ethnicity = models.CharField(max_length=200, blank=True, - help_text='A code for the person\'s ethnicity.') + race = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s race.') + ethnicity = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s ethnicity.') extra_properties = JSONField(blank=True, null=True, - help_text='Extra properties that are not supported by current schema') + help_text='Extra properties that are not supported by current schema') created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) diff --git a/chord_metadata_service/phenopackets/models.py b/chord_metadata_service/phenopackets/models.py index 938f2668d..0eca266a6 100644 --- a/chord_metadata_service/phenopackets/models.py +++ b/chord_metadata_service/phenopackets/models.py @@ -2,7 +2,6 @@ from django.utils import timezone from django.core.exceptions import ValidationError from django.contrib.postgres.fields import JSONField, ArrayField -from elasticsearch import Elasticsearch from chord_metadata_service.patients.models import Individual from chord_metadata_service.restapi.description_utils import rec_help from chord_metadata_service.restapi.models import IndexableMixin @@ -55,7 +54,7 @@ class MetaData(models.Model): JSONField(null=True, blank=True), blank=True, null=True, help_text=rec_help(d.META_DATA, "updates")) phenopacket_schema_version = models.CharField(max_length=200, blank=True, - help_text='Schema version of the current phenopacket.') + help_text='Schema version of the current phenopacket.') external_references = ArrayField( JSONField(null=True, blank=True), blank=True, null=True, help_text=rec_help(d.META_DATA, "external_references")) diff --git a/chord_metadata_service/phenopackets/views.py b/chord_metadata_service/phenopackets/views.py deleted file mode 100644 index 91ea44a21..000000000 --- a/chord_metadata_service/phenopackets/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/chord_metadata_service/restapi/views.py b/chord_metadata_service/restapi/views.py deleted file mode 100644 index 91ea44a21..000000000 --- a/chord_metadata_service/restapi/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. From 0f746704da84dea0d50b99c89f5f18c48c3f16f8 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 2 Mar 2020 15:25:43 -0500 Subject: [PATCH 06/95] More cleanup --- .../phenopackets/api_views.py | 4 +- .../phenopackets/serializers.py | 54 ++++++------------- chord_metadata_service/restapi/admin.py | 3 -- chord_metadata_service/restapi/models.py | 4 +- chord_metadata_service/restapi/schemas.py | 30 +++++------ 5 files changed, 34 insertions(+), 61 deletions(-) delete mode 100644 chord_metadata_service/restapi/admin.py diff --git a/chord_metadata_service/phenopackets/api_views.py b/chord_metadata_service/phenopackets/api_views.py index 723c09e23..4ecc99b5d 100644 --- a/chord_metadata_service/phenopackets/api_views.py +++ b/chord_metadata_service/phenopackets/api_views.py @@ -8,12 +8,12 @@ class PhenopacketsModelViewSet(viewsets.ModelViewSet): - renderer_classes = tuple(api_settings.DEFAULT_RENDERER_CLASSES) + (PhenopacketsRenderer,) + renderer_classes = (*api_settings.DEFAULT_RENDERER_CLASSES, PhenopacketsRenderer) pagination_class = LargeResultsSetPagination class ExtendedPhenopacketsModelViewSet(PhenopacketsModelViewSet): - renderer_classes = tuple(PhenopacketsModelViewSet.renderer_classes) + (FHIRRenderer,) + renderer_classes = (*PhenopacketsModelViewSet.renderer_classes, FHIRRenderer) class PhenotypicFeatureViewSet(ExtendedPhenopacketsModelViewSet): diff --git a/chord_metadata_service/phenopackets/serializers.py b/chord_metadata_service/phenopackets/serializers.py index 9761fd8b3..8669f16ec 100644 --- a/chord_metadata_service/phenopackets/serializers.py +++ b/chord_metadata_service/phenopackets/serializers.py @@ -60,8 +60,7 @@ def validate_external_references(self, value): ############################################################# class PhenotypicFeatureSerializer(GenericSerializer): - type = serializers.JSONField(source='pftype', - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) + type = serializers.JSONField(source='pftype', validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) severity = serializers.JSONField( validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], allow_null=True, required=False) @@ -210,8 +209,8 @@ class BiosampleSerializer(GenericSerializer): tumor_grade = serializers.JSONField( validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], allow_null=True, required=False) - phenotypic_features = PhenotypicFeatureSerializer(read_only=True, - many=True, exclude_when_nested=['id', 'biosample']) + phenotypic_features = PhenotypicFeatureSerializer( + read_only=True, many=True, exclude_when_nested=['id', 'biosample']) procedure = ProcedureSerializer(exclude_when_nested=['id']) class Meta: @@ -238,18 +237,12 @@ def create(self, validated_data): return biosample def update(self, instance, validated_data): - instance.sampled_tissue = validated_data.get('sampled_tissue', - instance.sampled_tissue) - instance.taxonomy = validated_data.get('taxonomy', - instance.taxonomy) - instance.histological_diagnosis = validated_data.get('histological_diagnosis', - instance.histological_diagnosis) - instance.tumor_progression = validated_data.get('tumor_progression', - instance.tumor_progression) - instance.tumor_grade = validated_data.get('tumor_grade', - instance.tumor_grade) - instance.diagnostic_markers = validated_data.get('diagnostic_markers', - instance.diagnostic_markers) + instance.sampled_tissue = validated_data.get('sampled_tissue', instance.sampled_tissue) + instance.taxonomy = validated_data.get('taxonomy', instance.taxonomy) + instance.histological_diagnosis = validated_data.get('histological_diagnosis', instance.histological_diagnosis) + instance.tumor_progression = validated_data.get('tumor_progression', instance.tumor_progression) + instance.tumor_grade = validated_data.get('tumor_grade', instance.tumor_grade) + instance.diagnostic_markers = validated_data.get('diagnostic_markers', instance.diagnostic_markers) instance.save() procedure_data = validated_data.pop('procedure', None) if procedure_data: @@ -276,25 +269,13 @@ def to_representation(self, instance): """ response = super().to_representation(instance) - response['biosamples'] = BiosampleSerializer( - instance.biosamples, many=True, required=False, - exclude_when_nested=["individual"] - ).data - response['genes'] = GeneSerializer( - instance.genes, many=True, required=False - ).data - response['variants'] = VariantSerializer( - instance.variants, many=True, required=False - ).data - response['diseases'] = DiseaseSerializer( - instance.diseases, many=True, required=False - ).data - response['hts_files'] = HtsFileSerializer( - instance.hts_files, many=True, required=False - ).data - response['meta_data'] = MetaDataSerializer( - instance.meta_data, exclude_when_nested=['id'] - ).data + response['biosamples'] = BiosampleSerializer(instance.biosamples, many=True, required=False, + exclude_when_nested=["individual"]).data + response['genes'] = GeneSerializer(instance.genes, many=True, required=False).data + response['variants'] = VariantSerializer(instance.variants, many=True, required=False).data + response['diseases'] = DiseaseSerializer(instance.diseases, many=True, required=False).data + response['hts_files'] = HtsFileSerializer(instance.hts_files, many=True, required=False).data + response['meta_data'] = MetaDataSerializer(instance.meta_data, exclude_when_nested=['id']).data return response @@ -319,21 +300,18 @@ def to_representation(self, instance): ############################################################# class GenomicInterpretationSerializer(GenericSerializer): - class Meta: model = GenomicInterpretation fields = '__all__' class DiagnosisSerializer(GenericSerializer): - class Meta: model = Diagnosis fields = '__all__' class InterpretationSerializer(GenericSerializer): - class Meta: model = Interpretation fields = '__all__' diff --git a/chord_metadata_service/restapi/admin.py b/chord_metadata_service/restapi/admin.py deleted file mode 100644 index 8c38f3f3d..000000000 --- a/chord_metadata_service/restapi/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/chord_metadata_service/restapi/models.py b/chord_metadata_service/restapi/models.py index d278c3d80..6f6183660 100644 --- a/chord_metadata_service/restapi/models.py +++ b/chord_metadata_service/restapi/models.py @@ -1,6 +1,4 @@ - - -class IndexableMixin(): +class IndexableMixin: @property def index_id(self): return f"{self.__class__.__name__}|{self.__str__()}" diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 76afd5ab7..653ec0170 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -129,22 +129,22 @@ AGE_RANGE = { "type": "object", - "properties": { - "start": { - "type": "object", - "properties": { - "age": AGE - } - }, - "end": { - "type": "object", - "properties": { - "age": AGE - } - } + "properties": { + "start": { + "type": "object", + "properties": { + "age": AGE + } + }, + "end": { + "type": "object", + "properties": { + "age": AGE + } + } }, - "additionalProperties": False, - "required": ["start", "end"] + "additionalProperties": False, + "required": ["start", "end"] } AGE_OR_AGE_RANGE = { From eecd2324b5c001068c2e5d7376e099e7f6ad8c2c Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 2 Mar 2020 15:26:41 -0500 Subject: [PATCH 07/95] Simplify camel_case_field_names util function --- chord_metadata_service/restapi/utils.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/chord_metadata_service/restapi/utils.py b/chord_metadata_service/restapi/utils.py index e9f44ab1a..a2d2c73a5 100644 --- a/chord_metadata_service/restapi/utils.py +++ b/chord_metadata_service/restapi/utils.py @@ -1,14 +1,10 @@ def camel_case_field_names(string): """ Function to convert snake_case field names to camelCase """ - - if '_' in string: - splitted = string.split('_') - capitilized = [] - capitilized.append(splitted[0]) - for each in splitted[1:]: - capitilized.append(each.title()) - return ''.join(capitilized) - return string + # Capitalize every part except the first + return "".join( + part.title() if i > 0 else part + for i, part in enumerate(string.split("_")) + ) def transform_keys(obj): From 6adca44ce952506c39d3b98791be3dd4121f1339 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 2 Mar 2020 15:32:47 -0500 Subject: [PATCH 08/95] Refactor transform_keys Also make it recursively apply through lists --- chord_metadata_service/restapi/utils.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/chord_metadata_service/restapi/utils.py b/chord_metadata_service/restapi/utils.py index a2d2c73a5..c8b5ceeed 100644 --- a/chord_metadata_service/restapi/utils.py +++ b/chord_metadata_service/restapi/utils.py @@ -13,10 +13,13 @@ def transform_keys(obj): It iterates over a dict and changes all keys in nested objects to camelCase. """ + if isinstance(obj, list): + return [transform_keys(i) for i in obj] + if isinstance(obj, dict): - transformed_obj = {} - for key, value in obj.items(): - if isinstance(value, dict): - value = transform_keys(value) - transformed_obj[camel_case_field_names(key)] = value - return transformed_obj + return { + camel_case_field_names(key): transform_keys(value) + for key, value in obj.items() + } + + return obj From ccddd1a770991ff07a1dbe69cd856f7f37ceefb3 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 4 Mar 2020 11:35:02 -0500 Subject: [PATCH 09/95] Update requirements --- requirements.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index c7b7daa98..bc4e36bf6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,18 +43,18 @@ requests==2.23.0 simplejson==3.17.0 six==1.14.0 snowballstemmer==2.0.0 -Sphinx==2.4.2 +Sphinx==2.4.3 sphinx-rtd-theme==0.4.3 -sphinxcontrib-applehelp==1.0.1 -sphinxcontrib-devhelp==1.0.1 +sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==1.0.3 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.2 -sphinxcontrib-serializinghtml==1.1.3 -sqlparse==0.3.0 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.4 +sqlparse==0.3.1 strict-rfc3339==0.7 uritemplate==3.0.1 urllib3==1.25.8 Werkzeug==1.0.0 wincertstore==0.2 -zipp==3.0.0 +zipp==3.1.0 From c0f2136da7a2d1bb8f28559e607b6e653c7facad Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 4 Mar 2020 11:51:23 -0500 Subject: [PATCH 10/95] Remove unused import --- chord_metadata_service/chord/tests/test_api_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chord_metadata_service/chord/tests/test_api_search.py b/chord_metadata_service/chord/tests/test_api_search.py index fbac7f0d6..224c026df 100644 --- a/chord_metadata_service/chord/tests/test_api_search.py +++ b/chord_metadata_service/chord/tests/test_api_search.py @@ -1,5 +1,5 @@ import json -from unittest.mock import Mock, patch +from unittest.mock import patch from django.test import override_settings from django.urls import reverse From 62d2845815bd5666f63fd13e48b131b35ca8a19c Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 4 Mar 2020 11:51:48 -0500 Subject: [PATCH 11/95] Add table summary endpoint --- .../chord/tests/test_api_search.py | 5 ++ chord_metadata_service/chord/views_search.py | 58 ++++++++++++++++++- chord_metadata_service/metadata/urls.py | 1 + 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/chord_metadata_service/chord/tests/test_api_search.py b/chord_metadata_service/chord/tests/test_api_search.py index 224c026df..4edee26fd 100644 --- a/chord_metadata_service/chord/tests/test_api_search.py +++ b/chord_metadata_service/chord/tests/test_api_search.py @@ -83,6 +83,11 @@ def test_table_list(self): self.assertEqual(len(c), 1) self.assertEqual(c[0], self.dataset_rep(self.dataset, c[0]["metadata"]["created"], c[0]["metadata"]["updated"])) + r = self.client.get(reverse("table-summary", kwargs={"table_id": self.dataset["identifier"]})) + s = r.json() + assert s["count"] == 0 # No phenopackets + assert "data_type_specific" in s + class SearchTest(APITestCase): def setUp(self) -> None: diff --git a/chord_metadata_service/chord/views_search.py b/chord_metadata_service/chord/views_search.py index f0a225bd6..450780da3 100644 --- a/chord_metadata_service/chord/views_search.py +++ b/chord_metadata_service/chord/views_search.py @@ -1,6 +1,7 @@ import itertools -from datetime import datetime +from collections import Counter +from datetime import datetime from django.db import connection from django.conf import settings from psycopg2 import sql @@ -11,11 +12,13 @@ from chord_lib.responses.errors import * from chord_lib.search import build_search_response, postgres from chord_metadata_service.metadata.settings import DEBUG +from chord_metadata_service.patients.models import Individual from chord_metadata_service.phenopackets.api_views import PHENOPACKET_PREFETCH -from chord_metadata_service.phenopackets.models import Phenopacket +from chord_metadata_service.phenopackets.models import Phenopacket, Biosample from chord_metadata_service.phenopackets.schemas import PHENOPACKET_SCHEMA from chord_metadata_service.phenopackets.serializers import PhenopacketSerializer from chord_metadata_service.metadata.elastic import es + from .models import Dataset from .permissions import OverrideOrSuperUserOnly @@ -92,6 +95,57 @@ def table_detail(request, table_id): # pragma: no cover return Response(status=204) +# Mounted on /private/, so will get protected anyway; this allows for access from federation service +# TODO: Ugly and misleading permissions +@api_view(["GET"]) +@permission_classes([AllowAny]) +def chord_private_table_summary(_request, table_id): + try: + table = Dataset.objects.get(identifier=table_id) + phenopackets = Phenopacket.objects.filter(dataset=table) + + biosamples_set = frozenset( + p["biosamples__id"] for p in phenopackets.prefetch_related("biosamples").values("biosamples__id")) + + biosamples_cs = Counter(b.is_control_sample for b in Biosample.objects.filter(id__in=biosamples_set)) + + biosamples_taxonomy = Counter(b.taxonomy["id"] for b in Biosample.objects.filter(id__in=biosamples_set) + if b.taxonomy is not None) + + individuals_set = frozenset({ + *(p["subject"] for p in phenopackets.values("subject")), + *(p["biosamples__individual_id"] + for p in phenopackets.prefetch_related("biosamples").values("biosamples__individual_id")), + }) + + individuals_sex = Counter(i.sex for i in Individual.objects.filter(id__in=individuals_set)) + individuals_k_sex = Counter(i.karyotypic_sex for i in Individual.objects.filter(id__in=individuals_set)) + individuals_taxonomy = Counter(i.taxonomy["id"] for i in Individual.objects.filter(id__in=individuals_set) + if i.taxonomy is not None) + + return Response({ + "count": phenopackets.count(), + "data_type_specific": { + "biosamples": { + "count": len(biosamples_set), + "is_control_sample": dict(biosamples_cs), + "taxonomy": dict(biosamples_taxonomy), + }, + "individuals": { + "count": len(individuals_set), + "sex": {k: individuals_sex[k] for k in (s[0] for s in Individual.SEX)}, + "karyotypic_sex": {k: individuals_k_sex[k] for k in (s[0] for s in Individual.KARYOTYPIC_SEX)}, + "diseases": {}, + "taxonomy": dict(individuals_taxonomy), + # TODO: age histogram + }, + } + }) + + except Dataset.DoesNotExist: + return Response(not_found_error(f"Table with ID {table_id} not found"), status=404) + + # TODO: CHORD-standardized logging def debug_log(message): # pragma: no cover if DEBUG: diff --git a/chord_metadata_service/metadata/urls.py b/chord_metadata_service/metadata/urls.py index 96f898a5b..145f2c10e 100644 --- a/chord_metadata_service/metadata/urls.py +++ b/chord_metadata_service/metadata/urls.py @@ -53,5 +53,6 @@ path('fhir-search', views_search.fhir_public_search, name="fhir-search"), path('private/fhir-search', views_search.fhir_private_search, name="fhir-private-search"), path('private/search', views_search.chord_private_search, name="private-search"), + path('private/tables//summary', views_search.chord_private_table_summary, name="table-summary"), path('private/tables//search', views_search.chord_private_table_search, name="table-search"), ] + ([path('admin/', admin.site.urls)] if DEBUG else []) From c8816691ce0f0f7aecf06cb8df7d5a2b68ba3786 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 4 Mar 2020 14:30:14 -0500 Subject: [PATCH 12/95] Move summary out from private endpoint --- chord_metadata_service/chord/views_search.py | 9 ++++----- chord_metadata_service/metadata/urls.py | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/chord_metadata_service/chord/views_search.py b/chord_metadata_service/chord/views_search.py index 450780da3..7d72e2903 100644 --- a/chord_metadata_service/chord/views_search.py +++ b/chord_metadata_service/chord/views_search.py @@ -84,7 +84,8 @@ def table_list(request): @permission_classes([OverrideOrSuperUserOnly]) def table_detail(request, table_id): # pragma: no cover # TODO: Implement GET, POST - # TODO: Permissions: Check if user has control / more direct access over this dataset? Or just always use owner... + # TODO: Permissions: Check if user has control / more direct access over this table and/or dataset? + # Or just always use owner... try: table = Dataset.objects.get(identifier=table_id) except Dataset.DoesNotExist: @@ -95,11 +96,9 @@ def table_detail(request, table_id): # pragma: no cover return Response(status=204) -# Mounted on /private/, so will get protected anyway; this allows for access from federation service -# TODO: Ugly and misleading permissions @api_view(["GET"]) -@permission_classes([AllowAny]) -def chord_private_table_summary(_request, table_id): +@permission_classes([OverrideOrSuperUserOnly]) +def chord_table_summary(_request, table_id): try: table = Dataset.objects.get(identifier=table_id) phenopackets = Phenopacket.objects.filter(dataset=table) diff --git a/chord_metadata_service/metadata/urls.py b/chord_metadata_service/metadata/urls.py index 145f2c10e..441a7af40 100644 --- a/chord_metadata_service/metadata/urls.py +++ b/chord_metadata_service/metadata/urls.py @@ -48,11 +48,11 @@ path('data-types/phenopacket/metadata_schema', views_search.data_type_phenopacket_metadata_schema, name="data-type-metadata-schema"), path('tables', views_search.table_list, name="table-list"), - path('tables/', views_search.table_detail, name="table-detail"), + path('tables/', views_search.table_detail, name="table-detail"), + path('tables//summary', views_search.chord_table_summary, name="table-summary"), path('search', views_search.chord_search, name="search"), path('fhir-search', views_search.fhir_public_search, name="fhir-search"), path('private/fhir-search', views_search.fhir_private_search, name="fhir-private-search"), path('private/search', views_search.chord_private_search, name="private-search"), - path('private/tables//summary', views_search.chord_private_table_summary, name="table-summary"), path('private/tables//search', views_search.chord_private_table_search, name="table-search"), ] + ([path('admin/', admin.site.urls)] if DEBUG else []) From 6cd8291bb66bdd2dc44d5138e71a1ec04a2b21f4 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 4 Mar 2020 15:25:51 -0500 Subject: [PATCH 13/95] Add some tests --- .../chord/tests/test_api_search.py | 10 ++- .../phenopackets/tests/test_models.py | 62 +++++++++++-------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/chord_metadata_service/chord/tests/test_api_search.py b/chord_metadata_service/chord/tests/test_api_search.py index 4edee26fd..e9774325c 100644 --- a/chord_metadata_service/chord/tests/test_api_search.py +++ b/chord_metadata_service/chord/tests/test_api_search.py @@ -1,4 +1,6 @@ import json +import uuid + from unittest.mock import patch from django.test import override_settings @@ -83,10 +85,14 @@ def test_table_list(self): self.assertEqual(len(c), 1) self.assertEqual(c[0], self.dataset_rep(self.dataset, c[0]["metadata"]["created"], c[0]["metadata"]["updated"])) + def test_table_summary(self): + r = self.client.get(reverse("table-summary", kwargs={"table_id": str(uuid.uuid4())})) + self.assertEqual(r.status_code, 404) + r = self.client.get(reverse("table-summary", kwargs={"table_id": self.dataset["identifier"]})) s = r.json() - assert s["count"] == 0 # No phenopackets - assert "data_type_specific" in s + self.assertEqual(s["count"], 0) # No phenopackets + self.assertIn("data_type_specific", s) class SearchTest(APITestCase): diff --git a/chord_metadata_service/phenopackets/tests/test_models.py b/chord_metadata_service/phenopackets/tests/test_models.py index e945e6212..4032c5d2f 100644 --- a/chord_metadata_service/phenopackets/tests/test_models.py +++ b/chord_metadata_service/phenopackets/tests/test_models.py @@ -59,29 +59,25 @@ def setUp(self): meta_data=self.meta_data, ) self.phenotypic_feature_1 = PhenotypicFeature.objects.create( - **valid_phenotypic_feature(biosample=self.biosample_1) - ) + **valid_phenotypic_feature(biosample=self.biosample_1)) self.phenotypic_feature_2 = PhenotypicFeature.objects.create( - **valid_phenotypic_feature( - biosample=self.biosample_2, - phenopacket=self.phenopacket) - ) + **valid_phenotypic_feature(biosample=self.biosample_2, phenopacket=self.phenopacket)) def test_phenotypic_feature(self): phenotypic_feature_query = PhenotypicFeature.objects.filter( severity__label='Mild', pftype__label='Proptosis' - ) - phenotypic_feature_2 = PhenotypicFeature.objects.filter( - phenopacket__id='phenopacket_id:1' - ) + ) + phenotypic_feature_2 = PhenotypicFeature.objects.filter(phenopacket__id='phenopacket_id:1') self.assertEqual(PhenotypicFeature.objects.count(), 2) self.assertEqual(phenotypic_feature_query.count(), 2) self.assertEqual(phenotypic_feature_2.count(), 1) + def test_phenotypic_feature_str(self): + self.assertEqual(str(self.phenotypic_feature_1), str(self.phenotypic_feature_1.id)) -class ProcedureTest(TestCase): +class ProcedureTest(TestCase): def setUp(self): self.procedure_1 = Procedure.objects.create(**VALID_PROCEDURE_1) self.procedure_2 = Procedure.objects.create(**VALID_PROCEDURE_2) @@ -96,7 +92,6 @@ def test_procedure(self): class HtsFileTest(TestCase): - def setUp(self): self.hts_file = HtsFile.objects.create(**VALID_HTS_FILE) @@ -104,22 +99,25 @@ def test_hts_file(self): hts_file = HtsFile.objects.get(genome_assembly='GRCh38') self.assertEqual(hts_file.uri, 'https://data.example/genomes/germline_wgs.vcf.gz') + def test_hts_file_str(self): + self.assertEqual(str(self.hts_file), 'https://data.example/genomes/germline_wgs.vcf.gz') -class GeneTest(TestCase): +class GeneTest(TestCase): def setUp(self): self.gene_1 = Gene.objects.create(**VALID_GENE_1) - def test_gene(self): gene_1 = Gene.objects.get(id='HGNC:347') self.assertEqual(gene_1.symbol, 'ETF1') with self.assertRaises(IntegrityError): Gene.objects.create(**DUPLICATE_GENE_2) + def test_gene_str(self): + self.assertEqual(str(self.gene_1), "HGNC:347") -class VariantTest(TestCase): +class VariantTest(TestCase): def setUp(self): self.variant = Variant.objects.create(**VALID_VARIANT_1) @@ -127,9 +125,11 @@ def test_variant(self): variant_query = Variant.objects.filter(zygosity__id='NCBITaxon:9606') self.assertEqual(variant_query.count(), 1) + def test_variant_str(self): + self.assertEqual(str(self.variant), str(self.variant.id)) -class DiseaseTest(TestCase): +class DiseaseTest(TestCase): def setUp(self): self.disease_1 = Disease.objects.create(**VALID_DISEASE_1) @@ -137,9 +137,11 @@ def test_disease(self): disease_query = Disease.objects.filter(term__id='OMIM:164400') self.assertEqual(disease_query.count(), 1) + def test_disease_str(self): + self.assertEqual(str(self.disease_1), str(self.disease_1.id)) -class GenomicInterpretationTest(TestCase): +class GenomicInterpretationTest(TestCase): def setUp(self): self.gene = Gene.objects.create(**VALID_GENE_1) self.variant = Variant.objects.create(**VALID_VARIANT_1) @@ -155,13 +157,13 @@ def test_genomic_interpretation(self): def test_validation_gene_or_variant(self): with self.assertRaises(ValidationError): - test = GenomicInterpretation.objects.create( - **valid_genomic_interpretation() - ).clean() + GenomicInterpretation.objects.create(**valid_genomic_interpretation()).clean() + def test_genomic_interpretation_str(self): + self.assertEqual(str(self.genomic_interpretation), str(self.genomic_interpretation.id)) -class DiagnosisTest(TestCase): +class DiagnosisTest(TestCase): def setUp(self): self.disease = Disease.objects.create(**VALID_DISEASE_1) @@ -178,15 +180,17 @@ def setUp(self): self.diagnosis.genomic_interpretations.set([ self.genomic_interpretation_1, self.genomic_interpretation_2 - ]) + ]) def test_diagnosis(self): diagnosis = Diagnosis.objects.filter(disease__term__id='OMIM:164400') self.assertEqual(diagnosis.count(), 1) + def test_diagnosis_str(self): + self.assertEqual(str(self.diagnosis), str(self.diagnosis.id)) -class InterpretationTest(TestCase): +class InterpretationTest(TestCase): def setUp(self): self.disease = Disease.objects.create(**VALID_DISEASE_1) self.diagnosis = Diagnosis.objects.create(**valid_diagnosis( @@ -212,9 +216,11 @@ def test_interpretation(self): ) self.assertEqual(interpretation_query.count(), 1) + def test_interpretation_str(self): + self.assertEqual(str(self.interpretation), str(self.interpretation.id)) -class ResourceTest(TestCase): +class ResourceTest(TestCase): def setUp(self): self.resource_1 = Resource.objects.create(**VALID_RESOURCE_1) self.resource_2 = Resource.objects.create(**VALID_RESOURCE_2) @@ -224,9 +230,12 @@ def test_resource(self): with self.assertRaises(IntegrityError): Resource.objects.create(**DUPLICATE_RESOURCE_3) + def test_resource_str(self): + self.assertEqual(str(self.resource_1), "so") + self.assertEqual(str(self.resource_2), "hgnc") -class MetaDataTest(TestCase): +class MetaDataTest(TestCase): def setUp(self): self.resource_1 = Resource.objects.create(**VALID_RESOURCE_1) self.resource_2 = Resource.objects.create(**VALID_RESOURCE_2) @@ -237,3 +246,6 @@ def test_metadata(self): metadata = MetaData.objects.get(created_by__icontains='ksenia') self.assertEqual(metadata.submitted_by, 'Ksenia Zaytseva') self.assertEqual(metadata.resources.count(), 2) + + def test_metadata_str(self): + self.assertEqual(str(self.metadata), str(self.metadata.id)) From 22ba911c997a8b8644e8aef308e5f7f714f71baf Mon Sep 17 00:00:00 2001 From: zxenia Date: Fri, 6 Mar 2020 10:46:36 -0500 Subject: [PATCH 14/95] add swagger view docs --- chord_metadata_service/metadata/settings.py | 4 +++- chord_metadata_service/metadata/urls.py | 6 ++++-- requirements.txt | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/chord_metadata_service/metadata/settings.py b/chord_metadata_service/metadata/settings.py index 36bfdc0d0..d027a9a69 100644 --- a/chord_metadata_service/metadata/settings.py +++ b/chord_metadata_service/metadata/settings.py @@ -68,6 +68,7 @@ 'rest_framework', 'django_nose', + 'rest_framework_swagger', ] MIDDLEWARE = [ @@ -165,7 +166,8 @@ 'djangorestframework_camel_case.parser.CamelCaseFormParser', 'djangorestframework_camel_case.parser.CamelCaseMultiPartParser', ), - 'DEFAULT_PERMISSION_CLASSES': ['chord_metadata_service.chord.permissions.OverrideOrSuperUserOnly'] + 'DEFAULT_PERMISSION_CLASSES': ['chord_metadata_service.chord.permissions.OverrideOrSuperUserOnly'], + 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' } # Password validation diff --git a/chord_metadata_service/metadata/urls.py b/chord_metadata_service/metadata/urls.py index 441a7af40..6e45b19ae 100644 --- a/chord_metadata_service/metadata/urls.py +++ b/chord_metadata_service/metadata/urls.py @@ -18,20 +18,22 @@ from chord_metadata_service.restapi import api_views, urls as restapi_urls from chord_metadata_service.chord import views_ingest, views_search from rest_framework.schemas import get_schema_view +from rest_framework_swagger.views import get_swagger_view # TODO: django.conf.settings breaks reverse(), how to import properly? from .settings import DEBUG +swagger_schema_view = get_swagger_view(title="Metadata Service API") urlpatterns = [ - path('', get_schema_view( + path('api/schema', get_schema_view( title="Metadata Service API", description="Metadata Service provides a phenotypic description of an Individual " "in the context of biomedical research.", version="0.1" ), name='openapi-schema'), - + path('', swagger_schema_view), path('api/', include(restapi_urls)), path('service-info', api_views.service_info, name="service-info"), diff --git a/requirements.txt b/requirements.txt index bc4e36bf6..43b9e28fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,12 +5,14 @@ certifi==2019.11.28 chardet==3.0.4 chord-lib==0.5.0 codecov==2.0.16 +colorama==0.4.3 coreapi==2.3.3 coreschema==0.0.4 coverage==5.0.3 Django==2.2.10 django-filter==2.2.0 django-nose==1.4.6 +django-rest-swagger==2.2.0 djangorestframework==3.10.3 djangorestframework-camel-case==1.1.2 docutils==0.16 From 15f381287be218173ac17db04a095319e77c6dae Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Fri, 6 Mar 2020 12:06:52 -0500 Subject: [PATCH 15/95] Add public table search --- .../chord/tests/test_api_search.py | 31 ++++++++++++++-- chord_metadata_service/chord/views_search.py | 37 +++++++++++++------ chord_metadata_service/metadata/urls.py | 3 +- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/chord_metadata_service/chord/tests/test_api_search.py b/chord_metadata_service/chord/tests/test_api_search.py index e9774325c..24ffb6d49 100644 --- a/chord_metadata_service/chord/tests/test_api_search.py +++ b/chord_metadata_service/chord/tests/test_api_search.py @@ -193,25 +193,48 @@ def test_private_search(self): def test_private_table_search_1(self): # No body - r = self.client.post(reverse("table-search", args=[str(self.dataset.identifier)])) + + r = self.client.post(reverse("public-table-search", args=[str(self.dataset.identifier)])) + self.assertEqual(r.status_code, status.HTTP_400_BAD_REQUEST) + + r = self.client.post(reverse("private-table-search", args=[str(self.dataset.identifier)])) self.assertEqual(r.status_code, status.HTTP_400_BAD_REQUEST) def test_private_table_search_2(self): # No query - r = self.client.post(reverse("table-search", args=[str(self.dataset.identifier)]), data=json.dumps({}), + + r = self.client.post(reverse("public-table-search", args=[str(self.dataset.identifier)]), data=json.dumps({}), + content_type="application/json") + self.assertEqual(r.status_code, status.HTTP_400_BAD_REQUEST) + + r = self.client.post(reverse("private-table-search", args=[str(self.dataset.identifier)]), data=json.dumps({}), content_type="application/json") self.assertEqual(r.status_code, status.HTTP_400_BAD_REQUEST) def test_private_table_search_3(self): # Bad syntax for query - r = self.client.post(reverse("table-search", args=[str(self.dataset.identifier)]), data=json.dumps({ + + r = self.client.post(reverse("public-table-search", args=[str(self.dataset.identifier)]), data=json.dumps({ + "query": ["hello", "world"] + }), content_type="application/json") + self.assertEqual(r.status_code, status.HTTP_400_BAD_REQUEST) + + r = self.client.post(reverse("private-table-search", args=[str(self.dataset.identifier)]), data=json.dumps({ "query": ["hello", "world"] }), content_type="application/json") self.assertEqual(r.status_code, status.HTTP_400_BAD_REQUEST) def test_private_table_search_4(self): # Valid query with one result - r = self.client.post(reverse("table-search", args=[str(self.dataset.identifier)]), data=json.dumps({ + + r = self.client.post(reverse("public-table-search", args=[str(self.dataset.identifier)]), data=json.dumps({ + "query": TEST_SEARCH_QUERY_1 + }), content_type="application/json") + self.assertEqual(r.status_code, status.HTTP_200_OK) + c = r.json() + self.assertEqual(c, True) + + r = self.client.post(reverse("private-table-search", args=[str(self.dataset.identifier)]), data=json.dumps({ "query": TEST_SEARCH_QUERY_1 }), content_type="application/json") self.assertEqual(r.status_code, status.HTTP_200_OK) diff --git a/chord_metadata_service/chord/views_search.py b/chord_metadata_service/chord/views_search.py index 7d72e2903..515d8f141 100644 --- a/chord_metadata_service/chord/views_search.py +++ b/chord_metadata_service/chord/views_search.py @@ -324,16 +324,9 @@ def fhir_private_search(request): return fhir_search(request, internal_data=True) -# Mounted on /private/, so will get protected anyway; this allows for access from federation service -# TODO: Ugly and misleading permissions -@api_view(["POST"]) -@permission_classes([AllowAny]) -def chord_private_table_search(request, table_id): - # Search phenopacket data types in specific tables - # Private search endpoints are protected by URL namespace, not by Django permissions. - +def chord_table_search(request, table_id, internal=False): start = datetime.now() - debug_log("Started private table search") + debug_log(f"Started {'private' if internal else 'public'} table search") if request.data is None or "query" not in request.data: # TODO: Better error @@ -357,7 +350,27 @@ def chord_private_table_search(request, table_id): debug_log(f"Finished running query in {datetime.now() - start}") - serialized_data = PhenopacketSerializer(query_results, many=True).data - debug_log(f"Finished running query and serializing in {datetime.now() - start}") + if internal: + serialized_data = PhenopacketSerializer(query_results, many=True).data + debug_log(f"Finished running query and serializing in {datetime.now() - start}") + + return Response(build_search_response(serialized_data, start)) + + return Response(len(query_results) > 0) + - return Response(build_search_response(serialized_data, start)) +@api_view(["POST"]) +@permission_classes([AllowAny]) +def chord_public_table_search(request, table_id): + # Search phenopacket data types in specific tables without leaking internal data + return chord_table_search(request, table_id, internal=False) + + +# Mounted on /private/, so will get protected anyway; this allows for access from federation service +# TODO: Ugly and misleading permissions +@api_view(["POST"]) +@permission_classes([AllowAny]) +def chord_private_table_search(request, table_id): + # Search phenopacket data types in specific tables + # Private search endpoints are protected by URL namespace, not by Django permissions. + return chord_table_search(request, table_id, internal=True) diff --git a/chord_metadata_service/metadata/urls.py b/chord_metadata_service/metadata/urls.py index 441a7af40..6fead63db 100644 --- a/chord_metadata_service/metadata/urls.py +++ b/chord_metadata_service/metadata/urls.py @@ -50,9 +50,10 @@ path('tables', views_search.table_list, name="table-list"), path('tables/', views_search.table_detail, name="table-detail"), path('tables//summary', views_search.chord_table_summary, name="table-summary"), + path('tables//search', views_search.chord_public_table_search, name="public-table-search"), path('search', views_search.chord_search, name="search"), path('fhir-search', views_search.fhir_public_search, name="fhir-search"), path('private/fhir-search', views_search.fhir_private_search, name="fhir-private-search"), path('private/search', views_search.chord_private_search, name="private-search"), - path('private/tables//search', views_search.chord_private_table_search, name="table-search"), + path('private/tables//search', views_search.chord_private_table_search, name="private-table-search"), ] + ([path('admin/', admin.site.urls)] if DEBUG else []) From 2e330b37f9fc02aceeb93b965f2d3fad3ccd2045 Mon Sep 17 00:00:00 2001 From: zxenia Date: Fri, 6 Mar 2020 14:25:57 -0500 Subject: [PATCH 16/95] add mcode app --- chord_metadata_service/mcode/__init__.py | 0 chord_metadata_service/mcode/admin.py | 3 + chord_metadata_service/mcode/apps.py | 5 + .../mcode/migrations/__init__.py | 0 chord_metadata_service/mcode/models.py | 104 ++++++++++++++++++ chord_metadata_service/mcode/tests.py | 3 + chord_metadata_service/mcode/views.py | 3 + 7 files changed, 118 insertions(+) create mode 100644 chord_metadata_service/mcode/__init__.py create mode 100644 chord_metadata_service/mcode/admin.py create mode 100644 chord_metadata_service/mcode/apps.py create mode 100644 chord_metadata_service/mcode/migrations/__init__.py create mode 100644 chord_metadata_service/mcode/models.py create mode 100644 chord_metadata_service/mcode/tests.py create mode 100644 chord_metadata_service/mcode/views.py diff --git a/chord_metadata_service/mcode/__init__.py b/chord_metadata_service/mcode/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/chord_metadata_service/mcode/admin.py b/chord_metadata_service/mcode/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/chord_metadata_service/mcode/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/chord_metadata_service/mcode/apps.py b/chord_metadata_service/mcode/apps.py new file mode 100644 index 000000000..20b703e4a --- /dev/null +++ b/chord_metadata_service/mcode/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class McodeConfig(AppConfig): + name = 'mcode' diff --git a/chord_metadata_service/mcode/migrations/__init__.py b/chord_metadata_service/mcode/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py new file mode 100644 index 000000000..3d732d4d0 --- /dev/null +++ b/chord_metadata_service/mcode/models.py @@ -0,0 +1,104 @@ +from django.db import models +from django.contrib.postgres.fields import JSONField, ArrayField +from chord_metadata_service.restapi.models import IndexableMixin +from chord_metadata_service.phenopackets.models import Gene +from django.core.exceptions import ValidationError + + +################################# Genomics ################################# + +class GeneticVariantTested(models.Model, IndexableMixin): + """ + The class records an alteration in the most common DNA nucleotide sequence. + """ + + # TODO Discuss: Connection to Gene from Phenopackets + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the genetic variant tested.') + gene_studied = models.ForeignKey(Gene, blank=True, null=True, + help_text='A gene targeted for mutation analysis, ' + 'identified in HUGO Gene Nomenclature Committee (HGNC) notation. ') + method = JSONField(blank=True, null=True, help_text='An ontology or controlled vocabulary term to indetify ' + 'the method used to perform the genetic test. ' + 'Accepted value set: NCIT') + variant_tested_identifier = JSONField(blank=True, null=True, + help_text='The variation ID assigned by HGVS, for example, ' + '360448 is the identifier for NM_005228.4(EGFR):c.-237A>G ' + '(single nucleotide variant in EGFR).') + variant_tested_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, + help_text='Symbolic representation of the variant used in HGVS, for example, ' + 'NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.') + variant_tested_description = models.CharField(max_length=200, blank=True, + help_text='Description of the variant.') + data_value = JSONField(blank=True, null=True, + help_text='An ontology or controlled vocabulary term to indetify ' + 'positive or negative value for the mutation. Accepted value set: SNOMED CT.') + + def __str__(self): + return str(self.id) + + def clean(self): + if not (self.variant_tested_identifier or self.variant_tested_hgvs_name or self.variant_tested_description): + raise ValidationError('At least one element out of the following must be reported: ' + 'Variant Tested Identifier, Variant Tested HGVS Name, and Variant Tested Description') + + +class GeneticVariantFound(models.Model, IndexableMixin): + """ + The class records whether a single discrete variant tested is present + or absent (denoted as positive or negative respectively). + """ + + # TODO Discuss: Connection to Gene from Phenopackets + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the genetic variant found.') + method = JSONField(blank=True, null=True, help_text='An ontology or controlled vocabulary term to indetify ' + 'the method used to perform the genetic test. ' + 'Accepted value set: NCIT') + + variant_found_identifier = JSONField(blank=True, null=True, + help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier ' + 'for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). ' + 'Accepted value set: ClinVar.') + variant_found_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, + help_text='Symbolic representation of the variant used in HGVS, ' + 'for example, NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.') + variant_found_description = models.CharField(max_length=200, blank=True, + help_text='Description of the variant.') + genomic_source_class = JSONField(blank=True, null=True, + help_text='An ontology or controlled vocabulary term to indetify ' + 'the genomic class of the specimen being analyzed.') + + def __str__(self): + return str(self.id) + + def clean(self): + if not (self.variant_found_identifier or self.variant_found_hgvs_name or self.variant_found_description): + raise ValidationError('At least one element out of the following must be reported: ' + 'Variant Found Identifier, Variant Found HGVS Name, and Variant Found Description') + + +class GenomicsReport(models.Model, IndexableMixin): + """ + Genetic Analysis Summary + """ + + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the genetics report.') + test_name = JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test.' + 'Accepted value sets: LOINC, GTR') + performing_ogranization_name = models.CharField(max_length=200, blank=True, + help_text='The name of the organization ' + 'producing the genomics report.') + specimen_type = JSONField(blank=True, null=True, + help_text='An ontology or controlled vocabulary term to indetify the type of ' + 'material the specimen contains or consists of.' + 'Accepted value set: HL7 Version 2 and Specimen Type.') + genetic_variant_tested = models.ManyToManyField(GeneticVariantTested, blank=True, null=True, + help_text='A test for a specific mutation on a particular gene.') + genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, null=True, + help_text='Records an alteration in the most common DNA ' + 'nucleotide sequence.') + + def __str__(self): + return str(self.id) diff --git a/chord_metadata_service/mcode/tests.py b/chord_metadata_service/mcode/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/chord_metadata_service/mcode/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/chord_metadata_service/mcode/views.py b/chord_metadata_service/mcode/views.py new file mode 100644 index 000000000..91ea44a21 --- /dev/null +++ b/chord_metadata_service/mcode/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 4253436b0dc43e664278150b8bd8c9e79fbc64fc Mon Sep 17 00:00:00 2001 From: zxenia Date: Fri, 6 Mar 2020 14:29:17 -0500 Subject: [PATCH 17/95] update setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 51674a9f1..337c2d854 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ "rdflib-jsonld==0.4.0", "requests>=2.23,<3.0", "uritemplate>=3.0,<4.0", + "django-rest-swagger==2.2.0", ], author=config["package"]["authors"], From 979334cb4f1ac6b0702a617c26ad1a2d19b64dc2 Mon Sep 17 00:00:00 2001 From: zxenia Date: Fri, 6 Mar 2020 17:04:25 -0500 Subject: [PATCH 18/95] add the rest of mcode classes --- chord_metadata_service/mcode/models.py | 221 +++++++++++++++++++++++-- 1 file changed, 206 insertions(+), 15 deletions(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 3d732d4d0..67a7d25cf 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -2,34 +2,37 @@ from django.contrib.postgres.fields import JSONField, ArrayField from chord_metadata_service.restapi.models import IndexableMixin from chord_metadata_service.phenopackets.models import Gene +from chord_metadata_service.patients.models import Individual from django.core.exceptions import ValidationError +#TODO MOVE all help_text in sep doc ################################# Genomics ################################# + class GeneticVariantTested(models.Model, IndexableMixin): """ - The class records an alteration in the most common DNA nucleotide sequence. + Class to record an alteration in the most common DNA nucleotide sequence. """ # TODO Discuss: Connection to Gene from Phenopackets id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the genetic variant tested.') - gene_studied = models.ForeignKey(Gene, blank=True, null=True, + gene_studied = models.ForeignKey(Gene, blank=True, null=True, on_delete=models.SET_NULL, help_text='A gene targeted for mutation analysis, ' - 'identified in HUGO Gene Nomenclature Committee (HGNC) notation. ') + 'identified in HUGO Gene Nomenclature Committee (HGNC) notation.') method = JSONField(blank=True, null=True, help_text='An ontology or controlled vocabulary term to indetify ' 'the method used to perform the genetic test. ' 'Accepted value set: NCIT') variant_tested_identifier = JSONField(blank=True, null=True, - help_text='The variation ID assigned by HGVS, for example, ' - '360448 is the identifier for NM_005228.4(EGFR):c.-237A>G ' - '(single nucleotide variant in EGFR).') + help_text='The variation ID assigned by HGVS, for example, ' + '360448 is the identifier for NM_005228.4(EGFR):c.-237A>G ' + '(single nucleotide variant in EGFR).') variant_tested_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, - help_text='Symbolic representation of the variant used in HGVS, for example, ' - 'NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.') + help_text='Symbolic representation of the variant used in HGVS, for example, ' + 'NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.') variant_tested_description = models.CharField(max_length=200, blank=True, - help_text='Description of the variant.') + help_text='Description of the variant.') data_value = JSONField(blank=True, null=True, help_text='An ontology or controlled vocabulary term to indetify ' 'positive or negative value for the mutation. Accepted value set: SNOMED CT.') @@ -45,7 +48,7 @@ def clean(self): class GeneticVariantFound(models.Model, IndexableMixin): """ - The class records whether a single discrete variant tested is present + Class to record whether a single discrete variant tested is present or absent (denoted as positive or negative respectively). """ @@ -57,12 +60,12 @@ class GeneticVariantFound(models.Model, IndexableMixin): 'Accepted value set: NCIT') variant_found_identifier = JSONField(blank=True, null=True, - help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier ' - 'for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). ' - 'Accepted value set: ClinVar.') + help_text='The variation ID assigned by HGVS, for example, 360448 is the' + ' identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide ' + 'variant in EGFR). Accepted value set: ClinVar.') variant_found_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, - help_text='Symbolic representation of the variant used in HGVS, ' - 'for example, NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.') + help_text='Symbolic representation of the variant used in HGVS, for example, ' + 'NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.') variant_found_description = models.CharField(max_length=200, blank=True, help_text='Description of the variant.') genomic_source_class = JSONField(blank=True, null=True, @@ -102,3 +105,191 @@ class GenomicsReport(models.Model, IndexableMixin): def __str__(self): return str(self.id) + + +################################# Labs/Vital ################################# + + +class LabsVital(models.Model, IndexableMixin): + """ + Class to record tests performed on patient. + """ + + # TODO the data value should be in form of Quantity datatype - ADD json schema for Quantity + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the labs/vital tests.') + individual = models.ForeignKey(Individual, on_delete=models.CASCADE, + help_text='The individual who is the subject of the tests.') + body_height = JSONField(help_text='The patient\'s height.') + body_weight = JSONField(help_text='The patient\'s weight.') + cbc_with_auto_differential_panel = ArrayField(models.CharField(max_length=200), blank=True, null=True, + help_text='Reference to a laboratory observation in the CBC with' + ' Auto Differential Panel test. ') + comprehensive_metabolic_2000 = ArrayField(models.CharField(max_length=200), blank=True, null=True, + help_text='Reference to a laboratory observation in the CMP 2000 test.') + blood_pressure_diastolic = JSONField(blank=True, null=True, + help_text='The blood pressure after the contraction of the heart while the ' + 'chambers of the heart refill with blood, when the pressure is lowest.') + blood_pressure_systolic = JSONField(blank=True, null=True, + help_text='The blood pressure during the contraction of the left ' + 'ventricle of the heart, when blood pressure is at its highest.') + #TODO Ontology or Quantity or Ratio (?) + tumor_marker_test = JSONField(help_text='An ontology or controlled vocabulary term to indetify tumor marker test.') + + def __str__(self): + return str(self.id) + + def clean(self): + if not (self.blood_pressure_diastolic or self.blood_pressure_systolic): + raise ValidationError('At least one of the following must be reported: Systolic Blood Pressure or' + 'Diastolic Blood Pressure.') + + +################################# Treatment ################################# + +###### Procedure ###### + +class CancerRelatedProcedure(models.Model, IndexableMixin): + """ + Class to represent radiological treatment or surgical action addressing a cancer condition. + """ + + PROCEDURE_TYPES = ( + ('radiation', 'radiation'), + ('surgical', 'surgical') + ) + #TODO Ontology class + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the procedure.') + procedure_type = models.CharField(choices=PROCEDURE_TYPES, help_text='Type of cancer related procedure: ' + 'radion or surgical procedure.') + #Ontology + code = JSONField(help_text='Code for the procedure performed.') + #DateTime or Ontology + occurence_time_or_period = JSONField(help_text='The date/time that a procedure was performed.') + #List of Ontologies + target_body_site = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, + help_text='The body location(s) where the procedure was performed.') + #Ontology + treatment_intent = JSONField(blank=True, null=True, help_text='The purpose of a treatment.') + + def __str__(self): + return str(self.id) + +###### Medication Statement ###### + +class MedicationStatement(models.Model, IndexableMixin): + """ + Class to record the use of a medication. + """ + + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the medication statement.') + medication_code = JSONField(help_text='A code for medication. Accepted code systems:' + 'Medication Clinical Drug (RxNorm) and other.') + # List of Ontologies + termination_reason = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, + help_text='A code explaining unplanned or premature termination of a course of' + 'medication. Accepted ontologies: SNOMED CT.') + #Ontology + treatment_intent = JSONField(blank=True, null=True, help_text='The purpose of a treatment.' + 'Accepted ontologies: SNOMED CT.') + start_date = models.DateTimeField(blank=True, null=True, help_text='The start date/time of the medication.') + end_date = models.DateTimeField(blank=True, null=True, help_text='The end date/time of the medication.') + date_time = models.DateTimeField(blank=True, null=True, help_text='The date/time the medication was administered.') + + def __str__(self): + return str(self.id) + + +class CancerCondition(models.Model, IndexableMixin): + """ + Class to record the history of primary or secondary cancer conditions. + """ + CANCER_CONDITION_TYPE = ( + ('primary', 'primary'), + ('secondary', 'secondary') + ) + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the cancer condition.') + condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, + help_text='Cancer condition type: primary or secondary.') + body_location_code = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, + help_text='Code for the body location, optionally pre-coordinating laterality ' + 'or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.') + clinical_status = JSONField(blank=True, null=True, + help_text='A flag indicating whether the condition is active ' + 'or inactive, recurring, in remission, or resolved (as of the last update ' + 'of the Condition). Accepted code system: ' + 'http://terminology.hl7.org/CodeSystem/condition-clinical') + condition_code = JSONField(help_text='A code describing the type of primary or secondary malignant ' + 'neoplastic disease.') + date_of_diagnosis = models.DateTimeField(blank=True, null=True, + help_text='The date the disease was first clinically recognized with ' + 'sufficient certainty, regardless of whether it was fully ' + 'characterized at that time.') + histology_morphology_behavior = JSONField(blank=True, null=True, + help_text='A description of the morphologic and behavioral ' + 'characteristics of the cancer. Accepted ontologies:' + 'SNOMED CT, ICD-O-3 and others.') + + def __str__(self): + return str(self.id) + + +class TNMStaging(models.Model, IndexableMixin): + """ + Class to describe the spread of cancer in a patient’s body. + """ + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the TNM staging.') + #TODO Extended Ontology class: stage group - required and staging system - not required + clinical_stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM ' + 'classification system. Accepted ontologies: SNOMED CT, AJCC and others.') + clinical_primary_tumor_category = JSONField(help_text='Category of the primary tumor, based on its size and ' + 'extent, assessed prior to surgery, based on evidence ' + 'such as physical examination, imaging, and/or biopsy.' + 'Accepted ontologies: SNOMED CT, AJCC and others.') + clinical_regional_nodes_category = JSONField(help_text='Category of the presence or absence of metastases in ' + 'regional lymph nodes, assessed using tests that are ' + 'done before surgery. Accepted ontologies: ' + 'SNOMED CT, AJCC and others.') + clinical_distant_metastases_category = JSONField(help_text='Category describing the presence or absence of ' + 'metastases in remote anatomical locations, assessed ' + 'using tests that are done before surgery. ' + 'Accepted ontologies: SNOMED CT, AJCC and others.') + pathologic_stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM ' + 'classification system, based on examination of tissue samples ' + 'removed during surgery, in addition to physical examination and ' + 'imaging and potentially, other prognostic factors. ' + 'Accepted ontologies: SNOMED CT, AJCC and others.') + pathologic_primary_tumor_category = JSONField(help_text='Category describing the primary tumor, based on its ' + 'size and extent, assessed through pathologic analysis ' + 'of a tumor specimen. Accepted ontologies: SNOMED CT, ' + 'AJCC and others.') + pathologic_regional_nodes_category = JSONField(help_text='Category describing the presence or absence of ' + 'metastases in regional lymph nodes, assessed through ' + 'pathologic analysis of a specimen. Accepted ontologies: ' + 'SNOMED CT, AJCC and others.') + pathologic_distant_metastases_category = JSONField(help_text='Category describing the presence or absence of ' + 'metastases in remote anatomical locations, assessed ' + 'through pathologic analysis of a specimen. ' + 'Accepted ontologies: SNOMED CT, AJCC and others.') + + def __str__(self): + return str(self.id) + + + + + + + + + + + + + + + From 7d76d91f104e8b91dce9876291408c62a570b48d Mon Sep 17 00:00:00 2001 From: zxenia Date: Fri, 6 Mar 2020 17:05:53 -0500 Subject: [PATCH 19/95] remove empty lines --- chord_metadata_service/mcode/models.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 67a7d25cf..32cd77ea5 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -278,18 +278,3 @@ class TNMStaging(models.Model, IndexableMixin): def __str__(self): return str(self.id) - - - - - - - - - - - - - - - From 2a1e32328984ddcfbd6b60fc53933d89d8302ca1 Mon Sep 17 00:00:00 2001 From: zxenia Date: Mon, 9 Mar 2020 14:58:14 -0400 Subject: [PATCH 20/95] add relations to mcode model register classes in admin --- chord_metadata_service/mcode/admin.py | 43 +++++++- chord_metadata_service/mcode/models.py | 147 ++++++++++++------------- 2 files changed, 113 insertions(+), 77 deletions(-) diff --git a/chord_metadata_service/mcode/admin.py b/chord_metadata_service/mcode/admin.py index 8c38f3f3d..d103f5229 100644 --- a/chord_metadata_service/mcode/admin.py +++ b/chord_metadata_service/mcode/admin.py @@ -1,3 +1,44 @@ from django.contrib import admin +from .models import * + + +@admin.register(GeneticVariantTested) +class GeneticVariantTestedAdmin(admin.ModelAdmin): + pass + + +@admin.register(GeneticVariantFound) +class GeneticVariantFoundAdmin(admin.ModelAdmin): + pass + + +@admin.register(GenomicsReport) +class GenomicsReportAdmin(admin.ModelAdmin): + pass + + +@admin.register(LabsVital) +class LabsVitalAdmin(admin.ModelAdmin): + pass + + +@admin.register(CancerCondition) +class CancerConditionAdmin(admin.ModelAdmin): + pass + + +@admin.register(TNMStaging) +class TNMStagingAdmin(admin.ModelAdmin): + pass + + +@admin.register(CancerRelatedProcedure) +class CancerRelatedProcedureAdmin(admin.ModelAdmin): + pass + + +@admin.register(MedicationStatement) +class MedicationStatementAdmin(admin.ModelAdmin): + pass + -# Register your models here. diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 32cd77ea5..43bb17445 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -102,6 +102,7 @@ class GenomicsReport(models.Model, IndexableMixin): genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, null=True, help_text='Records an alteration in the most common DNA ' 'nucleotide sequence.') + subject = models.ForeignKey(Individual, help_text='Subject of genomics report.') def __str__(self): return str(self.id) @@ -145,6 +146,74 @@ def clean(self): 'Diastolic Blood Pressure.') +################################# Disease ################################# + +class CancerCondition(models.Model, IndexableMixin): + """ + Class to record the history of primary or secondary cancer conditions. + """ + CANCER_CONDITION_TYPE = ( + ('primary', 'primary'), + ('secondary', 'secondary') + ) + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the cancer condition.') + condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, + help_text='Cancer condition type: primary or secondary.') + body_location_code = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, + help_text='Code for the body location, optionally pre-coordinating laterality ' + 'or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.') + clinical_status = JSONField(blank=True, null=True, + help_text='A flag indicating whether the condition is active ' + 'or inactive, recurring, in remission, or resolved (as of the last update ' + 'of the Condition). Accepted code system: ' + 'http://terminology.hl7.org/CodeSystem/condition-clinical') + condition_code = JSONField(help_text='A code describing the type of primary or secondary malignant ' + 'neoplastic disease.') + date_of_diagnosis = models.DateTimeField(blank=True, null=True, + help_text='The date the disease was first clinically recognized with ' + 'sufficient certainty, regardless of whether it was fully ' + 'characterized at that time.') + histology_morphology_behavior = JSONField(blank=True, null=True, + help_text='A description of the morphologic and behavioral ' + 'characteristics of the cancer. Accepted ontologies:' + 'SNOMED CT, ICD-O-3 and others.') + subject = models.ForeignKey(Individual, help_text='The subject of the study that has a cancer condition.') + + def __str__(self): + return str(self.id) + + +class TNMStaging(models.Model, IndexableMixin): + """ + Class to describe the spread of cancer in a patient’s body. + """ + + TNM_TYPES = ( + ('clinical', 'clinical'), + ('pathologic', 'pathologic') + ) + id = models.CharField(primary_key=True, max_length=200, + help_text='An arbitrary identifier for the TNM staging.') + #TODO Extended Ontology class: stage group - required and staging system - not required + tnm_type = models.CharField(choices=TNM_TYPES, help_text='TNM type: clinical or pathological.') + stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM ' + 'classification system. Accepted ontologies: SNOMED CT, AJCC and others.') + primary_tumor_category = JSONField(help_text='Category of the primary tumor, based on its size and ' + 'extent. Accepted ontologies: SNOMED CT, AJCC and others.') + regional_nodes_category = JSONField(help_text='Category of the presence or absence of metastases in ' + 'regional lymph nodes. Accepted ontologies: ' + 'SNOMED CT, AJCC and others.') + distant_metastases_category = JSONField(help_text='Category describing the presence or absence of ' + 'metastases in remote anatomical locations. ' + 'Accepted ontologies: SNOMED CT, AJCC and others.') + # TODO check if one cancer condition has many TNM Staging + cancer_condition = models.ForeignKey(CancerCondition, help_text='Cancer condition.') + + def __str__(self): + return str(self.id) + + ################################# Treatment ################################# ###### Procedure ###### @@ -172,6 +241,7 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): help_text='The body location(s) where the procedure was performed.') #Ontology treatment_intent = JSONField(blank=True, null=True, help_text='The purpose of a treatment.') + subject = models.ForeignKey(Individual, help_text='The patient who has a cancer condition.') def __str__(self): return str(self.id) @@ -197,84 +267,9 @@ class MedicationStatement(models.Model, IndexableMixin): start_date = models.DateTimeField(blank=True, null=True, help_text='The start date/time of the medication.') end_date = models.DateTimeField(blank=True, null=True, help_text='The end date/time of the medication.') date_time = models.DateTimeField(blank=True, null=True, help_text='The date/time the medication was administered.') + subject = models.ForeignKey(Individual, help_text="Subject of medication statement.") def __str__(self): return str(self.id) -class CancerCondition(models.Model, IndexableMixin): - """ - Class to record the history of primary or secondary cancer conditions. - """ - CANCER_CONDITION_TYPE = ( - ('primary', 'primary'), - ('secondary', 'secondary') - ) - id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the cancer condition.') - condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, - help_text='Cancer condition type: primary or secondary.') - body_location_code = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, - help_text='Code for the body location, optionally pre-coordinating laterality ' - 'or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.') - clinical_status = JSONField(blank=True, null=True, - help_text='A flag indicating whether the condition is active ' - 'or inactive, recurring, in remission, or resolved (as of the last update ' - 'of the Condition). Accepted code system: ' - 'http://terminology.hl7.org/CodeSystem/condition-clinical') - condition_code = JSONField(help_text='A code describing the type of primary or secondary malignant ' - 'neoplastic disease.') - date_of_diagnosis = models.DateTimeField(blank=True, null=True, - help_text='The date the disease was first clinically recognized with ' - 'sufficient certainty, regardless of whether it was fully ' - 'characterized at that time.') - histology_morphology_behavior = JSONField(blank=True, null=True, - help_text='A description of the morphologic and behavioral ' - 'characteristics of the cancer. Accepted ontologies:' - 'SNOMED CT, ICD-O-3 and others.') - - def __str__(self): - return str(self.id) - - -class TNMStaging(models.Model, IndexableMixin): - """ - Class to describe the spread of cancer in a patient’s body. - """ - id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the TNM staging.') - #TODO Extended Ontology class: stage group - required and staging system - not required - clinical_stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM ' - 'classification system. Accepted ontologies: SNOMED CT, AJCC and others.') - clinical_primary_tumor_category = JSONField(help_text='Category of the primary tumor, based on its size and ' - 'extent, assessed prior to surgery, based on evidence ' - 'such as physical examination, imaging, and/or biopsy.' - 'Accepted ontologies: SNOMED CT, AJCC and others.') - clinical_regional_nodes_category = JSONField(help_text='Category of the presence or absence of metastases in ' - 'regional lymph nodes, assessed using tests that are ' - 'done before surgery. Accepted ontologies: ' - 'SNOMED CT, AJCC and others.') - clinical_distant_metastases_category = JSONField(help_text='Category describing the presence or absence of ' - 'metastases in remote anatomical locations, assessed ' - 'using tests that are done before surgery. ' - 'Accepted ontologies: SNOMED CT, AJCC and others.') - pathologic_stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM ' - 'classification system, based on examination of tissue samples ' - 'removed during surgery, in addition to physical examination and ' - 'imaging and potentially, other prognostic factors. ' - 'Accepted ontologies: SNOMED CT, AJCC and others.') - pathologic_primary_tumor_category = JSONField(help_text='Category describing the primary tumor, based on its ' - 'size and extent, assessed through pathologic analysis ' - 'of a tumor specimen. Accepted ontologies: SNOMED CT, ' - 'AJCC and others.') - pathologic_regional_nodes_category = JSONField(help_text='Category describing the presence or absence of ' - 'metastases in regional lymph nodes, assessed through ' - 'pathologic analysis of a specimen. Accepted ontologies: ' - 'SNOMED CT, AJCC and others.') - pathologic_distant_metastases_category = JSONField(help_text='Category describing the presence or absence of ' - 'metastases in remote anatomical locations, assessed ' - 'through pathologic analysis of a specimen. ' - 'Accepted ontologies: SNOMED CT, AJCC and others.') - - def __str__(self): - return str(self.id) From e6f366f999c58378f550c8036859f733f1b73854 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 9 Mar 2020 15:18:59 -0400 Subject: [PATCH 21/95] Update dependencies --- requirements.txt | 10 +++++----- setup.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 43b9e28fb..62d6413df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,13 +3,13 @@ attrs==19.3.0 Babel==2.8.0 certifi==2019.11.28 chardet==3.0.4 -chord-lib==0.5.0 +chord-lib==0.6.0 codecov==2.0.16 colorama==0.4.3 coreapi==2.3.3 coreschema==0.0.4 coverage==5.0.3 -Django==2.2.10 +Django==2.2.11 django-filter==2.2.0 django-nose==1.4.6 django-rest-swagger==2.2.0 @@ -30,9 +30,9 @@ MarkupSafe==1.1.1 more-itertools==8.2.0 nose==1.3.7 openapi-codec==1.3.2 -packaging==20.1 +packaging==20.3 psycopg2-binary==2.8.4 -Pygments==2.5.2 +Pygments==2.6.1 pyparsing==2.4.6 pyrsistent==0.15.7 python-dateutil==2.8.1 @@ -45,7 +45,7 @@ requests==2.23.0 simplejson==3.17.0 six==1.14.0 snowballstemmer==2.0.0 -Sphinx==2.4.3 +Sphinx==2.4.4 sphinx-rtd-theme==0.4.3 sphinxcontrib-applehelp==1.0.2 sphinxcontrib-devhelp==1.0.2 diff --git a/setup.py b/setup.py index 337c2d854..bc58b9810 100644 --- a/setup.py +++ b/setup.py @@ -16,12 +16,13 @@ python_requires=">=3.6", install_requires=[ - "chord_lib[django]==0.5.0", + "chord_lib[django]==0.6.0", "Django>=2.2,<3.0", "django-filter>=2.2,<3.0", "django-nose>=1.4,<2.0", "djangorestframework>=3.10,<3.11", "djangorestframework-camel-case>=1.1,<2.0", + "django-rest-swagger==2.2.0", "elasticsearch==7.1.0", "fhirclient>=3.2,<4.0", "jsonschema>=3.2,<4.0", @@ -32,7 +33,6 @@ "rdflib-jsonld==0.4.0", "requests>=2.23,<3.0", "uritemplate>=3.0,<4.0", - "django-rest-swagger==2.2.0", ], author=config["package"]["authors"], From aa93abc8909a8020fddb468cb8b394150e2e2d3c Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 9 Mar 2020 15:31:38 -0400 Subject: [PATCH 22/95] Include table ownerships in dataset serializer --- chord_metadata_service/chord/api_views.py | 5 +++-- chord_metadata_service/chord/models.py | 1 + chord_metadata_service/chord/serializers.py | 15 +++++++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/chord_metadata_service/chord/api_views.py b/chord_metadata_service/chord/api_views.py index 613d5358c..d8e914efd 100644 --- a/chord_metadata_service/chord/api_views.py +++ b/chord_metadata_service/chord/api_views.py @@ -1,11 +1,12 @@ from rest_framework import viewsets from rest_framework.permissions import BasePermission, SAFE_METHODS +from rest_framework.settings import api_settings + +from chord_metadata_service.restapi.api_renderers import PhenopacketsRenderer, JSONLDDatasetRenderer, RDFDatasetRenderer from chord_metadata_service.restapi.pagination import LargeResultsSetPagination from .models import * from .permissions import OverrideOrSuperUserOnly from .serializers import * -from chord_metadata_service.restapi.api_renderers import PhenopacketsRenderer, JSONLDDatasetRenderer, RDFDatasetRenderer -from rest_framework.settings import api_settings __all__ = ["ProjectViewSet", "DatasetViewSet", "TableOwnershipViewSet"] diff --git a/chord_metadata_service/chord/models.py b/chord_metadata_service/chord/models.py index 5feda969e..0dbd3a26d 100644 --- a/chord_metadata_service/chord/models.py +++ b/chord_metadata_service/chord/models.py @@ -11,6 +11,7 @@ def version_default(): return f"version_{timezone.now()}" + ############################################################# # # # Project Management # diff --git a/chord_metadata_service/chord/serializers.py b/chord_metadata_service/chord/serializers.py index a315c6b92..73c3a3c13 100644 --- a/chord_metadata_service/chord/serializers.py +++ b/chord_metadata_service/chord/serializers.py @@ -19,13 +19,22 @@ ############################################################# +class TableOwnershipSerializer(GenericSerializer): + class Meta: + model = TableOwnership + fields = '__all__' + + class DatasetSerializer(GenericSerializer): always_include = ( "description", "contact_info", "linked_field_sets", + "table_ownerships", ) + table_ownerships = TableOwnershipSerializer(read_only=True, many=True, exclude_when_nested=["dataset"]) + # noinspection PyMethodMayBeStatic def validate_title(self, value): if len(value.strip()) < 3: @@ -140,9 +149,3 @@ def validate_title(self, value): class Meta: model = Project fields = '__all__' - - -class TableOwnershipSerializer(GenericSerializer): - class Meta: - model = TableOwnership - fields = '__all__' From 211bc04914e5e229f628907d6e173db813c09793 Mon Sep 17 00:00:00 2001 From: zxenia Date: Mon, 9 Mar 2020 15:37:32 -0400 Subject: [PATCH 23/95] add serializers, urls, api views make migrations --- chord_metadata_service/mcode/api_views.py | 53 ++++++++ chord_metadata_service/mcode/apps.py | 2 +- .../mcode/migrations/0001_initial.py | 127 ++++++++++++++++++ chord_metadata_service/mcode/models.py | 28 ++-- chord_metadata_service/mcode/serializers.py | 52 +++++++ chord_metadata_service/metadata/settings.py | 1 + chord_metadata_service/restapi/urls.py | 11 ++ 7 files changed, 261 insertions(+), 13 deletions(-) create mode 100644 chord_metadata_service/mcode/api_views.py create mode 100644 chord_metadata_service/mcode/migrations/0001_initial.py create mode 100644 chord_metadata_service/mcode/serializers.py diff --git a/chord_metadata_service/mcode/api_views.py b/chord_metadata_service/mcode/api_views.py new file mode 100644 index 000000000..bac1e92e7 --- /dev/null +++ b/chord_metadata_service/mcode/api_views.py @@ -0,0 +1,53 @@ +from rest_framework import viewsets +from rest_framework.settings import api_settings +from .serializers import * +from chord_metadata_service.restapi.api_renderers import ( + PhenopacketsRenderer +) +from chord_metadata_service.restapi.pagination import LargeResultsSetPagination + + +class McodeModelViewSet(viewsets.ModelViewSet): + pagination_class = LargeResultsSetPagination + renderer_classes = (*api_settings.DEFAULT_RENDERER_CLASSES, PhenopacketsRenderer) + + +class GeneticVariantTestedViewSet(McodeModelViewSet): + queryset = GeneticVariantTested.objects.all().order_by("id") + serializer_class = GeneticVariantTestedSerializer + + +class GeneticVariantFoundViewSet(McodeModelViewSet): + queryset = GeneticVariantFound.objects.all().order_by("id") + serializer_class = GeneticVariantFoundSerializer + + +class GenomicsReportViewSet(McodeModelViewSet): + queryset = GenomicsReport.objects.all().order_by("id") + serializer_class = GenomicsReportSerializer + + +class LabsVitalViewSet(McodeModelViewSet): + queryset = LabsVital.objects.all().order_by("id") + serializer_class = LabsVitalSerializer + + +class CancerConditionViewSet(McodeModelViewSet): + queryset = CancerCondition.objects.all().order_by("id") + serializer_class = CancerConditionSerializer + + +class TNMStagingViewSet(McodeModelViewSet): + queryset = TNMStaging.objects.all().order_by("id") + serializer_class = TNMStagingSerializer + + +class CancerRelatedProcedureViewSet(McodeModelViewSet): + queryset = CancerRelatedProcedure.objects.all().order_by("id") + serializer_class = CancerRelatedProcedureSerializer + + +class MedicationStatementViewSet(McodeModelViewSet): + queryset = MedicationStatement.objects.all().order_by("id") + serializer_class = MedicationStatementSerializer + diff --git a/chord_metadata_service/mcode/apps.py b/chord_metadata_service/mcode/apps.py index 20b703e4a..0c3bb040c 100644 --- a/chord_metadata_service/mcode/apps.py +++ b/chord_metadata_service/mcode/apps.py @@ -2,4 +2,4 @@ class McodeConfig(AppConfig): - name = 'mcode' + name = 'chord_metadata_service.mcode' diff --git a/chord_metadata_service/mcode/migrations/0001_initial.py b/chord_metadata_service/mcode/migrations/0001_initial.py new file mode 100644 index 000000000..912a4d296 --- /dev/null +++ b/chord_metadata_service/mcode/migrations/0001_initial.py @@ -0,0 +1,127 @@ +# Generated by Django 2.2.10 on 2020-03-09 19:32 + +import chord_metadata_service.restapi.models +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('patients', '0004_auto_20200129_1537'), + ('phenopackets', '0004_auto_20200129_1537'), + ] + + operations = [ + migrations.CreateModel( + name='CancerCondition', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the cancer condition.', max_length=200, primary_key=True, serialize=False)), + ('condition_type', models.CharField(choices=[('primary', 'primary'), ('secondary', 'secondary')], help_text='Cancer condition type: primary or secondary.', max_length=200)), + ('body_location_code', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), blank=True, help_text='Code for the body location, optionally pre-coordinating laterality or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True, size=None)), + ('clinical_status', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A flag indicating whether the condition is active or inactive, recurring, in remission, or resolved (as of the last update of the Condition). Accepted code system: http://terminology.hl7.org/CodeSystem/condition-clinical', null=True)), + ('condition_code', django.contrib.postgres.fields.jsonb.JSONField(help_text='A code describing the type of primary or secondary malignant neoplastic disease.')), + ('date_of_diagnosis', models.DateTimeField(blank=True, help_text='The date the disease was first clinically recognized with sufficient certainty, regardless of whether it was fully characterized at that time.', null=True)), + ('histology_morphology_behavior', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A description of the morphologic and behavioral characteristics of the cancer. Accepted ontologies:SNOMED CT, ICD-O-3 and others.', null=True)), + ('subject', models.ForeignKey(help_text='The subject of the study that has a cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + migrations.CreateModel( + name='GeneticVariantFound', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the genetic variant found.', max_length=200, primary_key=True, serialize=False)), + ('method', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify the method used to perform the genetic test. Accepted value set: NCIT', null=True)), + ('variant_found_identifier', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). Accepted value set: ClinVar.', null=True)), + ('variant_found_hgvs_name', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Symbolic representation of the variant used in HGVS, for example, NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.', null=True, size=None)), + ('variant_found_description', models.CharField(blank=True, help_text='Description of the variant.', max_length=200)), + ('genomic_source_class', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify the genomic class of the specimen being analyzed.', null=True)), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + migrations.CreateModel( + name='GeneticVariantTested', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the genetic variant tested.', max_length=200, primary_key=True, serialize=False)), + ('method', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify the method used to perform the genetic test. Accepted value set: NCIT', null=True)), + ('variant_tested_identifier', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR).', null=True)), + ('variant_tested_hgvs_name', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Symbolic representation of the variant used in HGVS, for example, NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.', null=True, size=None)), + ('variant_tested_description', models.CharField(blank=True, help_text='Description of the variant.', max_length=200)), + ('data_value', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify positive or negative value for the mutation. Accepted value set: SNOMED CT.', null=True)), + ('gene_studied', models.ForeignKey(blank=True, help_text='A gene targeted for mutation analysis, identified in HUGO Gene Nomenclature Committee (HGNC) notation.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='phenopackets.Gene')), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + migrations.CreateModel( + name='TNMStaging', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the TNM staging.', max_length=200, primary_key=True, serialize=False)), + ('tnm_type', models.CharField(choices=[('clinical', 'clinical'), ('pathologic', 'pathologic')], help_text='TNM type: clinical or pathological.', max_length=200)), + ('stage_group', django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system. Accepted ontologies: SNOMED CT, AJCC and others.')), + ('primary_tumor_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the primary tumor, based on its size and extent. Accepted ontologies: SNOMED CT, AJCC and others.')), + ('regional_nodes_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the presence or absence of metastases in regional lymph nodes. Accepted ontologies: SNOMED CT, AJCC and others.')), + ('distant_metastases_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category describing the presence or absence of metastases in remote anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.')), + ('cancer_condition', models.ForeignKey(help_text='Cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='mcode.CancerCondition')), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + migrations.CreateModel( + name='MedicationStatement', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the medication statement.', max_length=200, primary_key=True, serialize=False)), + ('medication_code', django.contrib.postgres.fields.jsonb.JSONField(help_text='A code for medication. Accepted code systems:Medication Clinical Drug (RxNorm) and other.')), + ('termination_reason', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), blank=True, help_text='A code explaining unplanned or premature termination of a course ofmedication. Accepted ontologies: SNOMED CT.', null=True, size=None)), + ('treatment_intent', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment.Accepted ontologies: SNOMED CT.', null=True)), + ('start_date', models.DateTimeField(blank=True, help_text='The start date/time of the medication.', null=True)), + ('end_date', models.DateTimeField(blank=True, help_text='The end date/time of the medication.', null=True)), + ('date_time', models.DateTimeField(blank=True, help_text='The date/time the medication was administered.', null=True)), + ('subject', models.ForeignKey(help_text='Subject of medication statement.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + migrations.CreateModel( + name='LabsVital', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the labs/vital tests.', max_length=200, primary_key=True, serialize=False)), + ('body_height', django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's height.")), + ('body_weight', django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's weight.")), + ('cbc_with_auto_differential_panel', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Reference to a laboratory observation in the CBC with Auto Differential Panel test. ', null=True, size=None)), + ('comprehensive_metabolic_2000', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Reference to a laboratory observation in the CMP 2000 test.', null=True, size=None)), + ('blood_pressure_diastolic', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure after the contraction of the heart while the chambers of the heart refill with blood, when the pressure is lowest.', null=True)), + ('blood_pressure_systolic', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure during the contraction of the left ventricle of the heart, when blood pressure is at its highest.', null=True)), + ('tumor_marker_test', django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to indetify tumor marker test.')), + ('individual', models.ForeignKey(help_text='The individual who is the subject of the tests.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + migrations.CreateModel( + name='GenomicsReport', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the genetics report.', max_length=200, primary_key=True, serialize=False)), + ('test_name', django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test.Accepted value sets: LOINC, GTR')), + ('performing_ogranization_name', models.CharField(blank=True, help_text='The name of the organization producing the genomics report.', max_length=200)), + ('specimen_type', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify the type of material the specimen contains or consists of.Accepted value set: HL7 Version 2 and Specimen Type.', null=True)), + ('genetic_variant_found', models.ManyToManyField(blank=True, help_text='Records an alteration in the most common DNA nucleotide sequence.', to='mcode.GeneticVariantFound')), + ('genetic_variant_tested', models.ManyToManyField(blank=True, help_text='A test for a specific mutation on a particular gene.', to='mcode.GeneticVariantTested')), + ('subject', models.ForeignKey(help_text='Subject of genomics report.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + migrations.CreateModel( + name='CancerRelatedProcedure', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the procedure.', max_length=200, primary_key=True, serialize=False)), + ('procedure_type', models.CharField(choices=[('radiation', 'radiation'), ('surgical', 'surgical')], help_text='Type of cancer related procedure: radion or surgical procedure.', max_length=200)), + ('code', django.contrib.postgres.fields.jsonb.JSONField(help_text='Code for the procedure performed.')), + ('occurence_time_or_period', django.contrib.postgres.fields.jsonb.JSONField(help_text='The date/time that a procedure was performed.')), + ('target_body_site', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), blank=True, help_text='The body location(s) where the procedure was performed.', null=True, size=None)), + ('treatment_intent', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment.', null=True)), + ('subject', models.ForeignKey(help_text='The patient who has a cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + ] diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 43bb17445..34a7fd462 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -97,12 +97,12 @@ class GenomicsReport(models.Model, IndexableMixin): help_text='An ontology or controlled vocabulary term to indetify the type of ' 'material the specimen contains or consists of.' 'Accepted value set: HL7 Version 2 and Specimen Type.') - genetic_variant_tested = models.ManyToManyField(GeneticVariantTested, blank=True, null=True, + genetic_variant_tested = models.ManyToManyField(GeneticVariantTested, blank=True, help_text='A test for a specific mutation on a particular gene.') - genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, null=True, + genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, help_text='Records an alteration in the most common DNA ' 'nucleotide sequence.') - subject = models.ForeignKey(Individual, help_text='Subject of genomics report.') + subject = models.ForeignKey(Individual, help_text='Subject of genomics report.', on_delete=models.CASCADE) def __str__(self): return str(self.id) @@ -115,7 +115,7 @@ class LabsVital(models.Model, IndexableMixin): """ Class to record tests performed on patient. """ - + # TODO Should this class be a part of Patients app? patient related metadata # TODO the data value should be in form of Quantity datatype - ADD json schema for Quantity id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the labs/vital tests.') @@ -158,7 +158,7 @@ class CancerCondition(models.Model, IndexableMixin): ) id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the cancer condition.') - condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, + condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, max_length=200, help_text='Cancer condition type: primary or secondary.') body_location_code = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, help_text='Code for the body location, optionally pre-coordinating laterality ' @@ -178,7 +178,8 @@ class CancerCondition(models.Model, IndexableMixin): help_text='A description of the morphologic and behavioral ' 'characteristics of the cancer. Accepted ontologies:' 'SNOMED CT, ICD-O-3 and others.') - subject = models.ForeignKey(Individual, help_text='The subject of the study that has a cancer condition.') + subject = models.ForeignKey(Individual, help_text='The subject of the study that has a cancer condition.', + on_delete=models.CASCADE) def __str__(self): return str(self.id) @@ -196,7 +197,8 @@ class TNMStaging(models.Model, IndexableMixin): id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the TNM staging.') #TODO Extended Ontology class: stage group - required and staging system - not required - tnm_type = models.CharField(choices=TNM_TYPES, help_text='TNM type: clinical or pathological.') + tnm_type = models.CharField(choices=TNM_TYPES, max_length=200, + help_text='TNM type: clinical or pathological.') stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM ' 'classification system. Accepted ontologies: SNOMED CT, AJCC and others.') primary_tumor_category = JSONField(help_text='Category of the primary tumor, based on its size and ' @@ -208,7 +210,7 @@ class TNMStaging(models.Model, IndexableMixin): 'metastases in remote anatomical locations. ' 'Accepted ontologies: SNOMED CT, AJCC and others.') # TODO check if one cancer condition has many TNM Staging - cancer_condition = models.ForeignKey(CancerCondition, help_text='Cancer condition.') + cancer_condition = models.ForeignKey(CancerCondition, help_text='Cancer condition.', on_delete=models.CASCADE) def __str__(self): return str(self.id) @@ -230,8 +232,8 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): #TODO Ontology class id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the procedure.') - procedure_type = models.CharField(choices=PROCEDURE_TYPES, help_text='Type of cancer related procedure: ' - 'radion or surgical procedure.') + procedure_type = models.CharField(choices=PROCEDURE_TYPES, max_length=200, + help_text='Type of cancer related procedure: radion or surgical procedure.') #Ontology code = JSONField(help_text='Code for the procedure performed.') #DateTime or Ontology @@ -241,7 +243,8 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): help_text='The body location(s) where the procedure was performed.') #Ontology treatment_intent = JSONField(blank=True, null=True, help_text='The purpose of a treatment.') - subject = models.ForeignKey(Individual, help_text='The patient who has a cancer condition.') + subject = models.ForeignKey(Individual, help_text='The patient who has a cancer condition.', + on_delete=models.CASCADE) def __str__(self): return str(self.id) @@ -267,7 +270,8 @@ class MedicationStatement(models.Model, IndexableMixin): start_date = models.DateTimeField(blank=True, null=True, help_text='The start date/time of the medication.') end_date = models.DateTimeField(blank=True, null=True, help_text='The end date/time of the medication.') date_time = models.DateTimeField(blank=True, null=True, help_text='The date/time the medication was administered.') - subject = models.ForeignKey(Individual, help_text="Subject of medication statement.") + subject = models.ForeignKey(Individual, help_text='Subject of medication statement.', + on_delete=models.CASCADE) def __str__(self): return str(self.id) diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py new file mode 100644 index 000000000..640f41b03 --- /dev/null +++ b/chord_metadata_service/mcode/serializers.py @@ -0,0 +1,52 @@ +from rest_framework import serializers +from chord_metadata_service.restapi.serializers import GenericSerializer +from .models import * + + +class GeneticVariantTestedSerializer(GenericSerializer): + + class Meta: + model = GeneticVariantTested + fields = '__all__' + + +class GeneticVariantFoundSerializer(GenericSerializer): + class Meta: + model = GeneticVariantFound + fields = '__all__' + + +class GenomicsReportSerializer(GenericSerializer): + class Meta: + model = GenomicsReport + fields = '__all__' + + +class LabsVitalSerializer(GenericSerializer): + class Meta: + model = LabsVital + fields = '__all__' + + +class CancerConditionSerializer(GenericSerializer): + class Meta: + model = CancerCondition + fields = '__all__' + + +class TNMStagingSerializer(GenericSerializer): + class Meta: + model = TNMStaging + fields = '__all__' + + +class CancerRelatedProcedureSerializer(GenericSerializer): + class Meta: + model = CancerRelatedProcedure + fields = '__all__' + + +class MedicationStatementSerializer(GenericSerializer): + class Meta: + model = MedicationStatement + fields = '__all__' \ No newline at end of file diff --git a/chord_metadata_service/metadata/settings.py b/chord_metadata_service/metadata/settings.py index 36bfdc0d0..083b07ca5 100644 --- a/chord_metadata_service/metadata/settings.py +++ b/chord_metadata_service/metadata/settings.py @@ -64,6 +64,7 @@ 'chord_metadata_service.chord', 'chord_metadata_service.patients.apps.PatientsConfig', 'chord_metadata_service.phenopackets.apps.PhenopacketsConfig', + 'chord_metadata_service.mcode.apps.McodeConfig', 'chord_metadata_service.restapi', 'rest_framework', diff --git a/chord_metadata_service/restapi/urls.py b/chord_metadata_service/restapi/urls.py index 75b4c3797..290827db3 100644 --- a/chord_metadata_service/restapi/urls.py +++ b/chord_metadata_service/restapi/urls.py @@ -3,6 +3,7 @@ from chord_metadata_service.chord import api_views as chord_views from chord_metadata_service.patients import api_views as individual_views from chord_metadata_service.phenopackets import api_views as phenopacket_views +from chord_metadata_service.mcode import api_views as mcode_views # from .settings import DEBUG @@ -31,6 +32,16 @@ router.register(r'datasets', chord_views.DatasetViewSet) router.register(r'table_ownership', chord_views.TableOwnershipViewSet) +# mCode app urls +router.register(r'geneticvariantstested', mcode_views.GeneticVariantTestedViewSet) +router.register(r'geneticvariantsfound', mcode_views.GeneticVariantFoundViewSet) +router.register(r'genomicsreports', mcode_views.GenomicsReportViewSet) +router.register(r'labsvital', mcode_views.LabsVitalViewSet) +router.register(r'cancerconditions', mcode_views.CancerConditionViewSet) +router.register(r'tnmstaging', mcode_views.TNMStagingViewSet) +router.register(r'cancerrelatedprocedures', mcode_views.CancerRelatedProcedureViewSet) +router.register(r'medicationstatements', mcode_views.MedicationStatementViewSet) + urlpatterns = [ path('', include(router.urls)), ] From 02a89ff1822e1dc0aa9d9cd4e14afe4c5683bf86 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 9 Mar 2020 15:46:02 -0400 Subject: [PATCH 24/95] table_ownerships -> table_ownership --- .../migrations/0010_auto_20200309_1945.py | 19 +++++++++++++++++++ chord_metadata_service/chord/models.py | 2 +- chord_metadata_service/chord/serializers.py | 4 ++-- .../chord/tests/test_models.py | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 chord_metadata_service/chord/migrations/0010_auto_20200309_1945.py diff --git a/chord_metadata_service/chord/migrations/0010_auto_20200309_1945.py b/chord_metadata_service/chord/migrations/0010_auto_20200309_1945.py new file mode 100644 index 000000000..5292a5273 --- /dev/null +++ b/chord_metadata_service/chord/migrations/0010_auto_20200309_1945.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.11 on 2020-03-09 19:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('chord', '0009_auto_20200218_1615'), + ] + + operations = [ + migrations.AlterField( + model_name='tableownership', + name='dataset', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='table_ownership', to='chord.Dataset'), + ), + ] diff --git a/chord_metadata_service/chord/models.py b/chord_metadata_service/chord/models.py index 0dbd3a26d..c7e7e64a7 100644 --- a/chord_metadata_service/chord/models.py +++ b/chord_metadata_service/chord/models.py @@ -147,7 +147,7 @@ class TableOwnership(models.Model): data_type = models.CharField(max_length=200) # TODO: Is this needed? # Delete table ownership upon project/dataset deletion - dataset = models.ForeignKey(Dataset, on_delete=models.CASCADE, related_name='table_ownerships') + dataset = models.ForeignKey(Dataset, on_delete=models.CASCADE, related_name='table_ownership') # If not specified, compound table which may link to many samples TODO: ??? sample = models.ForeignKey("phenopackets.Biosample", on_delete=models.CASCADE, blank=True, null=True) diff --git a/chord_metadata_service/chord/serializers.py b/chord_metadata_service/chord/serializers.py index 73c3a3c13..492583a79 100644 --- a/chord_metadata_service/chord/serializers.py +++ b/chord_metadata_service/chord/serializers.py @@ -30,10 +30,10 @@ class DatasetSerializer(GenericSerializer): "description", "contact_info", "linked_field_sets", - "table_ownerships", + "table_ownership", ) - table_ownerships = TableOwnershipSerializer(read_only=True, many=True, exclude_when_nested=["dataset"]) + table_ownership = TableOwnershipSerializer(read_only=True, many=True, exclude_when_nested=["dataset"]) # noinspection PyMethodMayBeStatic def validate_title(self, value): diff --git a/chord_metadata_service/chord/tests/test_models.py b/chord_metadata_service/chord/tests/test_models.py index 5dbc1362b..fc14a1477 100644 --- a/chord_metadata_service/chord/tests/test_models.py +++ b/chord_metadata_service/chord/tests/test_models.py @@ -65,6 +65,6 @@ def test_table_ownership(self): self.assertEqual(t.data_type, "variant") self.assertEqual(t.dataset, d) - self.assertIn(t, d.table_ownerships.all()) + self.assertIn(t, d.table_ownership.all()) self.assertEqual(str(t), f"{str(d)} -> {t.table_id}") From a5dd7eec83f42bac208eb590a4baec93dd01bc75 Mon Sep 17 00:00:00 2001 From: zxenia Date: Mon, 9 Mar 2020 17:35:13 -0400 Subject: [PATCH 25/95] add Quantity schema add validation for Ontology and Quantity fields --- chord_metadata_service/mcode/models.py | 2 +- chord_metadata_service/mcode/serializers.py | 41 +++++++++++++++++++++ chord_metadata_service/restapi/schemas.py | 29 +++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 34a7fd462..4a066bb8e 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -18,6 +18,7 @@ class GeneticVariantTested(models.Model, IndexableMixin): # TODO Discuss: Connection to Gene from Phenopackets id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the genetic variant tested.') + # make writable if it doesn't exist gene_studied = models.ForeignKey(Gene, blank=True, null=True, on_delete=models.SET_NULL, help_text='A gene targeted for mutation analysis, ' 'identified in HUGO Gene Nomenclature Committee (HGNC) notation.') @@ -58,7 +59,6 @@ class GeneticVariantFound(models.Model, IndexableMixin): method = JSONField(blank=True, null=True, help_text='An ontology or controlled vocabulary term to indetify ' 'the method used to perform the genetic test. ' 'Accepted value set: NCIT') - variant_found_identifier = JSONField(blank=True, null=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the' ' identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide ' diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index 640f41b03..ca1b77915 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -1,9 +1,20 @@ from rest_framework import serializers from chord_metadata_service.restapi.serializers import GenericSerializer from .models import * +from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, QUANTITY +from chord_metadata_service.restapi.validators import JsonSchemaValidator class GeneticVariantTestedSerializer(GenericSerializer): + method = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + variant_tested_identifier = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + data_value = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) class Meta: model = GeneticVariantTested @@ -11,18 +22,48 @@ class Meta: class GeneticVariantFoundSerializer(GenericSerializer): + method = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + variant_found_identifier = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + genomic_source_class = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + class Meta: model = GeneticVariantFound fields = '__all__' class GenomicsReportSerializer(GenericSerializer): + test_name = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + specimen_type = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + class Meta: model = GenomicsReport fields = '__all__' class LabsVitalSerializer(GenericSerializer): + body_height = serializers.JSONField( + validators=[JsonSchemaValidator(schema=QUANTITY)], + allow_null=True, required=False) + body_weight = serializers.JSONField( + validators=[JsonSchemaValidator(schema=QUANTITY)], + allow_null=True, required=False) + blood_pressure_diastolic = serializers.JSONField( + validators=[JsonSchemaValidator(schema=QUANTITY)], + allow_null=True, required=False) + blood_pressure_systolic = serializers.JSONField( + validators=[JsonSchemaValidator(schema=QUANTITY)], + allow_null=True, required=False) + class Meta: model = LabsVital fields = '__all__' diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 653ec0170..11a12f8c2 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -183,3 +183,32 @@ }, "additionalProperties": False } + +#mCode/FHIR Quantity + +QUANTITY = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "todo", + "title": "Quantity schema.", + "description": "Schema for the datatype Quantity.", + "type": "object", + "properties": { + "value": { + "type": "number" + }, + "comparator": { + "enum": ["<", ">", "<=", ">=", "="] + }, + "unit": { + "type": "string" + }, + "system": { + "type": "string", + "format": "uri" + }, + "code": { + "type": "string" + } + }, + "additionalProperties": False +} From 86b88d43eb7360269db25fa14f548c22b24196b3 Mon Sep 17 00:00:00 2001 From: zxenia Date: Tue, 10 Mar 2020 14:47:14 -0400 Subject: [PATCH 26/95] add codeable_concept and complex_ontology schemas --- chord_metadata_service/mcode/models.py | 8 +++- chord_metadata_service/mcode/serializers.py | 25 +++++++++- chord_metadata_service/restapi/schemas.py | 52 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 4a066bb8e..03723077f 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -199,13 +199,17 @@ class TNMStaging(models.Model, IndexableMixin): #TODO Extended Ontology class: stage group - required and staging system - not required tnm_type = models.CharField(choices=TNM_TYPES, max_length=200, help_text='TNM type: clinical or pathological.') - stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM ' - 'classification system. Accepted ontologies: SNOMED CT, AJCC and others.') + # Complex contains Ontology (required) + FHIR Observation.method - CodeableConcept (not required) + stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM classification ' + 'system. Accepted ontologies: SNOMED CT, AJCC and others.') + # Complex contains Ontology (required) + FHIR Observation.method - CodeableConcept (not required) primary_tumor_category = JSONField(help_text='Category of the primary tumor, based on its size and ' 'extent. Accepted ontologies: SNOMED CT, AJCC and others.') + # Complex contains Ontology (required) + FHIR Observation.method - CodeableConcept (not required) regional_nodes_category = JSONField(help_text='Category of the presence or absence of metastases in ' 'regional lymph nodes. Accepted ontologies: ' 'SNOMED CT, AJCC and others.') + # Complex contains Ontology (required) + FHIR Observation.method - CodeableConcept (not required) distant_metastases_category = JSONField(help_text='Category describing the presence or absence of ' 'metastases in remote anatomical locations. ' 'Accepted ontologies: SNOMED CT, AJCC and others.') diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index ca1b77915..63e99c788 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -1,8 +1,9 @@ from rest_framework import serializers from chord_metadata_service.restapi.serializers import GenericSerializer from .models import * -from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, QUANTITY +from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY from chord_metadata_service.restapi.validators import JsonSchemaValidator +from jsonschema import Draft7Validator class GeneticVariantTestedSerializer(GenericSerializer): @@ -70,12 +71,34 @@ class Meta: class CancerConditionSerializer(GenericSerializer): + clinical_status = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + condition_code = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + histology_morphology_behavior = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + class Meta: model = CancerCondition fields = '__all__' + def validate_body_location_code(self, value): + if isinstance(value, list): + for item in value: + validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) + if not validation: + raise serializers.ValidationError("Not valid JSON schema for this field.") + return value + class TNMStagingSerializer(GenericSerializer): + stage_group = serializers.JSONField( + validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY)], + allow_null=True, required=False) + class Meta: model = TNMStaging fields = '__all__' diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 11a12f8c2..145e5d7ee 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -1,5 +1,8 @@ # Individual schemas for validation of JSONField values +################################ Phenopackets based schemas ################################ + + ALLELE_SCHEMA = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "todo", @@ -184,6 +187,10 @@ "additionalProperties": False } + +################################## mCode/FHIR based schemas ################################## + + #mCode/FHIR Quantity QUANTITY = { @@ -212,3 +219,48 @@ }, "additionalProperties": False } + + +# mCode/FHIR CodeableConcept + +CODEABLE_CONCEPT = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "todo", + "title": "Codeable Concept schema.", + "description": "Schema for the datatype Concept.", + "type": "object", + "properties": { + "coding": { + "type": "array", + "items": { + "type": "object", + "properties": { + "system": {"type": "string", "format": "uri"}, + "version": {"type": "string"}, + "code": {"type": "string"}, + "display": {"type": "string"}, + "user_selected": {"type": "boolean"} + } + } + }, + "text": { + "type": "string" + } + }, + "additionalProperties": False +} + + +COMPLEX_ONTOLOGY = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "todo", + "title": "Complex ontology.", + "description": "Complex object to combine data value and staging system.", + "type": "object", + "properties": { + "data_value": CODEABLE_CONCEPT, + "staging_system": CODEABLE_CONCEPT + }, + "required": ["data_value"], + "additionalProperties": False +} From 65c7cf317989fa086ab29770ee749110c67baa5c Mon Sep 17 00:00:00 2001 From: zxenia Date: Tue, 10 Mar 2020 17:31:55 -0400 Subject: [PATCH 27/95] update requirements.txt and setup.py add format_checker to JsonSchemaValidator add validators in serializers --- chord_metadata_service/mcode/models.py | 6 --- chord_metadata_service/mcode/serializers.py | 57 +++++++++++++++++--- chord_metadata_service/restapi/schemas.py | 52 ++++++++++++++---- chord_metadata_service/restapi/validators.py | 9 ++-- requirements.txt | 1 + setup.py | 1 + 6 files changed, 101 insertions(+), 25 deletions(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 03723077f..fd22aefbb 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -199,17 +199,13 @@ class TNMStaging(models.Model, IndexableMixin): #TODO Extended Ontology class: stage group - required and staging system - not required tnm_type = models.CharField(choices=TNM_TYPES, max_length=200, help_text='TNM type: clinical or pathological.') - # Complex contains Ontology (required) + FHIR Observation.method - CodeableConcept (not required) stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM classification ' 'system. Accepted ontologies: SNOMED CT, AJCC and others.') - # Complex contains Ontology (required) + FHIR Observation.method - CodeableConcept (not required) primary_tumor_category = JSONField(help_text='Category of the primary tumor, based on its size and ' 'extent. Accepted ontologies: SNOMED CT, AJCC and others.') - # Complex contains Ontology (required) + FHIR Observation.method - CodeableConcept (not required) regional_nodes_category = JSONField(help_text='Category of the presence or absence of metastases in ' 'regional lymph nodes. Accepted ontologies: ' 'SNOMED CT, AJCC and others.') - # Complex contains Ontology (required) + FHIR Observation.method - CodeableConcept (not required) distant_metastases_category = JSONField(help_text='Category describing the presence or absence of ' 'metastases in remote anatomical locations. ' 'Accepted ontologies: SNOMED CT, AJCC and others.') @@ -264,11 +260,9 @@ class MedicationStatement(models.Model, IndexableMixin): help_text='An arbitrary identifier for the medication statement.') medication_code = JSONField(help_text='A code for medication. Accepted code systems:' 'Medication Clinical Drug (RxNorm) and other.') - # List of Ontologies termination_reason = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, help_text='A code explaining unplanned or premature termination of a course of' 'medication. Accepted ontologies: SNOMED CT.') - #Ontology treatment_intent = JSONField(blank=True, null=True, help_text='The purpose of a treatment.' 'Accepted ontologies: SNOMED CT.') start_date = models.DateTimeField(blank=True, null=True, help_text='The start date/time of the medication.') diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index 63e99c788..30c7530c5 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -1,7 +1,9 @@ from rest_framework import serializers from chord_metadata_service.restapi.serializers import GenericSerializer from .models import * -from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY +from chord_metadata_service.restapi.schemas import ( + ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY, TIME_OR_PERIOD +) from chord_metadata_service.restapi.validators import JsonSchemaValidator from jsonschema import Draft7Validator @@ -53,16 +55,16 @@ class Meta: class LabsVitalSerializer(GenericSerializer): body_height = serializers.JSONField( - validators=[JsonSchemaValidator(schema=QUANTITY)], + validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], allow_null=True, required=False) body_weight = serializers.JSONField( - validators=[JsonSchemaValidator(schema=QUANTITY)], + validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], allow_null=True, required=False) blood_pressure_diastolic = serializers.JSONField( - validators=[JsonSchemaValidator(schema=QUANTITY)], + validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], allow_null=True, required=False) blood_pressure_systolic = serializers.JSONField( - validators=[JsonSchemaValidator(schema=QUANTITY)], + validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], allow_null=True, required=False) class Meta: @@ -95,8 +97,19 @@ def validate_body_location_code(self, value): class TNMStagingSerializer(GenericSerializer): + #TODO Complex Ontology needs format checker + #TODO URI syntax examples for tests https://tools.ietf.org/html/rfc3986 stage_group = serializers.JSONField( - validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY)], + validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri'])], + allow_null=True, required=False) + primary_tumor_category = serializers.JSONField( + validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri'])], + allow_null=True, required=False) + regional_nodes_category = serializers.JSONField( + validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri'])], + allow_null=True, required=False) + distant_metastases_category = serializers.JSONField( + validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri'])], allow_null=True, required=False) class Meta: @@ -105,12 +118,44 @@ class Meta: class CancerRelatedProcedureSerializer(GenericSerializer): + code = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + occurence_time_or_period = serializers.JSONField( + validators=[JsonSchemaValidator(schema=TIME_OR_PERIOD, format_checker=['date-time'])], + allow_null=True, required=False) + treatment_intent = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + class Meta: model = CancerRelatedProcedure fields = '__all__' + def validate_target_body_site(self, value): + if isinstance(value, list): + for item in value: + validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) + if not validation: + raise serializers.ValidationError("Not valid JSON schema for this field.") + return value + class MedicationStatementSerializer(GenericSerializer): + medication_code = serializers.JSONField(validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) + treatment_intent = serializers.JSONField( + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + allow_null=True, required=False) + + def validate_termination_reason(self, value): + if isinstance(value, list): + for item in value: + validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) + if not validation: + raise serializers.ValidationError("Not valid JSON schema for this field.") + return value + + class Meta: model = MedicationStatement fields = '__all__' \ No newline at end of file diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 145e5d7ee..3b5ed566a 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -126,10 +126,8 @@ "required": ["evidence_code"] } - AGE = {"type": "string", "description": "An ISO8601 string represent age."} - AGE_RANGE = { "type": "object", "properties": { @@ -168,7 +166,6 @@ "additionalProperties": False } - DISEASE_ONSET = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "todo", @@ -187,16 +184,15 @@ "additionalProperties": False } - ################################## mCode/FHIR based schemas ################################## -#mCode/FHIR Quantity +# mCode/FHIR Quantity QUANTITY = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "todo", - "title": "Quantity schema.", + "title": "Quantity schema", "description": "Schema for the datatype Quantity.", "type": "object", "properties": { @@ -220,13 +216,12 @@ "additionalProperties": False } - # mCode/FHIR CodeableConcept CODEABLE_CONCEPT = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "todo", - "title": "Codeable Concept schema.", + "title": "Codeable Concept schema", "description": "Schema for the datatype Concept.", "type": "object", "properties": { @@ -250,11 +245,10 @@ "additionalProperties": False } - COMPLEX_ONTOLOGY = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "todo", - "title": "Complex ontology.", + "title": "Complex ontology", "description": "Complex object to combine data value and staging system.", "type": "object", "properties": { @@ -264,3 +258,41 @@ "required": ["data_value"], "additionalProperties": False } + + +PERIOD = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "todo", + "title": "Period", + "description": "Period schema.", + "type": "object", + "properties": { + "start": { + "type": "string", + "format": "date-time" + }, + "end": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": False +} + + +TIME_OR_PERIOD = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "todo", + "title": "Time of Period", + "description": "Time of Period schema.", + "type": "object", + "properties": { + "value": { + "anyOf": [ + {"type": "string", "format": "date-time"}, + PERIOD + ] + } + }, + "additionalProperties": False +} diff --git a/chord_metadata_service/restapi/validators.py b/chord_metadata_service/restapi/validators.py index 4b33a95d0..c6d1240a4 100644 --- a/chord_metadata_service/restapi/validators.py +++ b/chord_metadata_service/restapi/validators.py @@ -1,15 +1,18 @@ from rest_framework import serializers -from jsonschema import Draft7Validator +from jsonschema import Draft7Validator, FormatChecker class JsonSchemaValidator(object): """ Custom class based validator to validate against Json schema for JSONField """ - def __init__(self, schema): + def __init__(self, schema, format_checker=None): self.schema = schema + self.format_checker = format_checker def __call__(self, value): - validation = Draft7Validator(self.schema).is_valid(value) + validation = Draft7Validator( + self.schema, format_checker=FormatChecker(formats=self.format_checker) + ).is_valid(value) if not validation: raise serializers.ValidationError("Not valid JSON schema for this field.") return value diff --git a/requirements.txt b/requirements.txt index bc4e36bf6..7001b1f27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,6 +40,7 @@ rdflib==4.2.2 rdflib-jsonld==0.4.0 redis==3.4.1 requests==2.23.0 +rfc3987==1.3.8 simplejson==3.17.0 six==1.14.0 snowballstemmer==2.0.0 diff --git a/setup.py b/setup.py index 51674a9f1..44717a145 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ "rdflib-jsonld==0.4.0", "requests>=2.23,<3.0", "uritemplate>=3.0,<4.0", + "rfc3987==1.3.8", ], author=config["package"]["authors"], From e6ff508ed3fd68634c8a72743a48694689965bae Mon Sep 17 00:00:00 2001 From: zxenia Date: Tue, 10 Mar 2020 18:56:26 -0400 Subject: [PATCH 28/95] separate descriptions and update help_text in models --- chord_metadata_service/mcode/descriptions.py | 145 +++++++++++++++ chord_metadata_service/mcode/models.py | 182 +++++++------------ 2 files changed, 211 insertions(+), 116 deletions(-) create mode 100644 chord_metadata_service/mcode/descriptions.py diff --git a/chord_metadata_service/mcode/descriptions.py b/chord_metadata_service/mcode/descriptions.py new file mode 100644 index 000000000..f45e64a2e --- /dev/null +++ b/chord_metadata_service/mcode/descriptions.py @@ -0,0 +1,145 @@ +# Most parts of this text are taken from the mCODE:Minimal Common Oncology Data Elements Data Dictionary. +# The mCODE is made available under the Creative Commons 0 "No Rights Reserved" license https://creativecommons.org/share-your-work/public-domain/cc0/ + +# Portions of this text copyright (c) 2019-2020 the Canadian Centre for Computational Genomics; licensed under the +# GNU Lesser General Public License version 3. + + +GENETIC_VARIANT_TESTED = { + "description": "A description of an alteration in the most common DNA nucleotide sequence.", + "properties": { + "id": "An arbitrary identifier for the genetic variant tested.", + "gene_studied": "A gene targeted for mutation analysis, identified in HUGO Gene Nomenclature Committee " + "(HGNC) notation.", + "method": "An ontology or controlled vocabulary term to identify the method used to perform the genetic test. " + "Accepted value set: NCIT.", + "variant_tested_identifier": "The variation ID assigned by HGVS, for example, 360448 is the identifier for " + "NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR).", + "variant_tested_hgvs_name": "Symbolic representation of the variant used in HGVS, for example, " + "NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.", + "variant_tested_description": "Description of the variant.", + "data_value": "An ontology or controlled vocabulary term to identify positive or negative value for" + "the mutation. Accepted value set: SNOMED CT." + } +} + + +GENETIC_VARIANT_FOUND = { + "description": "Description of single discrete variant tested.", + "properties": { + "id": "An arbitrary identifier for the genetic variant found.", + "method": "An ontology or controlled vocabulary term to identify the method used to perform the genetic test. " + "Accepted value set: NCIT.", + "variant_found_identifier": "The variation ID assigned by HGVS, for example, 360448 is the identifier for " + "NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). " + "Accepted value set: ClinVar.", + "variant_found_hgvs_name": "Symbolic representation of the variant used in HGVS, for example, " + "NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.", + "variant_found_description": "Description of the variant.", + "genomic_source_class": "An ontology or controlled vocabulary term to identify the genomic class of the " + "specimen being analyzed." + } +} + + +GENOMICS_REPORT = { + "description": "Genetic Analysis Summary.", + "properties": { + "id": "An arbitrary identifier for the genetics report.", + "test_name": "An ontology or controlled vocabulary term to identify the laboratory test. " + "Accepted value sets: LOINC, GTR.", + "performing_ogranization_name": "The name of the organization producing the genomics report.", + "specimen_type": "An ontology or controlled vocabulary term to identify the type of material the specimen " + "contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.", + "genetic_variant_tested": "A test for a specific mutation on a particular gene.", + "genetic_variant_found": "Records an alteration in the most common DNA nucleotide sequence.", + "subject": "Subject (Patient) of genomics report." + } +} + + +LABS_VITAL = { + "description": "A description of tests performed on patient.", + "properties": { + "id": "An arbitrary identifier for the labs/vital tests.", + "individual": "The individual who is the subject of the tests.", + "body_height": "The patient\'s height.", + "body_weight": "The patient\'s weight.", + "cbc_with_auto_differential_panel": "Reference to a laboratory observation in the CBC with Auto Differential" + "Panel test.", + "comprehensive_metabolic_2000": "Reference to a laboratory observation in the CMP 2000 test.", + "blood_pressure_diastolic": "The blood pressure after the contraction of the heart while the chambers of " + "the heart refill with blood, when the pressure is lowest.", + "blood_pressure_systolic": "The blood pressure during the contraction of the left ventricle of the heart, " + "when blood pressure is at its highest.", + "tumor_marker_test": "An ontology or controlled vocabulary term to identify tumor marker test." + } +} + + +CANCER_CONDITION = { + "description": "A description of history of primary or secondary cancer conditions.", + "properties": { + "id": "An arbitrary identifier for the cancer condition.", + "condition_type": "Cancer condition type: primary or secondary.", + "body_location_code": "Code for the body location, optionally pre-coordinating laterality or direction. " + "Accepted ontologies: SNOMED CT, ICD-O-3 and others.", + "clinical_status": "A flag indicating whether the condition is active or inactive, recurring, in remission, " + "or resolved (as of the last update of the Condition). Accepted code system: " + "http://terminology.hl7.org/CodeSystem/condition-clinical", + "condition_code": "A code describing the type of primary or secondary malignant neoplastic disease.", + "date_of_diagnosis": "The date the disease was first clinically recognized with sufficient certainty, " + "regardless of whether it was fully characterized at that time.", + "histology_morphology_behavior": "A description of the morphologic and behavioral characteristics of " + "the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others.", + "subject": "The subject (Patient) of the study that has a cancer condition." + } +} + + +TNM_STAGING = { + "description": "A description of the cancer spread in a patient's body.", + "properties": { + "id": "An arbitrary identifier for the TNM staging.", + "tnm_type": "TNM type: clinical or pathological.", + "stage_group": "The extent of the cancer in the body, according to the TNM classification system." + "Accepted ontologies: SNOMED CT, AJCC and others.", + "primary_tumor_category": "Category of the primary tumor, based on its size and extent. " + "Accepted ontologies: SNOMED CT, AJCC and others.", + "regional_nodes_category": "Category of the presence or absence of metastases in regional lymph nodes. " + "Accepted ontologies: SNOMED CT, AJCC and others.", + "distant_metastases_category": "Category describing the presence or absence of metastases in remote " + "anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.", + "cancer_condition": "Cancer condition." + } +} + + +CANCER_RELATED_PROCEDURE = { + "description": "Description of radiological treatment or surgical action addressing a cancer condition.", + "properties": { + "id": "An arbitrary identifier for the procedure.", + "procedure_type": "Type of cancer related procedure: radion or surgical.", + "code": "Code for the procedure performed.", + "occurence_time_or_period": "The date/time that a procedure was performed.", + "target_body_site": "The body location(s) where the procedure was performed.", + "treatment_intent": "The purpose of a treatment.", + "subject": "The patient who has a cancer condition." + } +} + + +MEDICATION_STATEMENT = { + "description": "Description of medication use.", + "properties": { + "id": "An arbitrary identifier for the medication statement.", + "medication_code": "A code for medication. Accepted code systems: Medication Clinical Drug (RxNorm) and other.", + "termination_reason": "A code explaining unplanned or premature termination of a course of medication. " + "Accepted ontologies: SNOMED CT.", + "treatment_intent": "The purpose of a treatment. Accepted ontologies: SNOMED CT.", + "start_date": "The start date/time of the medication.", + "end_date": "The end date/time of the medication.", + "date_time": "The date/time the medication was administered.", + "subject": "Subject of medication statement." + } +} diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index fd22aefbb..e3129d242 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -4,8 +4,9 @@ from chord_metadata_service.phenopackets.models import Gene from chord_metadata_service.patients.models import Individual from django.core.exceptions import ValidationError +from chord_metadata_service.restapi.description_utils import rec_help +import chord_metadata_service.mcode.descriptions as d -#TODO MOVE all help_text in sep doc ################################# Genomics ################################# @@ -17,26 +18,20 @@ class GeneticVariantTested(models.Model, IndexableMixin): # TODO Discuss: Connection to Gene from Phenopackets id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the genetic variant tested.') + help_text=rec_help(d.GENETIC_VARIANT_TESTED, "id")) # make writable if it doesn't exist gene_studied = models.ForeignKey(Gene, blank=True, null=True, on_delete=models.SET_NULL, - help_text='A gene targeted for mutation analysis, ' - 'identified in HUGO Gene Nomenclature Committee (HGNC) notation.') - method = JSONField(blank=True, null=True, help_text='An ontology or controlled vocabulary term to indetify ' - 'the method used to perform the genetic test. ' - 'Accepted value set: NCIT') + help_text=rec_help(d.GENETIC_VARIANT_TESTED, "gene_studied")) + method = JSONField(blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "method")) variant_tested_identifier = JSONField(blank=True, null=True, - help_text='The variation ID assigned by HGVS, for example, ' - '360448 is the identifier for NM_005228.4(EGFR):c.-237A>G ' - '(single nucleotide variant in EGFR).') + help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_identifier")) variant_tested_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, - help_text='Symbolic representation of the variant used in HGVS, for example, ' - 'NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.') + help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_hgvs_name")) variant_tested_description = models.CharField(max_length=200, blank=True, - help_text='Description of the variant.') + help_text=rec_help(d.GENETIC_VARIANT_TESTED, + "variant_tested_description")) data_value = JSONField(blank=True, null=True, - help_text='An ontology or controlled vocabulary term to indetify ' - 'positive or negative value for the mutation. Accepted value set: SNOMED CT.') + help_text=rec_help(d.GENETIC_VARIANT_TESTED, "data_value")) def __str__(self): return str(self.id) @@ -55,22 +50,16 @@ class GeneticVariantFound(models.Model, IndexableMixin): # TODO Discuss: Connection to Gene from Phenopackets id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the genetic variant found.') - method = JSONField(blank=True, null=True, help_text='An ontology or controlled vocabulary term to indetify ' - 'the method used to perform the genetic test. ' - 'Accepted value set: NCIT') + help_text=rec_help(d.GENETIC_VARIANT_FOUND, "id")) + method = JSONField(blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "method")) variant_found_identifier = JSONField(blank=True, null=True, - help_text='The variation ID assigned by HGVS, for example, 360448 is the' - ' identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide ' - 'variant in EGFR). Accepted value set: ClinVar.') + help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_identifier")) variant_found_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, - help_text='Symbolic representation of the variant used in HGVS, for example, ' - 'NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.') + help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_hgvs_name")) variant_found_description = models.CharField(max_length=200, blank=True, - help_text='Description of the variant.') + help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_description")) genomic_source_class = JSONField(blank=True, null=True, - help_text='An ontology or controlled vocabulary term to indetify ' - 'the genomic class of the specimen being analyzed.') + help_text=rec_help(d.GENETIC_VARIANT_FOUND, "genomic_source_class")) def __str__(self): return str(self.id) @@ -86,23 +75,16 @@ class GenomicsReport(models.Model, IndexableMixin): Genetic Analysis Summary """ - id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the genetics report.') - test_name = JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test.' - 'Accepted value sets: LOINC, GTR') + id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.GENOMICS_REPORT, "id")) + test_name = JSONField(help_text=rec_help(d.GENOMICS_REPORT, "test_name")) performing_ogranization_name = models.CharField(max_length=200, blank=True, - help_text='The name of the organization ' - 'producing the genomics report.') - specimen_type = JSONField(blank=True, null=True, - help_text='An ontology or controlled vocabulary term to indetify the type of ' - 'material the specimen contains or consists of.' - 'Accepted value set: HL7 Version 2 and Specimen Type.') + help_text=rec_help(d.GENOMICS_REPORT, "performing_ogranization_name")) + specimen_type = JSONField(blank=True, null=True, help_text=rec_help(d.GENOMICS_REPORT, "specimen_type")) genetic_variant_tested = models.ManyToManyField(GeneticVariantTested, blank=True, - help_text='A test for a specific mutation on a particular gene.') + help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_tested")) genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, - help_text='Records an alteration in the most common DNA ' - 'nucleotide sequence.') - subject = models.ForeignKey(Individual, help_text='Subject of genomics report.', on_delete=models.CASCADE) + help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_found")) + subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.GENOMICS_REPORT, "subject")) def __str__(self): return str(self.id) @@ -118,24 +100,21 @@ class LabsVital(models.Model, IndexableMixin): # TODO Should this class be a part of Patients app? patient related metadata # TODO the data value should be in form of Quantity datatype - ADD json schema for Quantity id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the labs/vital tests.') + help_text=rec_help(d.LABS_VITAL, "id")) individual = models.ForeignKey(Individual, on_delete=models.CASCADE, - help_text='The individual who is the subject of the tests.') - body_height = JSONField(help_text='The patient\'s height.') - body_weight = JSONField(help_text='The patient\'s weight.') + help_text=rec_help(d.LABS_VITAL, "individual")) + body_height = JSONField(help_text=rec_help(d.LABS_VITAL, "body_height")) + body_weight = JSONField(help_text=rec_help(d.LABS_VITAL, "body_weight")) cbc_with_auto_differential_panel = ArrayField(models.CharField(max_length=200), blank=True, null=True, - help_text='Reference to a laboratory observation in the CBC with' - ' Auto Differential Panel test. ') + help_text=rec_help(d.LABS_VITAL, "cbc_with_auto_differential_panel")) comprehensive_metabolic_2000 = ArrayField(models.CharField(max_length=200), blank=True, null=True, - help_text='Reference to a laboratory observation in the CMP 2000 test.') + help_text=rec_help(d.LABS_VITAL, "comprehensive_metabolic_2000")) blood_pressure_diastolic = JSONField(blank=True, null=True, - help_text='The blood pressure after the contraction of the heart while the ' - 'chambers of the heart refill with blood, when the pressure is lowest.') + help_text=rec_help(d.LABS_VITAL, "blood_pressure_diastolic")) blood_pressure_systolic = JSONField(blank=True, null=True, - help_text='The blood pressure during the contraction of the left ' - 'ventricle of the heart, when blood pressure is at its highest.') + help_text=rec_help(d.LABS_VITAL, "blood_pressure_systolic")) #TODO Ontology or Quantity or Ratio (?) - tumor_marker_test = JSONField(help_text='An ontology or controlled vocabulary term to indetify tumor marker test.') + tumor_marker_test = JSONField(help_text=rec_help(d.LABS_VITAL, "tumor_marker_test")) def __str__(self): return str(self.id) @@ -156,30 +135,18 @@ class CancerCondition(models.Model, IndexableMixin): ('primary', 'primary'), ('secondary', 'secondary') ) - id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the cancer condition.') + id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.CANCER_CONDITION, "id")) condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, max_length=200, - help_text='Cancer condition type: primary or secondary.') + help_text=rec_help(d.CANCER_CONDITION, "condition_type")) body_location_code = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, - help_text='Code for the body location, optionally pre-coordinating laterality ' - 'or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.') - clinical_status = JSONField(blank=True, null=True, - help_text='A flag indicating whether the condition is active ' - 'or inactive, recurring, in remission, or resolved (as of the last update ' - 'of the Condition). Accepted code system: ' - 'http://terminology.hl7.org/CodeSystem/condition-clinical') - condition_code = JSONField(help_text='A code describing the type of primary or secondary malignant ' - 'neoplastic disease.') + help_text=rec_help(d.CANCER_CONDITION, "body_location_code")) + clinical_status = JSONField(blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "clinical_status")) + condition_code = JSONField(help_text=rec_help(d.CANCER_CONDITION, "condition_code")) date_of_diagnosis = models.DateTimeField(blank=True, null=True, - help_text='The date the disease was first clinically recognized with ' - 'sufficient certainty, regardless of whether it was fully ' - 'characterized at that time.') + help_text=rec_help(d.CANCER_CONDITION, "date_of_diagnosis")) histology_morphology_behavior = JSONField(blank=True, null=True, - help_text='A description of the morphologic and behavioral ' - 'characteristics of the cancer. Accepted ontologies:' - 'SNOMED CT, ICD-O-3 and others.') - subject = models.ForeignKey(Individual, help_text='The subject of the study that has a cancer condition.', - on_delete=models.CASCADE) + help_text=rec_help(d.CANCER_CONDITION, "histology_morphology_behavior")) + subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.CANCER_CONDITION, "subject")) def __str__(self): return str(self.id) @@ -194,23 +161,15 @@ class TNMStaging(models.Model, IndexableMixin): ('clinical', 'clinical'), ('pathologic', 'pathologic') ) - id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the TNM staging.') - #TODO Extended Ontology class: stage group - required and staging system - not required - tnm_type = models.CharField(choices=TNM_TYPES, max_length=200, - help_text='TNM type: clinical or pathological.') - stage_group = JSONField(help_text='The extent of the cancer in the body, according to the TNM classification ' - 'system. Accepted ontologies: SNOMED CT, AJCC and others.') - primary_tumor_category = JSONField(help_text='Category of the primary tumor, based on its size and ' - 'extent. Accepted ontologies: SNOMED CT, AJCC and others.') - regional_nodes_category = JSONField(help_text='Category of the presence or absence of metastases in ' - 'regional lymph nodes. Accepted ontologies: ' - 'SNOMED CT, AJCC and others.') - distant_metastases_category = JSONField(help_text='Category describing the presence or absence of ' - 'metastases in remote anatomical locations. ' - 'Accepted ontologies: SNOMED CT, AJCC and others.') + id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.TNM_STAGING, "id")) + tnm_type = models.CharField(choices=TNM_TYPES, max_length=200, help_text=rec_help(d.TNM_STAGING, "tnm_type")) + stage_group = JSONField(help_text=rec_help(d.TNM_STAGING, "stage_group")) + primary_tumor_category = JSONField(help_text=rec_help(d.TNM_STAGING, "primary_tumor_category")) + regional_nodes_category = JSONField(help_text=rec_help(d.TNM_STAGING, "regional_nodes_category")) + distant_metastases_category = JSONField(help_text=rec_help(d.TNM_STAGING, "distant_metastases_category")) # TODO check if one cancer condition has many TNM Staging - cancer_condition = models.ForeignKey(CancerCondition, help_text='Cancer condition.', on_delete=models.CASCADE) + cancer_condition = models.ForeignKey(CancerCondition, on_delete=models.CASCADE, + help_text=rec_help(d.TNM_STAGING, "cancer_condition")) def __str__(self): return str(self.id) @@ -229,22 +188,17 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): ('radiation', 'radiation'), ('surgical', 'surgical') ) - #TODO Ontology class - id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the procedure.') + id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "id")) procedure_type = models.CharField(choices=PROCEDURE_TYPES, max_length=200, - help_text='Type of cancer related procedure: radion or surgical procedure.') - #Ontology - code = JSONField(help_text='Code for the procedure performed.') - #DateTime or Ontology - occurence_time_or_period = JSONField(help_text='The date/time that a procedure was performed.') - #List of Ontologies + help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "procedure_type")) + code = JSONField(help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "code")) + occurence_time_or_period = JSONField(help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "occurence_time_or_period")) target_body_site = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, - help_text='The body location(s) where the procedure was performed.') - #Ontology - treatment_intent = JSONField(blank=True, null=True, help_text='The purpose of a treatment.') - subject = models.ForeignKey(Individual, help_text='The patient who has a cancer condition.', - on_delete=models.CASCADE) + help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "target_body_site")) + treatment_intent = JSONField(blank=True, null=True, + help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "treatment_intent")) + subject = models.ForeignKey(Individual, on_delete=models.CASCADE, + help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "subject")) def __str__(self): return str(self.id) @@ -256,20 +210,16 @@ class MedicationStatement(models.Model, IndexableMixin): Class to record the use of a medication. """ - id = models.CharField(primary_key=True, max_length=200, - help_text='An arbitrary identifier for the medication statement.') - medication_code = JSONField(help_text='A code for medication. Accepted code systems:' - 'Medication Clinical Drug (RxNorm) and other.') + id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.MEDICATION_STATEMENT, "id")) + medication_code = JSONField(help_text=rec_help(d.MEDICATION_STATEMENT, "medication_code")) termination_reason = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, - help_text='A code explaining unplanned or premature termination of a course of' - 'medication. Accepted ontologies: SNOMED CT.') - treatment_intent = JSONField(blank=True, null=True, help_text='The purpose of a treatment.' - 'Accepted ontologies: SNOMED CT.') - start_date = models.DateTimeField(blank=True, null=True, help_text='The start date/time of the medication.') - end_date = models.DateTimeField(blank=True, null=True, help_text='The end date/time of the medication.') - date_time = models.DateTimeField(blank=True, null=True, help_text='The date/time the medication was administered.') - subject = models.ForeignKey(Individual, help_text='Subject of medication statement.', - on_delete=models.CASCADE) + help_text=rec_help(d.MEDICATION_STATEMENT, "termination_reason")) + treatment_intent = JSONField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "treatment_intent")) + start_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "start_date")) + end_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "end_date")) + date_time = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "date_time")) + subject = models.ForeignKey(Individual, on_delete=models.CASCADE, + help_text=rec_help(d.MEDICATION_STATEMENT, "subject")) def __str__(self): return str(self.id) From 610cb06bd3340ce5f1c8efaf5848396f041a4f46 Mon Sep 17 00:00:00 2001 From: zxenia Date: Wed, 11 Mar 2020 16:13:53 -0400 Subject: [PATCH 29/95] add mcode specific fields to Individual model (Patients app) migrations for mcode and patients --- .../migrations/0002_auto_20200311_1610.py | 101 ++++++++++++++++++ .../migrations/0005_auto_20200311_1610.py | 29 +++++ chord_metadata_service/patients/models.py | 9 ++ 3 files changed, 139 insertions(+) create mode 100644 chord_metadata_service/mcode/migrations/0002_auto_20200311_1610.py create mode 100644 chord_metadata_service/patients/migrations/0005_auto_20200311_1610.py diff --git a/chord_metadata_service/mcode/migrations/0002_auto_20200311_1610.py b/chord_metadata_service/mcode/migrations/0002_auto_20200311_1610.py new file mode 100644 index 000000000..bfc0ee4f0 --- /dev/null +++ b/chord_metadata_service/mcode/migrations/0002_auto_20200311_1610.py @@ -0,0 +1,101 @@ +# Generated by Django 2.2.10 on 2020-03-11 20:10 + +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mcode', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='cancercondition', + name='histology_morphology_behavior', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A description of the morphologic and behavioral characteristics of the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True), + ), + migrations.AlterField( + model_name='cancercondition', + name='subject', + field=models.ForeignKey(help_text='The subject (Patient) of the study that has a cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual'), + ), + migrations.AlterField( + model_name='cancerrelatedprocedure', + name='procedure_type', + field=models.CharField(choices=[('radiation', 'radiation'), ('surgical', 'surgical')], help_text='Type of cancer related procedure: radion or surgical.', max_length=200), + ), + migrations.AlterField( + model_name='geneticvariantfound', + name='genomic_source_class', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the genomic class of the specimen being analyzed.', null=True), + ), + migrations.AlterField( + model_name='geneticvariantfound', + name='method', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True), + ), + migrations.AlterField( + model_name='geneticvarianttested', + name='data_value', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify positive or negative value forthe mutation. Accepted value set: SNOMED CT.', null=True), + ), + migrations.AlterField( + model_name='geneticvarianttested', + name='method', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True), + ), + migrations.AlterField( + model_name='genomicsreport', + name='performing_ogranization_name', + field=models.CharField(blank=True, help_text='The name of the organization producing the genomics report.', max_length=200), + ), + migrations.AlterField( + model_name='genomicsreport', + name='specimen_type', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the type of material the specimen contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.', null=True), + ), + migrations.AlterField( + model_name='genomicsreport', + name='subject', + field=models.ForeignKey(help_text='Subject (Patient) of genomics report.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual'), + ), + migrations.AlterField( + model_name='genomicsreport', + name='test_name', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test. Accepted value sets: LOINC, GTR.'), + ), + migrations.AlterField( + model_name='labsvital', + name='cbc_with_auto_differential_panel', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Reference to a laboratory observation in the CBC with Auto DifferentialPanel test.', null=True, size=None), + ), + migrations.AlterField( + model_name='labsvital', + name='tumor_marker_test', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify tumor marker test.'), + ), + migrations.AlterField( + model_name='medicationstatement', + name='medication_code', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='A code for medication. Accepted code systems: Medication Clinical Drug (RxNorm) and other.'), + ), + migrations.AlterField( + model_name='medicationstatement', + name='termination_reason', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), blank=True, help_text='A code explaining unplanned or premature termination of a course of medication. Accepted ontologies: SNOMED CT.', null=True, size=None), + ), + migrations.AlterField( + model_name='medicationstatement', + name='treatment_intent', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment. Accepted ontologies: SNOMED CT.', null=True), + ), + migrations.AlterField( + model_name='tnmstaging', + name='stage_group', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system.Accepted ontologies: SNOMED CT, AJCC and others.'), + ), + ] diff --git a/chord_metadata_service/patients/migrations/0005_auto_20200311_1610.py b/chord_metadata_service/patients/migrations/0005_auto_20200311_1610.py new file mode 100644 index 000000000..c763dc652 --- /dev/null +++ b/chord_metadata_service/patients/migrations/0005_auto_20200311_1610.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.10 on 2020-03-11 20:10 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('patients', '0004_auto_20200129_1537'), + ] + + operations = [ + migrations.AddField( + model_name='individual', + name='comorbid_condition', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='One or more conditions that occur with primary condition.', null=True), + ), + migrations.AddField( + model_name='individual', + name='ecog_performance_status', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Value representing the Eastern Cooperative Oncology Group performance status.', null=True), + ), + migrations.AddField( + model_name='individual', + name='karnofsky', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Value representing the Karnofsky Performance status.', null=True), + ), + ] diff --git a/chord_metadata_service/patients/models.py b/chord_metadata_service/patients/models.py index 046b4532e..ffc66efb6 100644 --- a/chord_metadata_service/patients/models.py +++ b/chord_metadata_service/patients/models.py @@ -44,8 +44,17 @@ class Individual(models.Model, IndexableMixin): active = models.BooleanField(default=False, help_text='Whether this patient\'s record is in active use.') deceased = models.BooleanField(default=False, help_text='Indicates if the individual is deceased or not.') # mCode specific + # this field should be complex Ontology - clinical status and code - two Codeable concept - single, cl status has enum list of values + comorbid_condition = JSONField(blank=True, null=True, help_text='One or more conditions that occur with primary' + ' condition.') + # Codeable concept single + ecog_performance_status = JSONField(blank=True, null=True, help_text='Value representing the Eastern Cooperative ' + 'Oncology Group performance status.') + # Codeable concept single + karnofsky = JSONField(blank=True, null=True, help_text='Value representing the Karnofsky Performance status.') race = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s race.') ethnicity = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s ethnicity.') + # extra extra_properties = JSONField(blank=True, null=True, help_text='Extra properties that are not supported by current schema') created = models.DateTimeField(auto_now_add=True) From 92074076ea2b5ffb743b82fb589f7301c2e4c647 Mon Sep 17 00:00:00 2001 From: zxenia Date: Thu, 12 Mar 2020 10:57:18 -0400 Subject: [PATCH 30/95] add comorbid condition schema, func for custom schema --- .../patients/serializers.py | 7 ++- chord_metadata_service/restapi/schemas.py | 51 ++++++++++++++----- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/chord_metadata_service/patients/serializers.py b/chord_metadata_service/patients/serializers.py index 4df229f8f..a85423d74 100644 --- a/chord_metadata_service/patients/serializers.py +++ b/chord_metadata_service/patients/serializers.py @@ -3,7 +3,7 @@ BiosampleSerializer, SimplePhenopacketSerializer ) -from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, AGE_OR_AGE_RANGE +from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, AGE_OR_AGE_RANGE, COMORBID_CONDITION from chord_metadata_service.restapi.validators import JsonSchemaValidator from chord_metadata_service.restapi.serializers import GenericSerializer from chord_metadata_service.restapi.fhir_utils import fhir_patient @@ -21,6 +21,11 @@ class IndividualSerializer(GenericSerializer): allow_null=True, required=False ) + comorbid_condition = serializers.JSONField( + validators=[JsonSchemaValidator(schema=COMORBID_CONDITION)], + allow_null=True, + required=False + ) biosamples = BiosampleSerializer( read_only=True, many=True, exclude_when_nested=['individual']) diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 3b5ed566a..a83db3768 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -1,3 +1,6 @@ +from typing import List + + # Individual schemas for validation of JSONField values ################################ Phenopackets based schemas ################################ @@ -245,20 +248,6 @@ "additionalProperties": False } -COMPLEX_ONTOLOGY = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", - "title": "Complex ontology", - "description": "Complex object to combine data value and staging system.", - "type": "object", - "properties": { - "data_value": CODEABLE_CONCEPT, - "staging_system": CODEABLE_CONCEPT - }, - "required": ["data_value"], - "additionalProperties": False -} - PERIOD = { "$schema": "http://json-schema.org/draft-07/schema#", @@ -296,3 +285,37 @@ }, "additionalProperties": False } + + +def customize_schema(first_typeof: dict, second_typeof: dict, first_property: str, second_property: str, + id: str=None, title: str=None, description: str=None, additionalProperties=False, + required=None) -> dict: + if required is None: + required = [] + return { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": id, + "title": title, + "description": description, + "type": "object", + "properties": { + first_property: first_typeof, + second_property: second_typeof + }, + "required": required, + "additionalProperties": additionalProperties + } + + +COMORBID_CONDITION = customize_schema(first_typeof=CODEABLE_CONCEPT, second_typeof=CODEABLE_CONCEPT, + first_property="clinical_status", second_property="code", + id="chord_metadata_service:comorbid_condition_schema", + title="Comorbid Condition schema", + description="Comorbid condition schema.") + + +COMPLEX_ONTOLOGY = customize_schema(first_typeof=CODEABLE_CONCEPT, second_typeof=CODEABLE_CONCEPT, + first_property="data_value", second_property="staging_system", + id="chord_metadata_service:complex_ontology_schema", title="Complex ontology", + description="Complex object to combine data value and staging system.", + required=["data_value"]) From 70a0f93dc70d5a007c34a0c0f05b9acb64e6f1d4 Mon Sep 17 00:00:00 2001 From: zxenia Date: Thu, 12 Mar 2020 11:10:50 -0400 Subject: [PATCH 31/95] add validators for new patient fields --- chord_metadata_service/patients/models.py | 3 +-- chord_metadata_service/patients/serializers.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/chord_metadata_service/patients/models.py b/chord_metadata_service/patients/models.py index ffc66efb6..5230874b9 100644 --- a/chord_metadata_service/patients/models.py +++ b/chord_metadata_service/patients/models.py @@ -47,10 +47,9 @@ class Individual(models.Model, IndexableMixin): # this field should be complex Ontology - clinical status and code - two Codeable concept - single, cl status has enum list of values comorbid_condition = JSONField(blank=True, null=True, help_text='One or more conditions that occur with primary' ' condition.') - # Codeable concept single + #TODO decide use ONTOLOGY_CLASS vs. CODEABLE_CONCEPT ecog_performance_status = JSONField(blank=True, null=True, help_text='Value representing the Eastern Cooperative ' 'Oncology Group performance status.') - # Codeable concept single karnofsky = JSONField(blank=True, null=True, help_text='Value representing the Karnofsky Performance status.') race = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s race.') ethnicity = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s ethnicity.') diff --git a/chord_metadata_service/patients/serializers.py b/chord_metadata_service/patients/serializers.py index a85423d74..b7bfa5345 100644 --- a/chord_metadata_service/patients/serializers.py +++ b/chord_metadata_service/patients/serializers.py @@ -3,7 +3,9 @@ BiosampleSerializer, SimplePhenopacketSerializer ) -from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, AGE_OR_AGE_RANGE, COMORBID_CONDITION +from chord_metadata_service.restapi.schemas import ( + ONTOLOGY_CLASS, AGE_OR_AGE_RANGE, COMORBID_CONDITION, CODEABLE_CONCEPT +) from chord_metadata_service.restapi.validators import JsonSchemaValidator from chord_metadata_service.restapi.serializers import GenericSerializer from chord_metadata_service.restapi.fhir_utils import fhir_patient @@ -26,6 +28,16 @@ class IndividualSerializer(GenericSerializer): allow_null=True, required=False ) + ecog_performance_status = serializers.JSONField( + validators=[JsonSchemaValidator(schema=CODEABLE_CONCEPT)], + allow_null=True, + required=False + ) + karnofsky = serializers.JSONField( + validators=[JsonSchemaValidator(schema=CODEABLE_CONCEPT)], + allow_null=True, + required=False + ) biosamples = BiosampleSerializer( read_only=True, many=True, exclude_when_nested=['individual']) From 04751f4830543ab2dc469f96086886fa8b7b449b Mon Sep 17 00:00:00 2001 From: zxenia Date: Thu, 12 Mar 2020 11:14:55 -0400 Subject: [PATCH 32/95] add descriptions for new fields in patients --- chord_metadata_service/patients/descriptions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chord_metadata_service/patients/descriptions.py b/chord_metadata_service/patients/descriptions.py index 41b91ea2e..c895b5835 100644 --- a/chord_metadata_service/patients/descriptions.py +++ b/chord_metadata_service/patients/descriptions.py @@ -25,6 +25,9 @@ # mCode-specific "race": "A code for a person's race (mCode).", "ethnicity": "A code for a person's ethnicity (mCode).", + "comorbid_condition": "One or more conditions that occur with primary condition.", + "ecog_performance_status": "Value representing the Eastern Cooperative Oncology Group performance status.", + "karnofsky": "Value representing the Karnofsky Performance status.", **EXTRA_PROPERTIES } From ddcd1d409f00ee167bd5730b652b8378a1c2852a Mon Sep 17 00:00:00 2001 From: zxenia Date: Thu, 12 Mar 2020 16:59:05 -0400 Subject: [PATCH 33/95] add tumor_market_test schema and validator change ids for schemas to local prefix+name of schema --- chord_metadata_service/mcode/models.py | 3 - chord_metadata_service/mcode/serializers.py | 5 +- .../patients/serializers.py | 1 + chord_metadata_service/restapi/schemas.py | 62 ++++++++++++++----- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index e3129d242..7b687fa6d 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -98,7 +98,6 @@ class LabsVital(models.Model, IndexableMixin): Class to record tests performed on patient. """ # TODO Should this class be a part of Patients app? patient related metadata - # TODO the data value should be in form of Quantity datatype - ADD json schema for Quantity id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.LABS_VITAL, "id")) individual = models.ForeignKey(Individual, on_delete=models.CASCADE, @@ -223,5 +222,3 @@ class MedicationStatement(models.Model, IndexableMixin): def __str__(self): return str(self.id) - - diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index 30c7530c5..681b781a8 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -2,7 +2,7 @@ from chord_metadata_service.restapi.serializers import GenericSerializer from .models import * from chord_metadata_service.restapi.schemas import ( - ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY, TIME_OR_PERIOD + ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY, TIME_OR_PERIOD, TUMOR_MARKER_TEST ) from chord_metadata_service.restapi.validators import JsonSchemaValidator from jsonschema import Draft7Validator @@ -66,6 +66,9 @@ class LabsVitalSerializer(GenericSerializer): blood_pressure_systolic = serializers.JSONField( validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], allow_null=True, required=False) + tumor_marker_test = serializers.JSONField( + validators=[JsonSchemaValidator(schema=TUMOR_MARKER_TEST)], + allow_null=True, required=False) class Meta: model = LabsVital diff --git a/chord_metadata_service/patients/serializers.py b/chord_metadata_service/patients/serializers.py index b7bfa5345..cda74dc43 100644 --- a/chord_metadata_service/patients/serializers.py +++ b/chord_metadata_service/patients/serializers.py @@ -23,6 +23,7 @@ class IndividualSerializer(GenericSerializer): allow_null=True, required=False ) + #TODO add these fields to FHIR converter ? comorbid_condition = serializers.JSONField( validators=[JsonSchemaValidator(schema=COMORBID_CONDITION)], allow_null=True, diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index a83db3768..696fd0e5b 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -1,6 +1,3 @@ -from typing import List - - # Individual schemas for validation of JSONField values ################################ Phenopackets based schemas ################################ @@ -8,7 +5,7 @@ ALLELE_SCHEMA = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:allele_schema", "title": "Allele schema", "description": "Variant allele types", "type": "object", @@ -55,7 +52,7 @@ UPDATE_SCHEMA = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:update_schema", "title": "Updates schema", "description": "Schema to check incoming updates format", "type": "object", @@ -71,7 +68,7 @@ ONTOLOGY_CLASS = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:ontology_class_schema", "title": "Ontology class schema", "description": "todo", "type": "object", @@ -85,7 +82,7 @@ EXTERNAL_REFERENCE = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:external_reference_schema", "title": "External reference schema", "description": "The schema encodes information about an external reference.", "type": "object", @@ -99,7 +96,7 @@ EVIDENCE = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:evidence_schema", "title": "Evidence schema", "description": "The schema represents the evidence for an assertion such as an observation of a PhenotypicFeature.", "type": "object", @@ -132,6 +129,10 @@ AGE = {"type": "string", "description": "An ISO8601 string represent age."} AGE_RANGE = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "chord_metadata_service:age_range_schema", + "title": "Age schema", + "description": "An age range of a subject.", "type": "object", "properties": { "start": { @@ -153,7 +154,7 @@ AGE_OR_AGE_RANGE = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:age_or_age_range_schema", "title": "Age schema", "description": "An age object describing the age of the individual at the time of collection of biospecimens or " "phenotypic observations.", @@ -171,7 +172,7 @@ DISEASE_ONSET = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:disease_onset_schema", "title": "Onset age", "description": "Schema for the age of the onset of the disease.", "type": "object", @@ -189,12 +190,14 @@ ################################## mCode/FHIR based schemas ################################## +#TODO currently these schemas are not distiguished by their provenance: some of them are FHIR elements, +# some are aggregated from FHIR and mCODE # mCode/FHIR Quantity QUANTITY = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:quantity_schema", "title": "Quantity schema", "description": "Schema for the datatype Quantity.", "type": "object", @@ -223,7 +226,7 @@ CODEABLE_CONCEPT = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:codeable_concept_schema", "title": "Codeable Concept schema", "description": "Schema for the datatype Concept.", "type": "object", @@ -251,7 +254,7 @@ PERIOD = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:period_schema", "title": "Period", "description": "Period schema.", "type": "object", @@ -271,7 +274,7 @@ TIME_OR_PERIOD = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "chord_metadata_service:time_or_period", "title": "Time of Period", "description": "Time of Period schema.", "type": "object", @@ -287,6 +290,20 @@ } +RATIO = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "chord_metadata_service:ratio", + "title": "Ratio", + "description": "Ratio schema.", + "type": "object", + "properties": { + "numerator": QUANTITY, + "denominator": QUANTITY + }, + "additionalProperties": False +} + + def customize_schema(first_typeof: dict, second_typeof: dict, first_property: str, second_property: str, id: str=None, title: str=None, description: str=None, additionalProperties=False, required=None) -> dict: @@ -319,3 +336,20 @@ def customize_schema(first_typeof: dict, second_typeof: dict, first_property: st id="chord_metadata_service:complex_ontology_schema", title="Complex ontology", description="Complex object to combine data value and staging system.", required=["data_value"]) + + +TUMOR_MARKER_TEST = customize_schema(first_typeof=CODEABLE_CONCEPT, + second_typeof={ + "anyOf": [ + CODEABLE_CONCEPT, + QUANTITY, + RATIO + ] + }, + first_property="code", second_property="data_value", + id="chord_metadata_service:tumor_marker_test", + title="Tumor marker test", + description="Tumor marker test schema.", + required=["code"] + ) + From ada704b3004178a1c096e3a28fed1fa5df52df9b Mon Sep 17 00:00:00 2001 From: zxenia Date: Fri, 13 Mar 2020 15:49:09 -0400 Subject: [PATCH 34/95] add models tests for mcode --- chord_metadata_service/mcode/models.py | 2 + .../mcode/tests/__init__.py | 0 .../mcode/tests/constants.py | 140 ++++++++++++++++++ .../mcode/tests/test_models.py | 84 +++++++++++ 4 files changed, 226 insertions(+) create mode 100644 chord_metadata_service/mcode/tests/__init__.py create mode 100644 chord_metadata_service/mcode/tests/constants.py create mode 100644 chord_metadata_service/mcode/tests/test_models.py diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 7b687fa6d..7aec7361e 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -58,6 +58,7 @@ class GeneticVariantFound(models.Model, IndexableMixin): help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_hgvs_name")) variant_found_description = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_description")) + # loinc value set https://loinc.org/48002-0/ genomic_source_class = JSONField(blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "genomic_source_class")) @@ -104,6 +105,7 @@ class LabsVital(models.Model, IndexableMixin): help_text=rec_help(d.LABS_VITAL, "individual")) body_height = JSONField(help_text=rec_help(d.LABS_VITAL, "body_height")) body_weight = JSONField(help_text=rec_help(d.LABS_VITAL, "body_weight")) + # corresponds to DiagnosticReport.result - complex element, probably should be changed to Array of json cbc_with_auto_differential_panel = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.LABS_VITAL, "cbc_with_auto_differential_panel")) comprehensive_metabolic_2000 = ArrayField(models.CharField(max_length=200), blank=True, null=True, diff --git a/chord_metadata_service/mcode/tests/__init__.py b/chord_metadata_service/mcode/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/chord_metadata_service/mcode/tests/constants.py b/chord_metadata_service/mcode/tests/constants.py new file mode 100644 index 000000000..fd5ab8bcc --- /dev/null +++ b/chord_metadata_service/mcode/tests/constants.py @@ -0,0 +1,140 @@ +VALID_INDIVIDUAL = { + "id": "patient:1", + "taxonomy": { + "id": "NCBITaxon:9606", + "label": "human" + }, + "date_of_birth": "1960-01-01", + "age": { + "age": { + "start": { + "age": "P45Y" + }, + "end": { + "age": "P49Y" + } + } + }, + "sex": "FEMALE", + "active": True +} + +VALID_GENETIC_VARIANT_TESTED = { + "id": "variant_tested:01", + "method": { + "id": "C17003", + "label": "Polymerase Chain Reaction" + }, + "variant_tested_identifier": { + "id": "360448", + "label": "360448", + }, + "variant_tested_hgvs_name": [ + "NC_000007.13:g.55086734A>G", + "NC_000007.14:g.55019041A>G", + "NM_001346897.2:c.-237A>G", + "NM_001346898.2:c.-237A>G", + "NM_001346899.1:c.-237A>G", + "NM_001346941.2:c.-237A>G", + "NM_005228.5:c.-237A>G", + "NM_201282.2:c.-237A>G", + "NM_201283.1:c.-237A>G", + "NM_201284.2:c.-237A>G", + "LRG_304t1:c.-237A>G", + "LRG_304:g.5010A>G", + "NG_007726.3:g.5010A>G" + ], + "variant_tested_description": "single nucleotide variant", + "data_value": { + "id": "LA6576-8", + "label": "Positive", + } +} + +VALID_GENETIC_VARIANT_FOUND = { + "id": "variant_found:01", + "method": { + "id": "C17003", + "label": "Polymerase Chain Reaction" + }, + "variant_found_identifier": { + "id": "360448", + "label": "360448", + }, + "variant_found_hgvs_name": [ + "NC_000007.13:g.55086734A>G", + "NC_000007.14:g.55019041A>G", + "NM_001346897.2:c.-237A>G", + "NM_001346898.2:c.-237A>G", + "NM_001346899.1:c.-237A>G", + "NM_001346941.2:c.-237A>G", + "NM_005228.5:c.-237A>G", + "NM_201282.2:c.-237A>G", + "NM_201283.1:c.-237A>G", + "NM_201284.2:c.-237A>G", + "LRG_304t1:c.-237A>G", + "LRG_304:g.5010A>G", + "NG_007726.3:g.5010A>G" + ], + "variant_found_description": "single nucleotide variant", + "genomic_source_class": { + "id": "LA6684-0", + "label": "Somatic", + } +} + + +def valid_genetic_report(subject): + return { + "id": "genomics_report:01", + "test_name": { + "id": "GTR000567625.2", + "label": "PREVENTEST", + }, + "performing_ogranization_name": "Test organization", + "specimen_type": { + "id": "119342007 ", + "label": "SAL (Saliva)", + }, + "subject": subject, + } + + +def valid_labs_vital(individual): + return { + "id": "labs_vital:01", + "body_height": { + "value": 1.70, + "unit": "m" + }, + "body_weight": { + "value": 60, + "unit": "kg" + }, + "cbc_with_auto_differential_panel": ["Test"], + "comprehensive_metabolic_2000": ["Test"], + "blood_pressure_diastolic": { + "value": 80, + "unit": "mmHg" + }, + "blood_pressure_systolic": { + "value": 120, + "unit": "mmHg" + }, + "tumor_marker_test": { + "code": { + "coding": [ + { + "code": "50610-5", + "display": "Alpha-1-Fetoprotein", + "system": "https://loinc.org/" + } + ] + }, + "data_value": { + "value": 10, + "unit": "ng/mL" + } + }, + "individual": individual, + } diff --git a/chord_metadata_service/mcode/tests/test_models.py b/chord_metadata_service/mcode/tests/test_models.py new file mode 100644 index 000000000..9e5d5f233 --- /dev/null +++ b/chord_metadata_service/mcode/tests/test_models.py @@ -0,0 +1,84 @@ +from django.test import TestCase +from chord_metadata_service.patients.models import Individual +from ..models import * +from .constants import * + +class GeneticVariantTestedTest(TestCase): + """ Test module for GeneticVariantTested model """ + + def setUp(self): + GeneticVariantTested.objects.create(**VALID_GENETIC_VARIANT_TESTED) + + def test_variant_tested(self): + variant_tested = GeneticVariantTested.objects.get(id='variant_tested:01') + self.assertIsInstance(variant_tested.method, dict) + self.assertEqual(variant_tested.method['label'], 'Polymerase Chain Reaction') + self.assertEqual(variant_tested.variant_tested_identifier['id'], + variant_tested.variant_tested_identifier['label']) + self.assertIsInstance(variant_tested.variant_tested_hgvs_name, list) + self.assertIn("NM_001346897.2:c.-237A>G", variant_tested.variant_tested_hgvs_name) + self.assertIsInstance(variant_tested.variant_tested_description, str) + self.assertEqual(variant_tested.variant_tested_description, 'single nucleotide variant') + self.assertEqual(variant_tested.data_value['id'], 'LA6576-8') + self.assertEqual(variant_tested.data_value['label'], 'Positive') + + +class GeneticVariantFoundTest(TestCase): + """ Test module for GeneticVariantFound model """ + + def setUp(self): + GeneticVariantFound.objects.create(**VALID_GENETIC_VARIANT_FOUND) + + def test_variant_found(self): + variant_found = GeneticVariantFound.objects.get(id='variant_found:01') + self.assertIsInstance(variant_found.method, dict) + self.assertEqual(variant_found.method['label'], 'Polymerase Chain Reaction') + self.assertEqual(variant_found.variant_found_identifier['id'], + variant_found.variant_found_identifier['label']) + self.assertIsInstance(variant_found.variant_found_hgvs_name, list) + self.assertIn("NM_001346897.2:c.-237A>G", variant_found.variant_found_hgvs_name) + self.assertIsInstance(variant_found.variant_found_description, str) + self.assertEqual(variant_found.variant_found_description, 'single nucleotide variant') + self.assertEqual(variant_found.genomic_source_class['id'], 'LA6684-0') + self.assertEqual(variant_found.genomic_source_class['label'], 'Somatic') + + +class GenomicsReportTest(TestCase): + """ Test module for Genomics Report model """ + + def setUp(self): + self.individual = Individual.objects.create(**VALID_INDIVIDUAL) + self.variant_tested = GeneticVariantTested.objects.create(**VALID_GENETIC_VARIANT_TESTED) + self.variant_found = GeneticVariantFound.objects.create(**VALID_GENETIC_VARIANT_FOUND) + self.genomics_report = GenomicsReport.objects.create(**valid_genetic_report(self.individual)) + self.genomics_report.genetic_variant_tested.set([self.variant_tested]) + self.genomics_report.genetic_variant_found.set([self.variant_found]) + + def test_genomics_report(self): + genomics_report = GenomicsReport.objects.get(id='genomics_report:01') + self.assertEqual(genomics_report.test_name['id'], 'GTR000567625.2') + self.assertIsInstance(genomics_report.specimen_type, dict) + self.assertIsNotNone(genomics_report.genetic_variant_tested) + self.assertEqual(genomics_report.genetic_variant_tested.count(), 1) + self.assertEqual(genomics_report.genetic_variant_found.count(), 1) + self.assertEqual(genomics_report.subject, self.individual) + + +class LabsVitalTest(TestCase): + """ Test module for LabsVital model """ + + def setUp(self): + self.individual = Individual.objects.create(**VALID_INDIVIDUAL) + self.labs_vital = LabsVital.objects.create(**valid_labs_vital(self.individual)) + + def test_labs_vital(self): + labs_vital = LabsVital.objects.get(id='labs_vital:01') + self.assertEqual(labs_vital.body_height['value'], 1.70) + self.assertEqual(labs_vital.body_height['unit'], 'm') + self.assertEqual(labs_vital.body_weight['value'], 60) + self.assertEqual(labs_vital.blood_pressure_diastolic['value'], 80) + self.assertEqual(labs_vital.blood_pressure_systolic['value'], 120) + self.assertIsInstance(labs_vital.tumor_marker_test, dict) + self.assertIsInstance(labs_vital.tumor_marker_test['code']['coding'], list) + self.assertEqual(labs_vital.tumor_marker_test['data_value']['value'], 10) + From 92e1e259eb0fa9cd58014ca870327398211f5e24 Mon Sep 17 00:00:00 2001 From: zxenia Date: Fri, 13 Mar 2020 16:51:13 -0400 Subject: [PATCH 35/95] add tests for cancer condition and tnm staging --- chord_metadata_service/mcode/serializers.py | 1 + .../mcode/tests/constants.py | 78 +++++++++++++++++++ .../mcode/tests/test_models.py | 31 ++++++++ 3 files changed, 110 insertions(+) diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index 681b781a8..c660c250f 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -76,6 +76,7 @@ class Meta: class CancerConditionSerializer(GenericSerializer): + #TODO add body_location_code validator array of json clinical_status = serializers.JSONField( validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], allow_null=True, required=False) diff --git a/chord_metadata_service/mcode/tests/constants.py b/chord_metadata_service/mcode/tests/constants.py index fd5ab8bcc..f0ca6dffd 100644 --- a/chord_metadata_service/mcode/tests/constants.py +++ b/chord_metadata_service/mcode/tests/constants.py @@ -138,3 +138,81 @@ def valid_labs_vital(individual): }, "individual": individual, } + + +def valid_cancer_condition(subject): + return { + "id": "cancer_condition:01", + "condition_type": "primary", + "body_location_code": [ + { + "id": "442083009", + "label": "Anatomical or acquired body structure (body structure)" + } + ], + "clinical_status": { + "id": "active", + "label": "Active" + }, + "condition_code": { + "id": "404087009", + "label": "Carcinosarcoma of skin (disorder)", + }, + "date_of_diagnosis": "2018-11-13T20:20:39+00:00", + "histology_morphology_behavior": { + "id": "372147008", + "label": "Kaposi's sarcoma - category (morphologic abnormality)", + }, + "subject": subject, + } + + +def valid_tnm_staging(cancer_condition): + return { + "id": "tnm_staging:01", + "tnm_type": "clinical", + "stage_group": { + "data_value": { + "coding": [ + { + "code": "123", + "display": "test", + "system": "https://example.com/path/resource.txt#fragment" + } + ] + } + }, + "primary_tumor_category": { + "data_value": { + "coding": [ + { + "code": "123", + "display": "test", + "system": "https://example.com/path/resource.txt#fragment" + } + ] + } + }, + "regional_nodes_category": { + "data_value": { + "coding": [ + { + "code": "123", + "display": "test", + "system": "https://example.com/path/resource.txt#fragment" + } + ] + } + }, + "distant_metastases_category": { + "data_value": { + "coding": [ + { + "code": "123", + "display": "test" + } + ] + } + }, + "cancer_condition": cancer_condition, + } diff --git a/chord_metadata_service/mcode/tests/test_models.py b/chord_metadata_service/mcode/tests/test_models.py index 9e5d5f233..d3f71ea1b 100644 --- a/chord_metadata_service/mcode/tests/test_models.py +++ b/chord_metadata_service/mcode/tests/test_models.py @@ -82,3 +82,34 @@ def test_labs_vital(self): self.assertIsInstance(labs_vital.tumor_marker_test['code']['coding'], list) self.assertEqual(labs_vital.tumor_marker_test['data_value']['value'], 10) + +class CancerConditionTest(TestCase): + """ Test module for CancerCondition model """ + + def setUp(self): + self.subject = Individual.objects.create(**VALID_INDIVIDUAL) + self.cancer_condition = CancerCondition.objects.create(**valid_cancer_condition(self.subject)) + + def test_cancer_condition(self): + cancer_condition = CancerCondition.objects.get(id='cancer_condition:01') + self.assertEqual(cancer_condition.condition_type, 'primary') + self.assertIsInstance(cancer_condition.body_location_code, list) + self.assertEqual(cancer_condition.body_location_code[0]['id'], '442083009') + self.assertEqual(cancer_condition.clinical_status['id'], 'active') + condition_code_keys = [key for key in cancer_condition.condition_code.keys()] + self.assertEqual(condition_code_keys, ['id', 'label']) + self.assertEqual(cancer_condition.histology_morphology_behavior['id'], '372147008') + + +class TNMStagingTest(TestCase): + """ Test module for TNMstaging model """ + + def setUp(self): + self.subject = Individual.objects.create(**VALID_INDIVIDUAL) + self.cancer_condition = CancerCondition.objects.create(**valid_cancer_condition(self.subject)) + self.tnm_staging = TNMStaging.objects.create(**valid_tnm_staging(self.cancer_condition)) + + def test_tnm_staging(self): + tnm_staging = TNMStaging.objects.get(id='tnm_staging:01') + self.assertEqual(tnm_staging.tnm_type, 'clinical') + self.assertIsInstance(tnm_staging.stage_group['data_value']['coding'], list) From 7deadb792fec1f802b504994e3463438c8b81dad Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 18 Mar 2020 14:10:33 -0400 Subject: [PATCH 36/95] draft: experiments --- .../experiments/__init__.py | 0 chord_metadata_service/experiments/admin.py | 3 + chord_metadata_service/experiments/apps.py | 5 ++ .../experiments/migrations/__init__.py | 0 chord_metadata_service/experiments/models.py | 82 +++++++++++++++++++ chord_metadata_service/experiments/tests.py | 3 + chord_metadata_service/experiments/views.py | 3 + 7 files changed, 96 insertions(+) create mode 100644 chord_metadata_service/experiments/__init__.py create mode 100644 chord_metadata_service/experiments/admin.py create mode 100644 chord_metadata_service/experiments/apps.py create mode 100644 chord_metadata_service/experiments/migrations/__init__.py create mode 100644 chord_metadata_service/experiments/models.py create mode 100644 chord_metadata_service/experiments/tests.py create mode 100644 chord_metadata_service/experiments/views.py diff --git a/chord_metadata_service/experiments/__init__.py b/chord_metadata_service/experiments/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/chord_metadata_service/experiments/admin.py b/chord_metadata_service/experiments/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/chord_metadata_service/experiments/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/chord_metadata_service/experiments/apps.py b/chord_metadata_service/experiments/apps.py new file mode 100644 index 000000000..57d0133d3 --- /dev/null +++ b/chord_metadata_service/experiments/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ExperimentsConfig(AppConfig): + name = 'experiments' diff --git a/chord_metadata_service/experiments/migrations/__init__.py b/chord_metadata_service/experiments/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py new file mode 100644 index 000000000..7512bcd5e --- /dev/null +++ b/chord_metadata_service/experiments/models.py @@ -0,0 +1,82 @@ +from django.db import models +from django.core.validators import RegexValidator +from django.contrib.postgres.fields import JSONField, ArrayField +from chord_metadata_service.restapi.models import IndexableMixin + + +class Experiment(models.Model, IndexableMixin): + """ Class to store Experiment information """ + + LIBRARY_STRATEGY = ( + ('DNase-Hypersensitivity', 'DNase-Hypersensitivity'), + ('ATAC-seq', 'ATAC-seq'), + ('NOME-Seq', 'NOME-Seq'), + ('Bisulfite-Seq', 'Bisulfite-Seq'), + ('MeDIP-Seq', 'MeDIP-Seq'), + ('MRE-Seq', 'MRE-Seq'), + ('ChIP-Seq', 'ChIP-Seq'), + ('RNA-Seq', 'RNA-Seq'), + ('miRNA-Seq', 'miRNA-Seq'), + ('WGS', 'WGS'), + ) + + MOLECULE = ( + ('total RNA', 'total RNA'), + ('polyA RNA', 'polyA RNA') + ('cytoplasmic RNA', 'cytoplasmic RNA') + ('nuclear RNA', 'nuclear RNA') + ('small RNA', 'small RNA') + ('genomic DNA', 'genomic DNA') + ('protein', 'protein') + ('other', 'other') + ) + + id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the individual.') + + # FIXME(validate qc_flags.max_length) + # FIXME(validate experiment_ontology_curie.max_length) + # FIXME(validate molecule_ontology_curie.max_length) + + reference_registry_id = models.CharField(max_length=30, null=True, blank=True, help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.') + qc_flags = models.CharField(max_length=30, null=True, blank=True, help_text='Any quanlity control observations can be noted here. This field can be omitted if empty') + experiment_type = models.CharField(max_length=30, null=True, blank=True, help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').") + experiment_ontology_curie = models.CharField(max_length=30, null=True, blank=True, + validators=[RegexValidator(regex="^[A-Za-z]*:[A-Za-z0-9]*")], + help_text="(Ontology: OBI) links to experiment ontology information.") + molecule_ontology_curie = models.CharField(max_length=30, null=True, blank=True, + validators=[RegexValidator(regex="^[A-Za-z]*:[A-Za-z0-9]*")], + help_text="(Ontology: SO) links to molecule ontology information.") + molecule = models.CharField(choices=MOLECULE, max_length=20, null=True, blank=True, + help_text="(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.") + + library_strategy = models.CharField(choices=LIBRARY_STRATEGY, max_length=25, null=False, blank=False, + help_text="(Controlled Vocabulary) The assay used. These are defined within the SRA metadata specifications with a controlled vocabulary (e.g. ‘Bisulfite-Seq’, ‘RNA-Seq’, ‘ChIP-Seq’). For a complete list, see https://www.ebi.ac.uk/ena/submit/reads-library-strategy.") + + + + + # TODO check for CURIE + alternate_ids = ArrayField(models.CharField(max_length=200), blank=True, null=True, + help_text='A list of alternative identifiers for the individual.') + date_of_birth = models.DateField(null=True, blank=True, help_text='A timestamp either exact or imprecise.') + # An ISO8601 string represent age + age = JSONField(blank=True, null=True, help_text='The age or age range of the individual.') + sex = models.CharField(choices=SEX, max_length=200, blank=True, null=True, + help_text='Observed apparent sex of the individual.') + karyotypic_sex = models.CharField(choices=KARYOTYPIC_SEX, max_length=200, default='UNKNOWN_KARYOTYPE', + help_text='The karyotypic sex of the individual.') + taxonomy = JSONField(blank=True, null=True, + help_text='Ontology resource representing the species (e.g., NCBITaxon:9615).') + # FHIR specific + active = models.BooleanField(default=False, help_text='Whether this patient\'s record is in active use.') + deceased = models.BooleanField(default=False, help_text='Indicates if the individual is deceased or not.') + # mCode specific + race = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s race.') + ethnicity = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s ethnicity.') + extra_properties = JSONField(blank=True, null=True, + help_text='Extra properties that are not supported by current schema') + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now=True) + + def __str__(self): + return str(self.id) diff --git a/chord_metadata_service/experiments/tests.py b/chord_metadata_service/experiments/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/chord_metadata_service/experiments/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/chord_metadata_service/experiments/views.py b/chord_metadata_service/experiments/views.py new file mode 100644 index 000000000..91ea44a21 --- /dev/null +++ b/chord_metadata_service/experiments/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 84132c4a7a0a3b456fbe8065ad519ca93d2cfa08 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 20 Mar 2020 14:24:10 -0400 Subject: [PATCH 37/95] experiments: remove unused files --- chord_metadata_service/experiments/admin.py | 3 --- chord_metadata_service/experiments/apps.py | 2 +- chord_metadata_service/experiments/models.py | 26 -------------------- chord_metadata_service/experiments/tests.py | 3 --- chord_metadata_service/experiments/views.py | 3 --- 5 files changed, 1 insertion(+), 36 deletions(-) delete mode 100644 chord_metadata_service/experiments/admin.py delete mode 100644 chord_metadata_service/experiments/tests.py delete mode 100644 chord_metadata_service/experiments/views.py diff --git a/chord_metadata_service/experiments/admin.py b/chord_metadata_service/experiments/admin.py deleted file mode 100644 index 8c38f3f3d..000000000 --- a/chord_metadata_service/experiments/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/chord_metadata_service/experiments/apps.py b/chord_metadata_service/experiments/apps.py index 57d0133d3..6b7b7a77e 100644 --- a/chord_metadata_service/experiments/apps.py +++ b/chord_metadata_service/experiments/apps.py @@ -2,4 +2,4 @@ class ExperimentsConfig(AppConfig): - name = 'experiments' + name = 'chord_metadata_service.experiments' diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 7512bcd5e..d6e770be5 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -52,31 +52,5 @@ class Experiment(models.Model, IndexableMixin): library_strategy = models.CharField(choices=LIBRARY_STRATEGY, max_length=25, null=False, blank=False, help_text="(Controlled Vocabulary) The assay used. These are defined within the SRA metadata specifications with a controlled vocabulary (e.g. ‘Bisulfite-Seq’, ‘RNA-Seq’, ‘ChIP-Seq’). For a complete list, see https://www.ebi.ac.uk/ena/submit/reads-library-strategy.") - - - - # TODO check for CURIE - alternate_ids = ArrayField(models.CharField(max_length=200), blank=True, null=True, - help_text='A list of alternative identifiers for the individual.') - date_of_birth = models.DateField(null=True, blank=True, help_text='A timestamp either exact or imprecise.') - # An ISO8601 string represent age - age = JSONField(blank=True, null=True, help_text='The age or age range of the individual.') - sex = models.CharField(choices=SEX, max_length=200, blank=True, null=True, - help_text='Observed apparent sex of the individual.') - karyotypic_sex = models.CharField(choices=KARYOTYPIC_SEX, max_length=200, default='UNKNOWN_KARYOTYPE', - help_text='The karyotypic sex of the individual.') - taxonomy = JSONField(blank=True, null=True, - help_text='Ontology resource representing the species (e.g., NCBITaxon:9615).') - # FHIR specific - active = models.BooleanField(default=False, help_text='Whether this patient\'s record is in active use.') - deceased = models.BooleanField(default=False, help_text='Indicates if the individual is deceased or not.') - # mCode specific - race = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s race.') - ethnicity = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s ethnicity.') - extra_properties = JSONField(blank=True, null=True, - help_text='Extra properties that are not supported by current schema') - created = models.DateTimeField(auto_now_add=True) - updated = models.DateTimeField(auto_now=True) - def __str__(self): return str(self.id) diff --git a/chord_metadata_service/experiments/tests.py b/chord_metadata_service/experiments/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/chord_metadata_service/experiments/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/chord_metadata_service/experiments/views.py b/chord_metadata_service/experiments/views.py deleted file mode 100644 index 91ea44a21..000000000 --- a/chord_metadata_service/experiments/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. From 370aaa5a73fc3312618531f54bf8f897b21ab82a Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 20 Mar 2020 14:40:07 -0400 Subject: [PATCH 38/95] experiments: add missing commas --- chord_metadata_service/experiments/models.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index d6e770be5..7084eee36 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -22,13 +22,13 @@ class Experiment(models.Model, IndexableMixin): MOLECULE = ( ('total RNA', 'total RNA'), - ('polyA RNA', 'polyA RNA') - ('cytoplasmic RNA', 'cytoplasmic RNA') - ('nuclear RNA', 'nuclear RNA') - ('small RNA', 'small RNA') - ('genomic DNA', 'genomic DNA') - ('protein', 'protein') - ('other', 'other') + ('polyA RNA', 'polyA RNA'), + ('cytoplasmic RNA', 'cytoplasmic RNA'), + ('nuclear RNA', 'nuclear RNA'), + ('small RNA', 'small RNA'), + ('genomic DNA', 'genomic DNA'), + ('protein', 'protein'), + ('other', 'other'), ) id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the individual.') From ba44819e8e731852e0bc446f0d51841ec4642498 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 20 Mar 2020 14:40:58 -0400 Subject: [PATCH 39/95] add experiments to installed apps --- chord_metadata_service/metadata/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/chord_metadata_service/metadata/settings.py b/chord_metadata_service/metadata/settings.py index d027a9a69..1691bdd4d 100644 --- a/chord_metadata_service/metadata/settings.py +++ b/chord_metadata_service/metadata/settings.py @@ -62,6 +62,7 @@ 'django.contrib.staticfiles', 'chord_metadata_service.chord', + 'chord_metadata_service.experiments.apps.ExperimentsConfig', 'chord_metadata_service.patients.apps.PatientsConfig', 'chord_metadata_service.phenopackets.apps.PhenopacketsConfig', 'chord_metadata_service.restapi', From 340e4081c489c3e36d3f189525927676cdd5c1d4 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 20 Mar 2020 14:41:09 -0400 Subject: [PATCH 40/95] experiments: create initial migration --- .../experiments/migrations/0001_initial.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 chord_metadata_service/experiments/migrations/0001_initial.py diff --git a/chord_metadata_service/experiments/migrations/0001_initial.py b/chord_metadata_service/experiments/migrations/0001_initial.py new file mode 100644 index 000000000..db0871dfe --- /dev/null +++ b/chord_metadata_service/experiments/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 2.2.11 on 2020-03-20 18:40 + +import chord_metadata_service.restapi.models +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Experiment', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the individual.', max_length=200, primary_key=True, serialize=False)), + ('reference_registry_id', models.CharField(blank=True, help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.', max_length=30, null=True)), + ('qc_flags', models.CharField(blank=True, help_text='Any quanlity control observations can be noted here. This field can be omitted if empty', max_length=30, null=True)), + ('experiment_type', models.CharField(blank=True, help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').", max_length=30, null=True)), + ('experiment_ontology_curie', models.CharField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', max_length=30, null=True, validators=[django.core.validators.RegexValidator(regex='^[A-Za-z]*:[A-Za-z0-9]*')])), + ('molecule_ontology_curie', models.CharField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', max_length=30, null=True, validators=[django.core.validators.RegexValidator(regex='^[A-Za-z]*:[A-Za-z0-9]*')])), + ('molecule', models.CharField(blank=True, choices=[('total RNA', 'total RNA'), ('polyA RNA', 'polyA RNA'), ('cytoplasmic RNA', 'cytoplasmic RNA'), ('nuclear RNA', 'nuclear RNA'), ('small RNA', 'small RNA'), ('genomic DNA', 'genomic DNA'), ('protein', 'protein'), ('other', 'other')], help_text='(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.', max_length=20, null=True)), + ('library_strategy', models.CharField(choices=[('DNase-Hypersensitivity', 'DNase-Hypersensitivity'), ('ATAC-seq', 'ATAC-seq'), ('NOME-Seq', 'NOME-Seq'), ('Bisulfite-Seq', 'Bisulfite-Seq'), ('MeDIP-Seq', 'MeDIP-Seq'), ('MRE-Seq', 'MRE-Seq'), ('ChIP-Seq', 'ChIP-Seq'), ('RNA-Seq', 'RNA-Seq'), ('miRNA-Seq', 'miRNA-Seq'), ('WGS', 'WGS')], help_text='(Controlled Vocabulary) The assay used. These are defined within the SRA metadata specifications with a controlled vocabulary (e.g. ‘Bisulfite-Seq’, ‘RNA-Seq’, ‘ChIP-Seq’). For a complete list, see https://www.ebi.ac.uk/ena/submit/reads-library-strategy.', max_length=25)), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + ] From b67276127025ad92181075ccff76529e1bdad865 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Sun, 22 Mar 2020 19:51:19 -0400 Subject: [PATCH 41/95] finish tests for mcode models --- chord_metadata_service/mcode/models.py | 1 + .../mcode/tests/constants.py | 50 +++++++++++++++++++ .../mcode/tests/test_models.py | 37 ++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 7aec7361e..5396b54b5 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -212,6 +212,7 @@ class MedicationStatement(models.Model, IndexableMixin): """ id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.MEDICATION_STATEMENT, "id")) + # list http://hl7.org/fhir/us/core/STU3.1/ValueSet-us-core-medication-codes.html medication_code = JSONField(help_text=rec_help(d.MEDICATION_STATEMENT, "medication_code")) termination_reason = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "termination_reason")) diff --git a/chord_metadata_service/mcode/tests/constants.py b/chord_metadata_service/mcode/tests/constants.py index f0ca6dffd..7b538ba99 100644 --- a/chord_metadata_service/mcode/tests/constants.py +++ b/chord_metadata_service/mcode/tests/constants.py @@ -216,3 +216,53 @@ def valid_tnm_staging(cancer_condition): }, "cancer_condition": cancer_condition, } + + +def valid_cancer_related_procedure(subject): + return { + "id": "cancer_related_procedure:01", + "procedure_type": "radiation", + "code": { + "id": "33356009", + "label": "Betatron teleradiotherapy (procedure)" + }, + "occurence_time_or_period": { + "start": "2018-11-13T20:20:39+00:00", + "end": "2019-04-13T20:20:39+00:00" + }, + "target_body_site": [ + { + "id": "74805009", + "label": "Mammary gland sinus" + } + ], + "treatment_intent": { + "id": "373808002", + "label": "Curative - procedure intent" + }, + "subject": subject, + } + + +def valid_medication_statement(subject): + return { + "id": "medication_statement:01", + "medication_code": { + "id": "92052", + "label": "Verapamil Oral Tablet [Calan]" + }, + "termination_reason": [ + { + "id": "182992009", + "label": "Treatment completed" + } + ], + "treatment_intent": { + "id": "373808002", + "label": "Curative - procedure intent" + }, + "start_date": "2018-11-13T20:20:39+00:00", + "end_date": "2019-04-13T20:20:39+00:00", + "date_time": "2019-04-13T20:20:39+00:00", + "subject": subject, + } \ No newline at end of file diff --git a/chord_metadata_service/mcode/tests/test_models.py b/chord_metadata_service/mcode/tests/test_models.py index d3f71ea1b..6d6fca39e 100644 --- a/chord_metadata_service/mcode/tests/test_models.py +++ b/chord_metadata_service/mcode/tests/test_models.py @@ -1,3 +1,4 @@ +import datetime from django.test import TestCase from chord_metadata_service.patients.models import Individual from ..models import * @@ -113,3 +114,39 @@ def test_tnm_staging(self): tnm_staging = TNMStaging.objects.get(id='tnm_staging:01') self.assertEqual(tnm_staging.tnm_type, 'clinical') self.assertIsInstance(tnm_staging.stage_group['data_value']['coding'], list) + + +class CancerRelatedProcedureTest(TestCase): + """ Test module for CancerRelatedProcedure model """ + + def setUp(self): + self.subject = Individual.objects.create(**VALID_INDIVIDUAL) + self.cancer_related_procedure = CancerRelatedProcedure.objects.create( + **valid_cancer_related_procedure(self.subject) + ) + + def test_cancer_related_procedure(self): + cancer_related_procedure = CancerRelatedProcedure.objects.get(id='cancer_related_procedure:01') + self.assertEqual(cancer_related_procedure.procedure_type, 'radiation') + self.assertEqual(cancer_related_procedure.code['id'], '33356009') + self.assertEqual(cancer_related_procedure.occurence_time_or_period['start'], '2018-11-13T20:20:39+00:00') + self.assertIsInstance(cancer_related_procedure.target_body_site, list) + self.assertEqual(cancer_related_procedure.treatment_intent['label'], 'Curative - procedure intent') + + +class MedicationStatementTest(TestCase): + """ Test module for MedicationStatement model """ + + def setUp(self): + self.subject = Individual.objects.create(**VALID_INDIVIDUAL) + self.cancer_related_procedure = MedicationStatement.objects.create( + **valid_medication_statement(self.subject) + ) + + def test_cancer_related_procedure(self): + medication_statement = MedicationStatement.objects.get(id='medication_statement:01') + self.assertEqual(medication_statement.medication_code['id'], '92052') + self.assertIsInstance(medication_statement.termination_reason, list) + self.assertEqual(medication_statement.treatment_intent['label'], 'Curative - procedure intent') + for date in [medication_statement.start_date, medication_statement.end_date, medication_statement.date_time]: + self.assertIsInstance(date, datetime.datetime) From ae9507c28e95c7bcde331c2f12f2f6ee9298c955 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 23 Mar 2020 17:25:55 -0400 Subject: [PATCH 42/95] experiments: update model --- chord_metadata_service/experiments/models.py | 62 +++++++------------- 1 file changed, 21 insertions(+), 41 deletions(-) diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 7512bcd5e..37e68afb6 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -22,61 +22,41 @@ class Experiment(models.Model, IndexableMixin): MOLECULE = ( ('total RNA', 'total RNA'), - ('polyA RNA', 'polyA RNA') - ('cytoplasmic RNA', 'cytoplasmic RNA') - ('nuclear RNA', 'nuclear RNA') - ('small RNA', 'small RNA') - ('genomic DNA', 'genomic DNA') - ('protein', 'protein') - ('other', 'other') + ('polyA RNA', 'polyA RNA'), + ('cytoplasmic RNA', 'cytoplasmic RNA'), + ('nuclear RNA', 'nuclear RNA'), + ('small RNA', 'small RNA'), + ('genomic DNA', 'genomic DNA'), + ('protein', 'protein'), + ('other', 'other'), ) - id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the individual.') - # FIXME(validate qc_flags.max_length) # FIXME(validate experiment_ontology_curie.max_length) # FIXME(validate molecule_ontology_curie.max_length) + # FIXME(validation for minSize/maxSize for ArrayField) + + id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the individual.') - reference_registry_id = models.CharField(max_length=30, null=True, blank=True, help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.') - qc_flags = models.CharField(max_length=30, null=True, blank=True, help_text='Any quanlity control observations can be noted here. This field can be omitted if empty') - experiment_type = models.CharField(max_length=30, null=True, blank=True, help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').") - experiment_ontology_curie = models.CharField(max_length=30, null=True, blank=True, + reference_registry_id = models.CharField(max_length=30, null=True, blank=True, + help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.') + qc_flags = ArrayField(models.CharField(max_length=30, null=True, blank=True, + help_text='Any quanlity control observations can be noted here. This field can be omitted if empty')) + experiment_type = models.CharField(max_length=30, null=True, blank=True, + help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').") + experiment_ontology_curie = ArrayField(models.CharField(max_length=30, null=True, blank=True, validators=[RegexValidator(regex="^[A-Za-z]*:[A-Za-z0-9]*")], - help_text="(Ontology: OBI) links to experiment ontology information.") - molecule_ontology_curie = models.CharField(max_length=30, null=True, blank=True, + help_text="(Ontology: OBI) links to experiment ontology information.")) + molecule_ontology_curie = ArrayField(models.CharField(max_length=30, null=True, blank=True, validators=[RegexValidator(regex="^[A-Za-z]*:[A-Za-z0-9]*")], - help_text="(Ontology: SO) links to molecule ontology information.") + help_text="(Ontology: SO) links to molecule ontology information.")) molecule = models.CharField(choices=MOLECULE, max_length=20, null=True, blank=True, help_text="(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.") library_strategy = models.CharField(choices=LIBRARY_STRATEGY, max_length=25, null=False, blank=False, help_text="(Controlled Vocabulary) The assay used. These are defined within the SRA metadata specifications with a controlled vocabulary (e.g. ‘Bisulfite-Seq’, ‘RNA-Seq’, ‘ChIP-Seq’). For a complete list, see https://www.ebi.ac.uk/ena/submit/reads-library-strategy.") - - - - # TODO check for CURIE - alternate_ids = ArrayField(models.CharField(max_length=200), blank=True, null=True, - help_text='A list of alternative identifiers for the individual.') - date_of_birth = models.DateField(null=True, blank=True, help_text='A timestamp either exact or imprecise.') - # An ISO8601 string represent age - age = JSONField(blank=True, null=True, help_text='The age or age range of the individual.') - sex = models.CharField(choices=SEX, max_length=200, blank=True, null=True, - help_text='Observed apparent sex of the individual.') - karyotypic_sex = models.CharField(choices=KARYOTYPIC_SEX, max_length=200, default='UNKNOWN_KARYOTYPE', - help_text='The karyotypic sex of the individual.') - taxonomy = JSONField(blank=True, null=True, - help_text='Ontology resource representing the species (e.g., NCBITaxon:9615).') - # FHIR specific - active = models.BooleanField(default=False, help_text='Whether this patient\'s record is in active use.') - deceased = models.BooleanField(default=False, help_text='Indicates if the individual is deceased or not.') - # mCode specific - race = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s race.') - ethnicity = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s ethnicity.') - extra_properties = JSONField(blank=True, null=True, - help_text='Extra properties that are not supported by current schema') - created = models.DateTimeField(auto_now_add=True) - updated = models.DateTimeField(auto_now=True) + other_fields = JSONField(blank=True, null=True, help_text='The other fields for the experiment') def __str__(self): return str(self.id) From 70c34d5f4259b60ebbf300e1a7299aa61fb31d3b Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 23 Mar 2020 17:50:35 -0400 Subject: [PATCH 43/95] experiments: update initial migration --- .../experiments/migrations/0001_initial.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/chord_metadata_service/experiments/migrations/0001_initial.py b/chord_metadata_service/experiments/migrations/0001_initial.py index db0871dfe..f2083479a 100644 --- a/chord_metadata_service/experiments/migrations/0001_initial.py +++ b/chord_metadata_service/experiments/migrations/0001_initial.py @@ -1,6 +1,8 @@ -# Generated by Django 2.2.11 on 2020-03-20 18:40 +# Generated by Django 2.2.11 on 2020-03-23 21:50 import chord_metadata_service.restapi.models +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb import django.core.validators from django.db import migrations, models @@ -18,12 +20,13 @@ class Migration(migrations.Migration): fields=[ ('id', models.CharField(help_text='An arbitrary identifier for the individual.', max_length=200, primary_key=True, serialize=False)), ('reference_registry_id', models.CharField(blank=True, help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.', max_length=30, null=True)), - ('qc_flags', models.CharField(blank=True, help_text='Any quanlity control observations can be noted here. This field can be omitted if empty', max_length=30, null=True)), + ('qc_flags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, help_text='Any quanlity control observations can be noted here. This field can be omitted if empty', max_length=30, null=True), size=None)), ('experiment_type', models.CharField(blank=True, help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').", max_length=30, null=True)), - ('experiment_ontology_curie', models.CharField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', max_length=30, null=True, validators=[django.core.validators.RegexValidator(regex='^[A-Za-z]*:[A-Za-z0-9]*')])), - ('molecule_ontology_curie', models.CharField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', max_length=30, null=True, validators=[django.core.validators.RegexValidator(regex='^[A-Za-z]*:[A-Za-z0-9]*')])), + ('experiment_ontology_curie', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', max_length=30, null=True, validators=[django.core.validators.RegexValidator(regex='^[A-Za-z]*:[A-Za-z0-9]*')]), size=None)), + ('molecule_ontology_curie', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', max_length=30, null=True, validators=[django.core.validators.RegexValidator(regex='^[A-Za-z]*:[A-Za-z0-9]*')]), size=None)), ('molecule', models.CharField(blank=True, choices=[('total RNA', 'total RNA'), ('polyA RNA', 'polyA RNA'), ('cytoplasmic RNA', 'cytoplasmic RNA'), ('nuclear RNA', 'nuclear RNA'), ('small RNA', 'small RNA'), ('genomic DNA', 'genomic DNA'), ('protein', 'protein'), ('other', 'other')], help_text='(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.', max_length=20, null=True)), ('library_strategy', models.CharField(choices=[('DNase-Hypersensitivity', 'DNase-Hypersensitivity'), ('ATAC-seq', 'ATAC-seq'), ('NOME-Seq', 'NOME-Seq'), ('Bisulfite-Seq', 'Bisulfite-Seq'), ('MeDIP-Seq', 'MeDIP-Seq'), ('MRE-Seq', 'MRE-Seq'), ('ChIP-Seq', 'ChIP-Seq'), ('RNA-Seq', 'RNA-Seq'), ('miRNA-Seq', 'miRNA-Seq'), ('WGS', 'WGS')], help_text='(Controlled Vocabulary) The assay used. These are defined within the SRA metadata specifications with a controlled vocabulary (e.g. ‘Bisulfite-Seq’, ‘RNA-Seq’, ‘ChIP-Seq’). For a complete list, see https://www.ebi.ac.uk/ena/submit/reads-library-strategy.', max_length=25)), + ('other_fields', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The other fields for the experiment', null=True)), ], bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), From 6197a1b92cef671a98ddbdcd5923bae69f2d0c84 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 24 Mar 2020 16:18:04 -0400 Subject: [PATCH 44/95] restapi: cache jsonschema validator --- chord_metadata_service/restapi/validators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chord_metadata_service/restapi/validators.py b/chord_metadata_service/restapi/validators.py index 4b33a95d0..675b97831 100644 --- a/chord_metadata_service/restapi/validators.py +++ b/chord_metadata_service/restapi/validators.py @@ -1,15 +1,15 @@ from rest_framework import serializers from jsonschema import Draft7Validator - class JsonSchemaValidator(object): """ Custom class based validator to validate against Json schema for JSONField """ def __init__(self, schema): self.schema = schema + self.validator = Draft7Validator(self.schema) def __call__(self, value): - validation = Draft7Validator(self.schema).is_valid(value) + validation = self.validator.is_valid(value) if not validation: raise serializers.ValidationError("Not valid JSON schema for this field.") return value From 55984b536dd636e83a9a7905ceb3f52fff564140 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Tue, 24 Mar 2020 16:28:06 -0400 Subject: [PATCH 45/95] add mcodepacket model and its descriptions add migrations --- chord_metadata_service/mcode/descriptions.py | 25 +++++++---- .../migrations/0003_auto_20200324_1625.py | 44 +++++++++++++++++++ chord_metadata_service/mcode/models.py | 31 ++++++++++--- 3 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 chord_metadata_service/mcode/migrations/0003_auto_20200324_1625.py diff --git a/chord_metadata_service/mcode/descriptions.py b/chord_metadata_service/mcode/descriptions.py index f45e64a2e..738a199a6 100644 --- a/chord_metadata_service/mcode/descriptions.py +++ b/chord_metadata_service/mcode/descriptions.py @@ -52,8 +52,7 @@ "specimen_type": "An ontology or controlled vocabulary term to identify the type of material the specimen " "contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.", "genetic_variant_tested": "A test for a specific mutation on a particular gene.", - "genetic_variant_found": "Records an alteration in the most common DNA nucleotide sequence.", - "subject": "Subject (Patient) of genomics report." + "genetic_variant_found": "Records an alteration in the most common DNA nucleotide sequence." } } @@ -91,8 +90,7 @@ "date_of_diagnosis": "The date the disease was first clinically recognized with sufficient certainty, " "regardless of whether it was fully characterized at that time.", "histology_morphology_behavior": "A description of the morphologic and behavioral characteristics of " - "the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others.", - "subject": "The subject (Patient) of the study that has a cancer condition." + "the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others." } } @@ -123,8 +121,7 @@ "code": "Code for the procedure performed.", "occurence_time_or_period": "The date/time that a procedure was performed.", "target_body_site": "The body location(s) where the procedure was performed.", - "treatment_intent": "The purpose of a treatment.", - "subject": "The patient who has a cancer condition." + "treatment_intent": "The purpose of a treatment." } } @@ -139,7 +136,19 @@ "treatment_intent": "The purpose of a treatment. Accepted ontologies: SNOMED CT.", "start_date": "The start date/time of the medication.", "end_date": "The end date/time of the medication.", - "date_time": "The date/time the medication was administered.", - "subject": "Subject of medication statement." + "date_time": "The date/time the medication was administered." + } +} + + +MCODEPACKET = { + "description": "Collection of cancer related metadata.", + "properties": { + "id": "An arbitrary identifier for the mcodepacket.", + "subject": "An individual who is a subject of mcodepacket.", + "genomics_report": "A genomics report associated with an Individual.", + "cancer_condition": "An Individual's cancer condition.", + "cancer_related_procedures": "A radiological or surgical procedures addressing a cancer condition.", + "medication_statement": "Medication treatment addressed to an Individual." } } diff --git a/chord_metadata_service/mcode/migrations/0003_auto_20200324_1625.py b/chord_metadata_service/mcode/migrations/0003_auto_20200324_1625.py new file mode 100644 index 000000000..80877efda --- /dev/null +++ b/chord_metadata_service/mcode/migrations/0003_auto_20200324_1625.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.10 on 2020-03-24 20:25 + +import chord_metadata_service.restapi.models +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('patients', '0005_auto_20200311_1610'), + ('mcode', '0002_auto_20200311_1610'), + ] + + operations = [ + migrations.RemoveField( + model_name='cancercondition', + name='subject', + ), + migrations.RemoveField( + model_name='cancerrelatedprocedure', + name='subject', + ), + migrations.RemoveField( + model_name='genomicsreport', + name='subject', + ), + migrations.RemoveField( + model_name='medicationstatement', + name='subject', + ), + migrations.CreateModel( + name='MCodePacket', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the mcodepacket.', max_length=200, primary_key=True, serialize=False)), + ('cancer_condition', models.ForeignKey(blank=True, help_text="An Individual's cancer condition.", null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.CancerCondition')), + ('cancer_related_procedures', models.ManyToManyField(blank=True, help_text='A radiological or surgical procedures addressing a cancer condition.', to='mcode.CancerRelatedProcedure')), + ('genomics_report', models.ForeignKey(blank=True, help_text='A genomics report associated with an Individual.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.GenomicsReport')), + ('medication_statement', models.ForeignKey(blank=True, help_text='Medication treatment addressed to an Individual.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.MedicationStatement')), + ('subject', models.ForeignKey(help_text='An individual who is a subject of mcodepacket.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ], + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + ] diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 5396b54b5..b126261c1 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -85,7 +85,7 @@ class GenomicsReport(models.Model, IndexableMixin): help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_tested")) genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_found")) - subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.GENOMICS_REPORT, "subject")) + # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.GENOMICS_REPORT, "subject")) def __str__(self): return str(self.id) @@ -147,7 +147,7 @@ class CancerCondition(models.Model, IndexableMixin): help_text=rec_help(d.CANCER_CONDITION, "date_of_diagnosis")) histology_morphology_behavior = JSONField(blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "histology_morphology_behavior")) - subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.CANCER_CONDITION, "subject")) + # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.CANCER_CONDITION, "subject")) def __str__(self): return str(self.id) @@ -198,8 +198,8 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "target_body_site")) treatment_intent = JSONField(blank=True, null=True, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "treatment_intent")) - subject = models.ForeignKey(Individual, on_delete=models.CASCADE, - help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "subject")) + # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, + # help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "subject")) def __str__(self): return str(self.id) @@ -220,8 +220,29 @@ class MedicationStatement(models.Model, IndexableMixin): start_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "start_date")) end_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "end_date")) date_time = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "date_time")) + # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, + # help_text=rec_help(d.MEDICATION_STATEMENT, "subject")) + + def __str__(self): + return str(self.id) + + +class MCodePacket(models.Model, IndexableMixin): + """ + Class to aggregate Individual's cancer related metadata + """ + + id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.MCODEPACKET, "id")) subject = models.ForeignKey(Individual, on_delete=models.CASCADE, - help_text=rec_help(d.MEDICATION_STATEMENT, "subject")) + help_text=rec_help(d.MCODEPACKET, "subject")) + genomics_report = models.ForeignKey(GenomicsReport, blank=True, null=True, on_delete=models.SET_NULL, + help_text=rec_help(d.MCODEPACKET, "genomics_report")) + cancer_condition = models.ForeignKey(CancerCondition, blank=True, null=True, on_delete=models.SET_NULL, + help_text=rec_help(d.MCODEPACKET, "cancer_condition")) + cancer_related_procedures = models.ManyToManyField(CancerRelatedProcedure, blank=True, + help_text=rec_help(d.MCODEPACKET, "cancer_related_procedures")) + medication_statement = models.ForeignKey(MedicationStatement, blank=True, null=True, on_delete=models.SET_NULL, + help_text=rec_help(d.MCODEPACKET, "medication_statement")) def __str__(self): return str(self.id) From 9d2b3e7597e010e6d58167470e75d8bcbabc0f91 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Tue, 24 Mar 2020 16:34:30 -0400 Subject: [PATCH 46/95] add api_view, serializer, url for mcodepacket --- chord_metadata_service/mcode/api_views.py | 4 ++++ chord_metadata_service/mcode/serializers.py | 9 ++++++++- chord_metadata_service/restapi/urls.py | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/chord_metadata_service/mcode/api_views.py b/chord_metadata_service/mcode/api_views.py index bac1e92e7..c805936bb 100644 --- a/chord_metadata_service/mcode/api_views.py +++ b/chord_metadata_service/mcode/api_views.py @@ -51,3 +51,7 @@ class MedicationStatementViewSet(McodeModelViewSet): queryset = MedicationStatement.objects.all().order_by("id") serializer_class = MedicationStatementSerializer + +class MCodePacketViewSet(McodeModelViewSet): + queryset = MCodePacket.objects.all().order_by("id") + serializer_class = MCodePacketSerializer diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index c660c250f..b51b13d89 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -162,4 +162,11 @@ def validate_termination_reason(self, value): class Meta: model = MedicationStatement - fields = '__all__' \ No newline at end of file + fields = '__all__' + + +class MCodePacketSerializer(GenericSerializer): + + class Meta: + model = MCodePacket + fields = '__all__' diff --git a/chord_metadata_service/restapi/urls.py b/chord_metadata_service/restapi/urls.py index 290827db3..3509d90a2 100644 --- a/chord_metadata_service/restapi/urls.py +++ b/chord_metadata_service/restapi/urls.py @@ -41,6 +41,7 @@ router.register(r'tnmstaging', mcode_views.TNMStagingViewSet) router.register(r'cancerrelatedprocedures', mcode_views.CancerRelatedProcedureViewSet) router.register(r'medicationstatements', mcode_views.MedicationStatementViewSet) +router.register(r'mcodepackets', mcode_views.MCodePacketViewSet) urlpatterns = [ path('', include(router.urls)), From 6a504cc88a1ab898a0eaa71ad2f3eced16e5825b Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 24 Mar 2020 16:44:44 -0400 Subject: [PATCH 47/95] schema: add additional schemas --- chord_metadata_service/restapi/schemas.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 653ec0170..062b146b9 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -65,7 +65,7 @@ ONTOLOGY_CLASS = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "todo", + "$id": "ONTOLOGY_CLASS", "title": "Ontology class schema", "description": "todo", "type": "object", @@ -77,6 +77,15 @@ "required": ["id", "label"] } +ONTOLOGY_CLASS_LIST = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "ONTOLOGY_CLASS_LIST", + "title": "Ontology class list", + "description": "Ontology class list", + "type": "array", + "items": ONTOLOGY_CLASS, +} + EXTERNAL_REFERENCE = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "todo", @@ -123,6 +132,18 @@ "required": ["evidence_code"] } +KEY_VALUE_OBJECT = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "KEY_VALUE_OBJECT", + "title": "Key-value object", + "description": "The schema represents a key-value object.", + "type": "object", + "patternProperties": { + "^.*$": { "type": "string" } + }, + "additionalProperties": False +} + AGE = {"type": "string", "description": "An ISO8601 string represent age."} From d66d9dc06c5e0df43e6d37b6cd86b6a8056db41a Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 24 Mar 2020 16:45:13 -0400 Subject: [PATCH 48/95] validators: make JsonSchemaValidator serializable --- chord_metadata_service/restapi/validators.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/chord_metadata_service/restapi/validators.py b/chord_metadata_service/restapi/validators.py index 675b97831..5ed353147 100644 --- a/chord_metadata_service/restapi/validators.py +++ b/chord_metadata_service/restapi/validators.py @@ -13,3 +13,13 @@ def __call__(self, value): if not validation: raise serializers.ValidationError("Not valid JSON schema for this field.") return value + + def __eq__(self, other): + return self.schema == other.schema + + def deconstruct(self): + return ( + 'chord_metadata_service.restapi.validators.JsonSchemaValidator', + [self.schema], + {} + ) From bcd2f7388ce4a650b8b8764d5e7a6aebef759cc0 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 24 Mar 2020 16:52:36 -0400 Subject: [PATCH 49/95] experiments: update model --- .../experiments/descriptions.py | 20 ++++++++ .../experiments/migrations/0001_initial.py | 14 +++--- chord_metadata_service/experiments/models.py | 46 ++++++++----------- 3 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 chord_metadata_service/experiments/descriptions.py diff --git a/chord_metadata_service/experiments/descriptions.py b/chord_metadata_service/experiments/descriptions.py new file mode 100644 index 000000000..7e620fc3b --- /dev/null +++ b/chord_metadata_service/experiments/descriptions.py @@ -0,0 +1,20 @@ +from chord_metadata_service.restapi.description_utils import EXTRA_PROPERTIES + +EXPERIMENT = { + "description": "A subject of a phenopacket, representing either a human (typically) or another organism.", + "properties": { + "id": "An arbitrary identifier for the experiment.", + + "reference_registry_id": "The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.", + "qc_flags": "Any quanlity control observations can be noted here. This field can be omitted if empty", + "experiment_type": "(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').", + "experiment_ontology": "(Ontology: OBI) links to experiment ontology information.", + "molecule_ontology": "(Ontology: SO) links to molecule ontology information.", + "molecule": "(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.", + "library_strategy": "(Controlled Vocabulary) The assay used. These are defined within the SRA metadata specifications with a controlled vocabulary (e.g. ‘Bisulfite-Seq’, ‘RNA-Seq’, ‘ChIP-Seq’). For a complete list, see https://www.ebi.ac.uk/ena/submit/reads-library-strategy.", + + "other_fields": "The other fields for the experiment", + + **EXTRA_PROPERTIES + } +} diff --git a/chord_metadata_service/experiments/migrations/0001_initial.py b/chord_metadata_service/experiments/migrations/0001_initial.py index f2083479a..c819bc9df 100644 --- a/chord_metadata_service/experiments/migrations/0001_initial.py +++ b/chord_metadata_service/experiments/migrations/0001_initial.py @@ -1,9 +1,9 @@ -# Generated by Django 2.2.11 on 2020-03-23 21:50 +# Generated by Django 2.2.11 on 2020-03-24 20:52 import chord_metadata_service.restapi.models +import chord_metadata_service.restapi.validators import django.contrib.postgres.fields import django.contrib.postgres.fields.jsonb -import django.core.validators from django.db import migrations, models @@ -18,15 +18,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Experiment', fields=[ - ('id', models.CharField(help_text='An arbitrary identifier for the individual.', max_length=200, primary_key=True, serialize=False)), + ('id', models.CharField(help_text='An arbitrary identifier for the experiment.', max_length=200, primary_key=True, serialize=False)), ('reference_registry_id', models.CharField(blank=True, help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.', max_length=30, null=True)), - ('qc_flags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, help_text='Any quanlity control observations can be noted here. This field can be omitted if empty', max_length=30, null=True), size=None)), + ('qc_flags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(help_text='Any quanlity control observations can be noted here. This field can be omitted if empty', max_length=100), default=list, null=True, size=None)), ('experiment_type', models.CharField(blank=True, help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').", max_length=30, null=True)), - ('experiment_ontology_curie', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', max_length=30, null=True, validators=[django.core.validators.RegexValidator(regex='^[A-Za-z]*:[A-Za-z0-9]*')]), size=None)), - ('molecule_ontology_curie', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', max_length=30, null=True, validators=[django.core.validators.RegexValidator(regex='^[A-Za-z]*:[A-Za-z0-9]*')]), size=None)), + ('experiment_ontology', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'ONTOLOGY_CLASS', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})])), + ('molecule_ontology', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'ONTOLOGY_CLASS', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})])), ('molecule', models.CharField(blank=True, choices=[('total RNA', 'total RNA'), ('polyA RNA', 'polyA RNA'), ('cytoplasmic RNA', 'cytoplasmic RNA'), ('nuclear RNA', 'nuclear RNA'), ('small RNA', 'small RNA'), ('genomic DNA', 'genomic DNA'), ('protein', 'protein'), ('other', 'other')], help_text='(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.', max_length=20, null=True)), ('library_strategy', models.CharField(choices=[('DNase-Hypersensitivity', 'DNase-Hypersensitivity'), ('ATAC-seq', 'ATAC-seq'), ('NOME-Seq', 'NOME-Seq'), ('Bisulfite-Seq', 'Bisulfite-Seq'), ('MeDIP-Seq', 'MeDIP-Seq'), ('MRE-Seq', 'MRE-Seq'), ('ChIP-Seq', 'ChIP-Seq'), ('RNA-Seq', 'RNA-Seq'), ('miRNA-Seq', 'miRNA-Seq'), ('WGS', 'WGS')], help_text='(Controlled Vocabulary) The assay used. These are defined within the SRA metadata specifications with a controlled vocabulary (e.g. ‘Bisulfite-Seq’, ‘RNA-Seq’, ‘ChIP-Seq’). For a complete list, see https://www.ebi.ac.uk/ena/submit/reads-library-strategy.', max_length=25)), - ('other_fields', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The other fields for the experiment', null=True)), + ('other_fields', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The other fields for the experiment', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'KEY_VALUE_OBJECT', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'The schema represents a key-value object.', 'patternProperties': {'^.*$': {'type': 'string'}}, 'title': 'Key-value object', 'type': 'object'})])), ], bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 37e68afb6..182e0ebd3 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -1,8 +1,16 @@ from django.db import models +from django.db.models import CharField from django.core.validators import RegexValidator +from django.core.exceptions import ValidationError from django.contrib.postgres.fields import JSONField, ArrayField from chord_metadata_service.restapi.models import IndexableMixin +from chord_metadata_service.restapi.description_utils import rec_help +from chord_metadata_service.restapi.validators import JsonSchemaValidator +from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT +import chord_metadata_service.experiments.descriptions as d +ontologyListValidator = JsonSchemaValidator(ONTOLOGY_CLASS_LIST) +keyValueValidator = JsonSchemaValidator(KEY_VALUE_OBJECT) class Experiment(models.Model, IndexableMixin): """ Class to store Experiment information """ @@ -31,32 +39,18 @@ class Experiment(models.Model, IndexableMixin): ('other', 'other'), ) - # FIXME(validate qc_flags.max_length) - # FIXME(validate experiment_ontology_curie.max_length) - # FIXME(validate molecule_ontology_curie.max_length) - # FIXME(validation for minSize/maxSize for ArrayField) - - id = models.CharField(primary_key=True, max_length=200, help_text='An arbitrary identifier for the individual.') - - reference_registry_id = models.CharField(max_length=30, null=True, blank=True, - help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.') - qc_flags = ArrayField(models.CharField(max_length=30, null=True, blank=True, - help_text='Any quanlity control observations can be noted here. This field can be omitted if empty')) - experiment_type = models.CharField(max_length=30, null=True, blank=True, - help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').") - experiment_ontology_curie = ArrayField(models.CharField(max_length=30, null=True, blank=True, - validators=[RegexValidator(regex="^[A-Za-z]*:[A-Za-z0-9]*")], - help_text="(Ontology: OBI) links to experiment ontology information.")) - molecule_ontology_curie = ArrayField(models.CharField(max_length=30, null=True, blank=True, - validators=[RegexValidator(regex="^[A-Za-z]*:[A-Za-z0-9]*")], - help_text="(Ontology: SO) links to molecule ontology information.")) - molecule = models.CharField(choices=MOLECULE, max_length=20, null=True, blank=True, - help_text="(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.") - - library_strategy = models.CharField(choices=LIBRARY_STRATEGY, max_length=25, null=False, blank=False, - help_text="(Controlled Vocabulary) The assay used. These are defined within the SRA metadata specifications with a controlled vocabulary (e.g. ‘Bisulfite-Seq’, ‘RNA-Seq’, ‘ChIP-Seq’). For a complete list, see https://www.ebi.ac.uk/ena/submit/reads-library-strategy.") - - other_fields = JSONField(blank=True, null=True, help_text='The other fields for the experiment') + id = CharField(primary_key=True, max_length=200, help_text=rec_help(d.EXPERIMENT, 'id')) + + reference_registry_id = CharField(max_length=30, null=True, blank=True, help_text=rec_help(d.EXPERIMENT, 'reference_registry_id')) + qc_flags = ArrayField(CharField(max_length=100, help_text=rec_help(d.EXPERIMENT, 'qc_flags')), null=True, default=list) + experiment_type = CharField(max_length=30, null=True, blank=True, help_text=rec_help(d.EXPERIMENT, 'experiment_type')) + experiment_ontology = JSONField(null=True, blank=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) + molecule_ontology = JSONField(null=True, blank=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) + molecule = CharField(choices=MOLECULE, max_length=20, null=True, blank=True, help_text=rec_help(d.EXPERIMENT, 'molecule')) + + library_strategy = CharField(choices=LIBRARY_STRATEGY, max_length=25, null=False, blank=False, help_text=rec_help(d.EXPERIMENT, 'library_strategy')) + + other_fields = JSONField(blank=True, null=True, validators=[keyValueValidator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) def __str__(self): return str(self.id) From 0f88cd9fd1fe866225a1557839346258b3fbb028 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 24 Mar 2020 16:52:51 -0400 Subject: [PATCH 50/95] experiments: add tests --- .../experiments/tests/__init__.py | 0 .../experiments/tests/test_models.py | 44 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 chord_metadata_service/experiments/tests/__init__.py create mode 100644 chord_metadata_service/experiments/tests/test_models.py diff --git a/chord_metadata_service/experiments/tests/__init__.py b/chord_metadata_service/experiments/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/chord_metadata_service/experiments/tests/test_models.py b/chord_metadata_service/experiments/tests/test_models.py new file mode 100644 index 000000000..b6f2f7cfa --- /dev/null +++ b/chord_metadata_service/experiments/tests/test_models.py @@ -0,0 +1,44 @@ +from django.test import TestCase +from rest_framework import serializers +from ..models import Experiment + + +class ExperimentTest(TestCase): + """ Test module for Experiment model """ + + def setUp(self): + Experiment.objects.create( + id='experiment:1', + reference_registry_id='', + qc_flags=['flag 1', 'flag 2'], + experiment_type='Chromatin Accessibility', + experiment_ontology=[{"id": "ontology:1", "label": "Ontology term 1"}], + molecule_ontology=[{"id": "ontology:1", "label": "Ontology term 1"}], + molecule='total RNA', + library_strategy='Bisulfite-Seq', + other_fields={"some_field": "value"} + ) + + def create(self, **kwargs): + e = Experiment(**kwargs) + e.full_clean() + e.save() + + def test_validation(self): + self.assertRaises(serializers.ValidationError, self.create, + id='experiment:2', + library_strategy='Bisulfite-Seq', + experiment_ontology=["invalid_value"], + ) + + self.assertRaises(serializers.ValidationError, self.create, + id='experiment:2', + library_strategy='Bisulfite-Seq', + molecule_ontology=[{"id": "some_id"}], + ) + + self.assertRaises(serializers.ValidationError, self.create, + id='experiment:2', + library_strategy='Bisulfite-Seq', + other_fields={"some_field": "value", "invalid_value": 42} + ) From ba800494bc99f227164155dddcd707863fd9332f Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 24 Mar 2020 16:57:46 -0400 Subject: [PATCH 51/95] validators: refactor: remove variable --- chord_metadata_service/restapi/validators.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chord_metadata_service/restapi/validators.py b/chord_metadata_service/restapi/validators.py index 5ed353147..0287e9879 100644 --- a/chord_metadata_service/restapi/validators.py +++ b/chord_metadata_service/restapi/validators.py @@ -9,8 +9,7 @@ def __init__(self, schema): self.validator = Draft7Validator(self.schema) def __call__(self, value): - validation = self.validator.is_valid(value) - if not validation: + if not self.validator.is_valid(value): raise serializers.ValidationError("Not valid JSON schema for this field.") return value From f97c21b531998bdc27a254c290bd58ad2b9355a4 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Tue, 24 Mar 2020 19:14:33 -0400 Subject: [PATCH 52/95] fix tests --- .../mcode/tests/constants.py | 20 ++++++++----------- .../mcode/tests/test_models.py | 16 +++++---------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/chord_metadata_service/mcode/tests/constants.py b/chord_metadata_service/mcode/tests/constants.py index 7b538ba99..54af4f0ea 100644 --- a/chord_metadata_service/mcode/tests/constants.py +++ b/chord_metadata_service/mcode/tests/constants.py @@ -84,7 +84,7 @@ } -def valid_genetic_report(subject): +def valid_genetic_report(): return { "id": "genomics_report:01", "test_name": { @@ -95,8 +95,7 @@ def valid_genetic_report(subject): "specimen_type": { "id": "119342007 ", "label": "SAL (Saliva)", - }, - "subject": subject, + } } @@ -140,7 +139,7 @@ def valid_labs_vital(individual): } -def valid_cancer_condition(subject): +def valid_cancer_condition(): return { "id": "cancer_condition:01", "condition_type": "primary", @@ -162,8 +161,7 @@ def valid_cancer_condition(subject): "histology_morphology_behavior": { "id": "372147008", "label": "Kaposi's sarcoma - category (morphologic abnormality)", - }, - "subject": subject, + } } @@ -218,7 +216,7 @@ def valid_tnm_staging(cancer_condition): } -def valid_cancer_related_procedure(subject): +def valid_cancer_related_procedure(): return { "id": "cancer_related_procedure:01", "procedure_type": "radiation", @@ -239,12 +237,11 @@ def valid_cancer_related_procedure(subject): "treatment_intent": { "id": "373808002", "label": "Curative - procedure intent" - }, - "subject": subject, + } } -def valid_medication_statement(subject): +def valid_medication_statement(): return { "id": "medication_statement:01", "medication_code": { @@ -263,6 +260,5 @@ def valid_medication_statement(subject): }, "start_date": "2018-11-13T20:20:39+00:00", "end_date": "2019-04-13T20:20:39+00:00", - "date_time": "2019-04-13T20:20:39+00:00", - "subject": subject, + "date_time": "2019-04-13T20:20:39+00:00" } \ No newline at end of file diff --git a/chord_metadata_service/mcode/tests/test_models.py b/chord_metadata_service/mcode/tests/test_models.py index 6d6fca39e..809ee2321 100644 --- a/chord_metadata_service/mcode/tests/test_models.py +++ b/chord_metadata_service/mcode/tests/test_models.py @@ -48,10 +48,9 @@ class GenomicsReportTest(TestCase): """ Test module for Genomics Report model """ def setUp(self): - self.individual = Individual.objects.create(**VALID_INDIVIDUAL) self.variant_tested = GeneticVariantTested.objects.create(**VALID_GENETIC_VARIANT_TESTED) self.variant_found = GeneticVariantFound.objects.create(**VALID_GENETIC_VARIANT_FOUND) - self.genomics_report = GenomicsReport.objects.create(**valid_genetic_report(self.individual)) + self.genomics_report = GenomicsReport.objects.create(**valid_genetic_report()) self.genomics_report.genetic_variant_tested.set([self.variant_tested]) self.genomics_report.genetic_variant_found.set([self.variant_found]) @@ -62,7 +61,6 @@ def test_genomics_report(self): self.assertIsNotNone(genomics_report.genetic_variant_tested) self.assertEqual(genomics_report.genetic_variant_tested.count(), 1) self.assertEqual(genomics_report.genetic_variant_found.count(), 1) - self.assertEqual(genomics_report.subject, self.individual) class LabsVitalTest(TestCase): @@ -88,8 +86,7 @@ class CancerConditionTest(TestCase): """ Test module for CancerCondition model """ def setUp(self): - self.subject = Individual.objects.create(**VALID_INDIVIDUAL) - self.cancer_condition = CancerCondition.objects.create(**valid_cancer_condition(self.subject)) + self.cancer_condition = CancerCondition.objects.create(**valid_cancer_condition()) def test_cancer_condition(self): cancer_condition = CancerCondition.objects.get(id='cancer_condition:01') @@ -106,8 +103,7 @@ class TNMStagingTest(TestCase): """ Test module for TNMstaging model """ def setUp(self): - self.subject = Individual.objects.create(**VALID_INDIVIDUAL) - self.cancer_condition = CancerCondition.objects.create(**valid_cancer_condition(self.subject)) + self.cancer_condition = CancerCondition.objects.create(**valid_cancer_condition()) self.tnm_staging = TNMStaging.objects.create(**valid_tnm_staging(self.cancer_condition)) def test_tnm_staging(self): @@ -120,9 +116,8 @@ class CancerRelatedProcedureTest(TestCase): """ Test module for CancerRelatedProcedure model """ def setUp(self): - self.subject = Individual.objects.create(**VALID_INDIVIDUAL) self.cancer_related_procedure = CancerRelatedProcedure.objects.create( - **valid_cancer_related_procedure(self.subject) + **valid_cancer_related_procedure() ) def test_cancer_related_procedure(self): @@ -138,9 +133,8 @@ class MedicationStatementTest(TestCase): """ Test module for MedicationStatement model """ def setUp(self): - self.subject = Individual.objects.create(**VALID_INDIVIDUAL) self.cancer_related_procedure = MedicationStatement.objects.create( - **valid_medication_statement(self.subject) + **valid_medication_statement() ) def test_cancer_related_procedure(self): From 5fed396efe0173a7e8249efa4e502b9a48d04e4a Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Wed, 25 Mar 2020 14:34:25 -0400 Subject: [PATCH 53/95] move validators to models --- chord_metadata_service/mcode/models.py | 63 ++++++++++++------- chord_metadata_service/mcode/serializers.py | 62 ------------------ .../mcode/tests/constants.py | 31 +++++++++ .../mcode/tests/test_models.py | 10 +++ chord_metadata_service/restapi/schemas.py | 4 +- 5 files changed, 85 insertions(+), 85 deletions(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index b126261c1..705d7d03a 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -6,8 +6,19 @@ from django.core.exceptions import ValidationError from chord_metadata_service.restapi.description_utils import rec_help import chord_metadata_service.mcode.descriptions as d +from chord_metadata_service.restapi.validators import JsonSchemaValidator +from chord_metadata_service.restapi.schemas import ( + ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY, TIME_OR_PERIOD, TUMOR_MARKER_TEST +) +############################ Field validators ############################# + +ontology_class_validator = JsonSchemaValidator(ONTOLOGY_CLASS) +quantity_validator = JsonSchemaValidator(schema=QUANTITY, format_checker=['uri']) +tumor_marker_test_validator = JsonSchemaValidator(schema=TUMOR_MARKER_TEST) +complex_ontology_validator = JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri']) + ################################# Genomics ################################# @@ -22,15 +33,16 @@ class GeneticVariantTested(models.Model, IndexableMixin): # make writable if it doesn't exist gene_studied = models.ForeignKey(Gene, blank=True, null=True, on_delete=models.SET_NULL, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "gene_studied")) - method = JSONField(blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "method")) - variant_tested_identifier = JSONField(blank=True, null=True, + method = JSONField(blank=True, null=True, validators=[ontology_class_validator], + help_text=rec_help(d.GENETIC_VARIANT_TESTED, "method")) + variant_tested_identifier = JSONField(blank=True, null=True, validators=[ontology_class_validator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_identifier")) variant_tested_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_hgvs_name")) variant_tested_description = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_description")) - data_value = JSONField(blank=True, null=True, + data_value = JSONField(blank=True, null=True, validators=[ontology_class_validator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "data_value")) def __str__(self): @@ -51,15 +63,16 @@ class GeneticVariantFound(models.Model, IndexableMixin): # TODO Discuss: Connection to Gene from Phenopackets id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "id")) - method = JSONField(blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "method")) - variant_found_identifier = JSONField(blank=True, null=True, + method = JSONField(blank=True, null=True, validators=[ontology_class_validator], + help_text=rec_help(d.GENETIC_VARIANT_FOUND, "method")) + variant_found_identifier = JSONField(blank=True, null=True, validators=[ontology_class_validator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_identifier")) variant_found_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_hgvs_name")) variant_found_description = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_description")) # loinc value set https://loinc.org/48002-0/ - genomic_source_class = JSONField(blank=True, null=True, + genomic_source_class = JSONField(blank=True, null=True, validators=[ontology_class_validator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "genomic_source_class")) def __str__(self): @@ -77,10 +90,11 @@ class GenomicsReport(models.Model, IndexableMixin): """ id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.GENOMICS_REPORT, "id")) - test_name = JSONField(help_text=rec_help(d.GENOMICS_REPORT, "test_name")) + test_name = JSONField(validators=[ontology_class_validator], help_text=rec_help(d.GENOMICS_REPORT, "test_name")) performing_ogranization_name = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENOMICS_REPORT, "performing_ogranization_name")) - specimen_type = JSONField(blank=True, null=True, help_text=rec_help(d.GENOMICS_REPORT, "specimen_type")) + specimen_type = JSONField(blank=True, null=True, validators=[ontology_class_validator], + help_text=rec_help(d.GENOMICS_REPORT, "specimen_type")) genetic_variant_tested = models.ManyToManyField(GeneticVariantTested, blank=True, help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_tested")) genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, @@ -103,19 +117,20 @@ class LabsVital(models.Model, IndexableMixin): help_text=rec_help(d.LABS_VITAL, "id")) individual = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.LABS_VITAL, "individual")) - body_height = JSONField(help_text=rec_help(d.LABS_VITAL, "body_height")) - body_weight = JSONField(help_text=rec_help(d.LABS_VITAL, "body_weight")) + body_height = JSONField(validators=[quantity_validator], help_text=rec_help(d.LABS_VITAL, "body_height")) + body_weight = JSONField(validators=[quantity_validator], help_text=rec_help(d.LABS_VITAL, "body_weight")) # corresponds to DiagnosticReport.result - complex element, probably should be changed to Array of json cbc_with_auto_differential_panel = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.LABS_VITAL, "cbc_with_auto_differential_panel")) comprehensive_metabolic_2000 = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.LABS_VITAL, "comprehensive_metabolic_2000")) - blood_pressure_diastolic = JSONField(blank=True, null=True, + blood_pressure_diastolic = JSONField(blank=True, null=True, validators=[quantity_validator], help_text=rec_help(d.LABS_VITAL, "blood_pressure_diastolic")) - blood_pressure_systolic = JSONField(blank=True, null=True, + blood_pressure_systolic = JSONField(blank=True, null=True, validators=[quantity_validator], help_text=rec_help(d.LABS_VITAL, "blood_pressure_systolic")) - #TODO Ontology or Quantity or Ratio (?) - tumor_marker_test = JSONField(help_text=rec_help(d.LABS_VITAL, "tumor_marker_test")) + #TODO Change CodeableConcept to Ontology class + tumor_marker_test = JSONField(validators=[tumor_marker_test_validator], + help_text=rec_help(d.LABS_VITAL, "tumor_marker_test")) def __str__(self): return str(self.id) @@ -139,13 +154,16 @@ class CancerCondition(models.Model, IndexableMixin): id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.CANCER_CONDITION, "id")) condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, max_length=200, help_text=rec_help(d.CANCER_CONDITION, "condition_type")) + # TODO add body_location_code validator array of json body_location_code = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "body_location_code")) - clinical_status = JSONField(blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "clinical_status")) - condition_code = JSONField(help_text=rec_help(d.CANCER_CONDITION, "condition_code")) + clinical_status = JSONField(blank=True, null=True, validators=[ontology_class_validator], + help_text=rec_help(d.CANCER_CONDITION, "clinical_status")) + condition_code = JSONField(validators=[ontology_class_validator], + help_text=rec_help(d.CANCER_CONDITION, "condition_code")) date_of_diagnosis = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "date_of_diagnosis")) - histology_morphology_behavior = JSONField(blank=True, null=True, + histology_morphology_behavior = JSONField(blank=True, null=True, validators=[ontology_class_validator], help_text=rec_help(d.CANCER_CONDITION, "histology_morphology_behavior")) # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.CANCER_CONDITION, "subject")) @@ -164,10 +182,13 @@ class TNMStaging(models.Model, IndexableMixin): ) id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.TNM_STAGING, "id")) tnm_type = models.CharField(choices=TNM_TYPES, max_length=200, help_text=rec_help(d.TNM_STAGING, "tnm_type")) - stage_group = JSONField(help_text=rec_help(d.TNM_STAGING, "stage_group")) - primary_tumor_category = JSONField(help_text=rec_help(d.TNM_STAGING, "primary_tumor_category")) - regional_nodes_category = JSONField(help_text=rec_help(d.TNM_STAGING, "regional_nodes_category")) - distant_metastases_category = JSONField(help_text=rec_help(d.TNM_STAGING, "distant_metastases_category")) + stage_group = JSONField(validators=[complex_ontology_validator], help_text=rec_help(d.TNM_STAGING, "stage_group")) + primary_tumor_category = JSONField(validators=[complex_ontology_validator], + help_text=rec_help(d.TNM_STAGING, "primary_tumor_category")) + regional_nodes_category = JSONField(validators=[complex_ontology_validator], + help_text=rec_help(d.TNM_STAGING, "regional_nodes_category")) + distant_metastases_category = JSONField(validators=[complex_ontology_validator], + help_text=rec_help(d.TNM_STAGING, "distant_metastases_category")) # TODO check if one cancer condition has many TNM Staging cancer_condition = models.ForeignKey(CancerCondition, on_delete=models.CASCADE, help_text=rec_help(d.TNM_STAGING, "cancer_condition")) diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index b51b13d89..25a72fcf0 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -9,15 +9,6 @@ class GeneticVariantTestedSerializer(GenericSerializer): - method = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - variant_tested_identifier = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - data_value = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) class Meta: model = GeneticVariantTested @@ -25,15 +16,6 @@ class Meta: class GeneticVariantFoundSerializer(GenericSerializer): - method = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - variant_found_identifier = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - genomic_source_class = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) class Meta: model = GeneticVariantFound @@ -41,12 +23,6 @@ class Meta: class GenomicsReportSerializer(GenericSerializer): - test_name = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - specimen_type = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) class Meta: model = GenomicsReport @@ -54,21 +30,6 @@ class Meta: class LabsVitalSerializer(GenericSerializer): - body_height = serializers.JSONField( - validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], - allow_null=True, required=False) - body_weight = serializers.JSONField( - validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], - allow_null=True, required=False) - blood_pressure_diastolic = serializers.JSONField( - validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], - allow_null=True, required=False) - blood_pressure_systolic = serializers.JSONField( - validators=[JsonSchemaValidator(schema=QUANTITY, format_checker=['uri'])], - allow_null=True, required=False) - tumor_marker_test = serializers.JSONField( - validators=[JsonSchemaValidator(schema=TUMOR_MARKER_TEST)], - allow_null=True, required=False) class Meta: model = LabsVital @@ -76,16 +37,6 @@ class Meta: class CancerConditionSerializer(GenericSerializer): - #TODO add body_location_code validator array of json - clinical_status = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - condition_code = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - histology_morphology_behavior = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) class Meta: model = CancerCondition @@ -101,20 +52,7 @@ def validate_body_location_code(self, value): class TNMStagingSerializer(GenericSerializer): - #TODO Complex Ontology needs format checker #TODO URI syntax examples for tests https://tools.ietf.org/html/rfc3986 - stage_group = serializers.JSONField( - validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri'])], - allow_null=True, required=False) - primary_tumor_category = serializers.JSONField( - validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri'])], - allow_null=True, required=False) - regional_nodes_category = serializers.JSONField( - validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri'])], - allow_null=True, required=False) - distant_metastases_category = serializers.JSONField( - validators=[JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri'])], - allow_null=True, required=False) class Meta: model = TNMStaging diff --git a/chord_metadata_service/mcode/tests/constants.py b/chord_metadata_service/mcode/tests/constants.py index 54af4f0ea..959b75772 100644 --- a/chord_metadata_service/mcode/tests/constants.py +++ b/chord_metadata_service/mcode/tests/constants.py @@ -51,6 +51,37 @@ } } + +INVALID_GENETIC_VARIANT_TESTED = { + "id": "variant_tested:02", + "method": ["invalid_value"], + "variant_tested_identifier": { + "id": "360448", + "label": "360448", + }, + "variant_tested_hgvs_name": [ + "NC_000007.13:g.55086734A>G", + "NC_000007.14:g.55019041A>G", + "NM_001346897.2:c.-237A>G", + "NM_001346898.2:c.-237A>G", + "NM_001346899.1:c.-237A>G", + "NM_001346941.2:c.-237A>G", + "NM_005228.5:c.-237A>G", + "NM_201282.2:c.-237A>G", + "NM_201283.1:c.-237A>G", + "NM_201284.2:c.-237A>G", + "LRG_304t1:c.-237A>G", + "LRG_304:g.5010A>G", + "NG_007726.3:g.5010A>G" + ], + "variant_tested_description": "single nucleotide variant", + "data_value": { + "id": "LA6576-8", + "label": "Positive", + } +} + + VALID_GENETIC_VARIANT_FOUND = { "id": "variant_found:01", "method": { diff --git a/chord_metadata_service/mcode/tests/test_models.py b/chord_metadata_service/mcode/tests/test_models.py index 809ee2321..1990eb8a8 100644 --- a/chord_metadata_service/mcode/tests/test_models.py +++ b/chord_metadata_service/mcode/tests/test_models.py @@ -3,6 +3,7 @@ from chord_metadata_service.patients.models import Individual from ..models import * from .constants import * +from rest_framework import serializers class GeneticVariantTestedTest(TestCase): """ Test module for GeneticVariantTested model """ @@ -23,6 +24,15 @@ def test_variant_tested(self): self.assertEqual(variant_tested.data_value['id'], 'LA6576-8') self.assertEqual(variant_tested.data_value['label'], 'Positive') + def create(self, **kwargs): + e = GeneticVariantTested(**kwargs) + e.full_clean() + e.save() + + def test_validation(self): + # invalid = GeneticVariantTested.objects.create(**INVALID_GENETIC_VARIANT_TESTED) + self.assertRaises(serializers.ValidationError, self.create, **INVALID_GENETIC_VARIANT_TESTED) + class GeneticVariantFoundTest(TestCase): """ Test module for GeneticVariantFound model """ diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 696fd0e5b..9777df718 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -330,14 +330,14 @@ def customize_schema(first_typeof: dict, second_typeof: dict, first_property: st title="Comorbid Condition schema", description="Comorbid condition schema.") - +#TODO this is definitely should be changed, fhir datatypes are too complex use Ontology_ class COMPLEX_ONTOLOGY = customize_schema(first_typeof=CODEABLE_CONCEPT, second_typeof=CODEABLE_CONCEPT, first_property="data_value", second_property="staging_system", id="chord_metadata_service:complex_ontology_schema", title="Complex ontology", description="Complex object to combine data value and staging system.", required=["data_value"]) - +#TODO this is definitely should be changed, fhir datatypes are too complex use Ontology_ class TUMOR_MARKER_TEST = customize_schema(first_typeof=CODEABLE_CONCEPT, second_typeof={ "anyOf": [ From d864f06849d5b00c1aab8569f3edd978323b1db2 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 25 Mar 2020 15:52:21 -0400 Subject: [PATCH 54/95] experiments: make experiment_type required --- chord_metadata_service/experiments/migrations/0001_initial.py | 4 ++-- chord_metadata_service/experiments/models.py | 2 +- chord_metadata_service/experiments/tests/test_models.py | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/chord_metadata_service/experiments/migrations/0001_initial.py b/chord_metadata_service/experiments/migrations/0001_initial.py index c819bc9df..b854ba022 100644 --- a/chord_metadata_service/experiments/migrations/0001_initial.py +++ b/chord_metadata_service/experiments/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.11 on 2020-03-24 20:52 +# Generated by Django 2.2.11 on 2020-03-25 19:50 import chord_metadata_service.restapi.models import chord_metadata_service.restapi.validators @@ -21,7 +21,7 @@ class Migration(migrations.Migration): ('id', models.CharField(help_text='An arbitrary identifier for the experiment.', max_length=200, primary_key=True, serialize=False)), ('reference_registry_id', models.CharField(blank=True, help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.', max_length=30, null=True)), ('qc_flags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(help_text='Any quanlity control observations can be noted here. This field can be omitted if empty', max_length=100), default=list, null=True, size=None)), - ('experiment_type', models.CharField(blank=True, help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').", max_length=30, null=True)), + ('experiment_type', models.CharField(help_text="(Controlled Vocabulary) The assay target (e.g. ‘DNA Methylation’, ‘mRNA-Seq’, ‘smRNA-Seq’, 'Histone H3K4me1').", max_length=30)), ('experiment_ontology', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'ONTOLOGY_CLASS', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})])), ('molecule_ontology', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'ONTOLOGY_CLASS', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})])), ('molecule', models.CharField(blank=True, choices=[('total RNA', 'total RNA'), ('polyA RNA', 'polyA RNA'), ('cytoplasmic RNA', 'cytoplasmic RNA'), ('nuclear RNA', 'nuclear RNA'), ('small RNA', 'small RNA'), ('genomic DNA', 'genomic DNA'), ('protein', 'protein'), ('other', 'other')], help_text='(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.', max_length=20, null=True)), diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 182e0ebd3..9a1d56e3c 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -43,7 +43,7 @@ class Experiment(models.Model, IndexableMixin): reference_registry_id = CharField(max_length=30, null=True, blank=True, help_text=rec_help(d.EXPERIMENT, 'reference_registry_id')) qc_flags = ArrayField(CharField(max_length=100, help_text=rec_help(d.EXPERIMENT, 'qc_flags')), null=True, default=list) - experiment_type = CharField(max_length=30, null=True, blank=True, help_text=rec_help(d.EXPERIMENT, 'experiment_type')) + experiment_type = CharField(max_length=30, null=False, blank=False, help_text=rec_help(d.EXPERIMENT, 'experiment_type')) experiment_ontology = JSONField(null=True, blank=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) molecule_ontology = JSONField(null=True, blank=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) molecule = CharField(choices=MOLECULE, max_length=20, null=True, blank=True, help_text=rec_help(d.EXPERIMENT, 'molecule')) diff --git a/chord_metadata_service/experiments/tests/test_models.py b/chord_metadata_service/experiments/tests/test_models.py index b6f2f7cfa..774cbd96d 100644 --- a/chord_metadata_service/experiments/tests/test_models.py +++ b/chord_metadata_service/experiments/tests/test_models.py @@ -28,17 +28,20 @@ def test_validation(self): self.assertRaises(serializers.ValidationError, self.create, id='experiment:2', library_strategy='Bisulfite-Seq', + experiment_type='Chromatin Accessibility', experiment_ontology=["invalid_value"], ) self.assertRaises(serializers.ValidationError, self.create, id='experiment:2', library_strategy='Bisulfite-Seq', + experiment_type='Chromatin Accessibility', molecule_ontology=[{"id": "some_id"}], ) self.assertRaises(serializers.ValidationError, self.create, id='experiment:2', library_strategy='Bisulfite-Seq', + experiment_type='Chromatin Accessibility', other_fields={"some_field": "value", "invalid_value": 42} ) From f83ccf1e8fd4ff9311d1e5729693ec0fd98e3a40 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Wed, 25 Mar 2020 18:34:36 -0400 Subject: [PATCH 55/95] move rest of the validators in models, fix in tests --- chord_metadata_service/mcode/models.py | 20 ++++++--- chord_metadata_service/mcode/serializers.py | 45 ------------------- .../mcode/tests/constants.py | 14 +++--- .../mcode/tests/test_models.py | 5 ++- 4 files changed, 23 insertions(+), 61 deletions(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 705d7d03a..7273183b2 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -18,6 +18,7 @@ quantity_validator = JsonSchemaValidator(schema=QUANTITY, format_checker=['uri']) tumor_marker_test_validator = JsonSchemaValidator(schema=TUMOR_MARKER_TEST) complex_ontology_validator = JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri']) +time_or_period_validator = JsonSchemaValidator(schema=TIME_OR_PERIOD, format_checker=['date-time']) ################################# Genomics ################################# @@ -155,7 +156,8 @@ class CancerCondition(models.Model, IndexableMixin): condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, max_length=200, help_text=rec_help(d.CANCER_CONDITION, "condition_type")) # TODO add body_location_code validator array of json - body_location_code = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, + body_location_code = ArrayField(JSONField(null=True, blank=True, validators=[ontology_class_validator]), + blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "body_location_code")) clinical_status = JSONField(blank=True, null=True, validators=[ontology_class_validator], help_text=rec_help(d.CANCER_CONDITION, "clinical_status")) @@ -213,11 +215,12 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "id")) procedure_type = models.CharField(choices=PROCEDURE_TYPES, max_length=200, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "procedure_type")) - code = JSONField(help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "code")) + code = JSONField(validators=[ontology_class_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "code")) occurence_time_or_period = JSONField(help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "occurence_time_or_period")) - target_body_site = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, + target_body_site = ArrayField(JSONField(null=True, blank=True, validators=[ontology_class_validator]), + blank=True, null=True, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "target_body_site")) - treatment_intent = JSONField(blank=True, null=True, + treatment_intent = JSONField(blank=True, null=True, validators=[ontology_class_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "treatment_intent")) # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, # help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "subject")) @@ -234,10 +237,13 @@ class MedicationStatement(models.Model, IndexableMixin): id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.MEDICATION_STATEMENT, "id")) # list http://hl7.org/fhir/us/core/STU3.1/ValueSet-us-core-medication-codes.html - medication_code = JSONField(help_text=rec_help(d.MEDICATION_STATEMENT, "medication_code")) - termination_reason = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, + medication_code = JSONField(validators=[ontology_class_validator], + help_text=rec_help(d.MEDICATION_STATEMENT, "medication_code")) + termination_reason = ArrayField(JSONField(null=True, blank=True, validators=[ontology_class_validator]), + blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "termination_reason")) - treatment_intent = JSONField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "treatment_intent")) + treatment_intent = JSONField(blank=True, null=True, validators=[ontology_class_validator], + help_text=rec_help(d.MEDICATION_STATEMENT, "treatment_intent")) start_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "start_date")) end_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "end_date")) date_time = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "date_time")) diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index 25a72fcf0..2b9a37c61 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -1,11 +1,5 @@ -from rest_framework import serializers from chord_metadata_service.restapi.serializers import GenericSerializer from .models import * -from chord_metadata_service.restapi.schemas import ( - ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY, TIME_OR_PERIOD, TUMOR_MARKER_TEST -) -from chord_metadata_service.restapi.validators import JsonSchemaValidator -from jsonschema import Draft7Validator class GeneticVariantTestedSerializer(GenericSerializer): @@ -42,17 +36,8 @@ class Meta: model = CancerCondition fields = '__all__' - def validate_body_location_code(self, value): - if isinstance(value, list): - for item in value: - validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) - if not validation: - raise serializers.ValidationError("Not valid JSON schema for this field.") - return value - class TNMStagingSerializer(GenericSerializer): - #TODO URI syntax examples for tests https://tools.ietf.org/html/rfc3986 class Meta: model = TNMStaging @@ -60,43 +45,13 @@ class Meta: class CancerRelatedProcedureSerializer(GenericSerializer): - code = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - occurence_time_or_period = serializers.JSONField( - validators=[JsonSchemaValidator(schema=TIME_OR_PERIOD, format_checker=['date-time'])], - allow_null=True, required=False) - treatment_intent = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) class Meta: model = CancerRelatedProcedure fields = '__all__' - def validate_target_body_site(self, value): - if isinstance(value, list): - for item in value: - validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) - if not validation: - raise serializers.ValidationError("Not valid JSON schema for this field.") - return value - class MedicationStatementSerializer(GenericSerializer): - medication_code = serializers.JSONField(validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) - treatment_intent = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - - def validate_termination_reason(self, value): - if isinstance(value, list): - for item in value: - validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) - if not validation: - raise serializers.ValidationError("Not valid JSON schema for this field.") - return value - class Meta: model = MedicationStatement diff --git a/chord_metadata_service/mcode/tests/constants.py b/chord_metadata_service/mcode/tests/constants.py index 959b75772..58ff181dd 100644 --- a/chord_metadata_service/mcode/tests/constants.py +++ b/chord_metadata_service/mcode/tests/constants.py @@ -51,7 +51,6 @@ } } - INVALID_GENETIC_VARIANT_TESTED = { "id": "variant_tested:02", "method": ["invalid_value"], @@ -81,7 +80,6 @@ } } - VALID_GENETIC_VARIANT_FOUND = { "id": "variant_found:01", "method": { @@ -186,12 +184,12 @@ def valid_cancer_condition(): }, "condition_code": { "id": "404087009", - "label": "Carcinosarcoma of skin (disorder)", + "label": "Carcinosarcoma of skin (disorder)" }, "date_of_diagnosis": "2018-11-13T20:20:39+00:00", "histology_morphology_behavior": { "id": "372147008", - "label": "Kaposi's sarcoma - category (morphologic abnormality)", + "label": "Kaposi's sarcoma - category (morphologic abnormality)" } } @@ -256,8 +254,10 @@ def valid_cancer_related_procedure(): "label": "Betatron teleradiotherapy (procedure)" }, "occurence_time_or_period": { - "start": "2018-11-13T20:20:39+00:00", - "end": "2019-04-13T20:20:39+00:00" + "value": { + "start": "2018-11-13T20:20:39+00:00", + "end": "2019-04-13T20:20:39+00:00" + } }, "target_body_site": [ { @@ -292,4 +292,4 @@ def valid_medication_statement(): "start_date": "2018-11-13T20:20:39+00:00", "end_date": "2019-04-13T20:20:39+00:00", "date_time": "2019-04-13T20:20:39+00:00" - } \ No newline at end of file + } diff --git a/chord_metadata_service/mcode/tests/test_models.py b/chord_metadata_service/mcode/tests/test_models.py index 1990eb8a8..3fb4e6232 100644 --- a/chord_metadata_service/mcode/tests/test_models.py +++ b/chord_metadata_service/mcode/tests/test_models.py @@ -30,7 +30,6 @@ def create(self, **kwargs): e.save() def test_validation(self): - # invalid = GeneticVariantTested.objects.create(**INVALID_GENETIC_VARIANT_TESTED) self.assertRaises(serializers.ValidationError, self.create, **INVALID_GENETIC_VARIANT_TESTED) @@ -112,6 +111,8 @@ def test_cancer_condition(self): class TNMStagingTest(TestCase): """ Test module for TNMstaging model """ + # TODO URI syntax examples for tests https://tools.ietf.org/html/rfc3986 + def setUp(self): self.cancer_condition = CancerCondition.objects.create(**valid_cancer_condition()) self.tnm_staging = TNMStaging.objects.create(**valid_tnm_staging(self.cancer_condition)) @@ -134,7 +135,7 @@ def test_cancer_related_procedure(self): cancer_related_procedure = CancerRelatedProcedure.objects.get(id='cancer_related_procedure:01') self.assertEqual(cancer_related_procedure.procedure_type, 'radiation') self.assertEqual(cancer_related_procedure.code['id'], '33356009') - self.assertEqual(cancer_related_procedure.occurence_time_or_period['start'], '2018-11-13T20:20:39+00:00') + self.assertEqual(cancer_related_procedure.occurence_time_or_period['value']['start'], '2018-11-13T20:20:39+00:00') self.assertIsInstance(cancer_related_procedure.target_body_site, list) self.assertEqual(cancer_related_procedure.treatment_intent['label'], 'Curative - procedure intent') From 094324d73477e5b97815d89b5826d3a3cfcccc09 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Wed, 25 Mar 2020 19:23:07 -0400 Subject: [PATCH 56/95] add migrations --- .../migrations/0004_auto_20200325_1920.py | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 chord_metadata_service/mcode/migrations/0004_auto_20200325_1920.py diff --git a/chord_metadata_service/mcode/migrations/0004_auto_20200325_1920.py b/chord_metadata_service/mcode/migrations/0004_auto_20200325_1920.py new file mode 100644 index 000000000..3e820e154 --- /dev/null +++ b/chord_metadata_service/mcode/migrations/0004_auto_20200325_1920.py @@ -0,0 +1,151 @@ +# Generated by Django 2.2.10 on 2020-03-25 23:20 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mcode', '0003_auto_20200324_1625'), + ] + + operations = [ + migrations.AlterField( + model_name='cancercondition', + name='body_location_code', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='Code for the body location, optionally pre-coordinating laterality or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True, size=None), + ), + migrations.AlterField( + model_name='cancercondition', + name='clinical_status', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A flag indicating whether the condition is active or inactive, recurring, in remission, or resolved (as of the last update of the Condition). Accepted code system: http://terminology.hl7.org/CodeSystem/condition-clinical', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='cancercondition', + name='condition_code', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='A code describing the type of primary or secondary malignant neoplastic disease.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='cancercondition', + name='histology_morphology_behavior', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A description of the morphologic and behavioral characteristics of the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='cancerrelatedprocedure', + name='code', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Code for the procedure performed.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='cancerrelatedprocedure', + name='target_body_site', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='The body location(s) where the procedure was performed.', null=True, size=None), + ), + migrations.AlterField( + model_name='cancerrelatedprocedure', + name='treatment_intent', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='geneticvariantfound', + name='genomic_source_class', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the genomic class of the specimen being analyzed.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='geneticvariantfound', + name='method', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='geneticvariantfound', + name='variant_found_identifier', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). Accepted value set: ClinVar.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='geneticvarianttested', + name='data_value', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify positive or negative value forthe mutation. Accepted value set: SNOMED CT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='geneticvarianttested', + name='method', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='geneticvarianttested', + name='variant_tested_identifier', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR).', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='genomicsreport', + name='specimen_type', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the type of material the specimen contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='genomicsreport', + name='test_name', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test. Accepted value sets: LOINC, GTR.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='labsvital', + name='blood_pressure_diastolic', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure after the contraction of the heart while the chambers of the heart refill with blood, when the pressure is lowest.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='labsvital', + name='blood_pressure_systolic', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure during the contraction of the left ventricle of the heart, when blood pressure is at its highest.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='labsvital', + name='body_height', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's height.", validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='labsvital', + name='body_weight', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's weight.", validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='labsvital', + name='tumor_marker_test', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify tumor marker test.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:tumor_marker_test', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Tumor marker test schema.', 'properties': {'code': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'data_value': {'anyOf': [{'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, {'$id': 'chord_metadata_service:ratio', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Ratio schema.', 'properties': {'denominator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, 'numerator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}}, 'title': 'Ratio', 'type': 'object'}]}}, 'required': ['code'], 'title': 'Tumor marker test', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='medicationstatement', + name='medication_code', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='A code for medication. Accepted code systems: Medication Clinical Drug (RxNorm) and other.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='medicationstatement', + name='termination_reason', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='A code explaining unplanned or premature termination of a course of medication. Accepted ontologies: SNOMED CT.', null=True, size=None), + ), + migrations.AlterField( + model_name='medicationstatement', + name='treatment_intent', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment. Accepted ontologies: SNOMED CT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='tnmstaging', + name='distant_metastases_category', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category describing the presence or absence of metastases in remote anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='tnmstaging', + name='primary_tumor_category', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the primary tumor, based on its size and extent. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='tnmstaging', + name='regional_nodes_category', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the presence or absence of metastases in regional lymph nodes. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='tnmstaging', + name='stage_group', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system.Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), + ), + ] From 428603443a35457a40f788ec41df059f3153e789 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Thu, 26 Mar 2020 13:49:35 -0400 Subject: [PATCH 57/95] replace CodeableConcept with Ontology_class in mcode schemas add tests for validation in affected models --- chord_metadata_service/mcode/models.py | 3 +- .../mcode/tests/constants.py | 11 ++--- .../mcode/tests/test_models.py | 29 ++++++++++- chord_metadata_service/restapi/schemas.py | 48 ++++++++++--------- 4 files changed, 57 insertions(+), 34 deletions(-) diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 7273183b2..12dae6abb 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -216,7 +216,8 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): procedure_type = models.CharField(choices=PROCEDURE_TYPES, max_length=200, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "procedure_type")) code = JSONField(validators=[ontology_class_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "code")) - occurence_time_or_period = JSONField(help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "occurence_time_or_period")) + occurence_time_or_period = JSONField(validators=[time_or_period_validator], + help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "occurence_time_or_period")) target_body_site = ArrayField(JSONField(null=True, blank=True, validators=[ontology_class_validator]), blank=True, null=True, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "target_body_site")) diff --git a/chord_metadata_service/mcode/tests/constants.py b/chord_metadata_service/mcode/tests/constants.py index 58ff181dd..2f0d4ff31 100644 --- a/chord_metadata_service/mcode/tests/constants.py +++ b/chord_metadata_service/mcode/tests/constants.py @@ -151,13 +151,8 @@ def valid_labs_vital(individual): }, "tumor_marker_test": { "code": { - "coding": [ - { - "code": "50610-5", - "display": "Alpha-1-Fetoprotein", - "system": "https://loinc.org/" - } - ] + "id": "50610-5", + "label": "Alpha-1-Fetoprotein" }, "data_value": { "value": 10, @@ -194,7 +189,7 @@ def valid_cancer_condition(): } -def valid_tnm_staging(cancer_condition): +def invalid_tnm_staging(cancer_condition): return { "id": "tnm_staging:01", "tnm_type": "clinical", diff --git a/chord_metadata_service/mcode/tests/test_models.py b/chord_metadata_service/mcode/tests/test_models.py index 3fb4e6232..8fd4c93b9 100644 --- a/chord_metadata_service/mcode/tests/test_models.py +++ b/chord_metadata_service/mcode/tests/test_models.py @@ -4,6 +4,7 @@ from ..models import * from .constants import * from rest_framework import serializers +from django.core.validators import ValidationError class GeneticVariantTestedTest(TestCase): """ Test module for GeneticVariantTested model """ @@ -87,9 +88,25 @@ def test_labs_vital(self): self.assertEqual(labs_vital.blood_pressure_diastolic['value'], 80) self.assertEqual(labs_vital.blood_pressure_systolic['value'], 120) self.assertIsInstance(labs_vital.tumor_marker_test, dict) - self.assertIsInstance(labs_vital.tumor_marker_test['code']['coding'], list) + self.assertIsInstance(labs_vital.tumor_marker_test['code'], dict) self.assertEqual(labs_vital.tumor_marker_test['data_value']['value'], 10) + def test_validation(self): + invalid_obj = valid_labs_vital(self.individual) + invalid_obj["id"] = "labs_vital:02" + invalid_obj["tumor_marker_test"]["code"] = { + "coding": [ + { + "code": "50610-5", + "display": "Alpha-1-Fetoprotein", + "system": "loinc.org" + } + ] + } + invalid = LabsVital.objects.create(**invalid_obj) + with self.assertRaises(serializers.ValidationError): + invalid.full_clean() + class CancerConditionTest(TestCase): """ Test module for CancerCondition model """ @@ -115,13 +132,21 @@ class TNMStagingTest(TestCase): def setUp(self): self.cancer_condition = CancerCondition.objects.create(**valid_cancer_condition()) - self.tnm_staging = TNMStaging.objects.create(**valid_tnm_staging(self.cancer_condition)) + self.tnm_staging = TNMStaging.objects.create(**invalid_tnm_staging(self.cancer_condition)) def test_tnm_staging(self): tnm_staging = TNMStaging.objects.get(id='tnm_staging:01') self.assertEqual(tnm_staging.tnm_type, 'clinical') + # this should fails in validation below self.assertIsInstance(tnm_staging.stage_group['data_value']['coding'], list) + def test_validation(self): + invalid_obj = invalid_tnm_staging(self.cancer_condition) + invalid_obj["id"] = "tnm_staging:02" + invalid = TNMStaging.objects.create(**invalid_obj) + with self.assertRaises(serializers.ValidationError): + invalid.full_clean() + class CancerRelatedProcedureTest(TestCase): """ Test module for CancerRelatedProcedure model """ diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 9c495bb5e..00b726144 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -213,11 +213,9 @@ ################################## mCode/FHIR based schemas ################################## -#TODO currently these schemas are not distiguished by their provenance: some of them are FHIR elements, -# some are aggregated from FHIR and mCODE - -# mCode/FHIR Quantity +### FHIR datatypes +# FHIR Quantity https://www.hl7.org/fhir/datatypes.html#Quantity QUANTITY = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "chord_metadata_service:quantity_schema", @@ -245,8 +243,8 @@ "additionalProperties": False } -# mCode/FHIR CodeableConcept +# FHIR CodeableConcept https://www.hl7.org/fhir/datatypes.html#CodeableConcept CODEABLE_CONCEPT = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "chord_metadata_service:codeable_concept_schema", @@ -275,6 +273,7 @@ } +# FHIR Period https://www.hl7.org/fhir/datatypes.html#Period PERIOD = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "chord_metadata_service:period_schema", @@ -295,6 +294,23 @@ } +# FHIR Ratio https://www.hl7.org/fhir/datatypes.html#Ratio +RATIO = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "chord_metadata_service:ratio", + "title": "Ratio", + "description": "Ratio schema.", + "type": "object", + "properties": { + "numerator": QUANTITY, + "denominator": QUANTITY + }, + "additionalProperties": False +} + + +### FHIR based mCode elements + TIME_OR_PERIOD = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "chord_metadata_service:time_or_period", @@ -313,20 +329,6 @@ } -RATIO = { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "chord_metadata_service:ratio", - "title": "Ratio", - "description": "Ratio schema.", - "type": "object", - "properties": { - "numerator": QUANTITY, - "denominator": QUANTITY - }, - "additionalProperties": False -} - - def customize_schema(first_typeof: dict, second_typeof: dict, first_property: str, second_property: str, id: str=None, title: str=None, description: str=None, additionalProperties=False, required=None) -> dict: @@ -347,24 +349,24 @@ def customize_schema(first_typeof: dict, second_typeof: dict, first_property: st } -COMORBID_CONDITION = customize_schema(first_typeof=CODEABLE_CONCEPT, second_typeof=CODEABLE_CONCEPT, +COMORBID_CONDITION = customize_schema(first_typeof=ONTOLOGY_CLASS, second_typeof=ONTOLOGY_CLASS, first_property="clinical_status", second_property="code", id="chord_metadata_service:comorbid_condition_schema", title="Comorbid Condition schema", description="Comorbid condition schema.") #TODO this is definitely should be changed, fhir datatypes are too complex use Ontology_ class -COMPLEX_ONTOLOGY = customize_schema(first_typeof=CODEABLE_CONCEPT, second_typeof=CODEABLE_CONCEPT, +COMPLEX_ONTOLOGY = customize_schema(first_typeof=ONTOLOGY_CLASS, second_typeof=ONTOLOGY_CLASS, first_property="data_value", second_property="staging_system", id="chord_metadata_service:complex_ontology_schema", title="Complex ontology", description="Complex object to combine data value and staging system.", required=["data_value"]) #TODO this is definitely should be changed, fhir datatypes are too complex use Ontology_ class -TUMOR_MARKER_TEST = customize_schema(first_typeof=CODEABLE_CONCEPT, +TUMOR_MARKER_TEST = customize_schema(first_typeof=ONTOLOGY_CLASS, second_typeof={ "anyOf": [ - CODEABLE_CONCEPT, + ONTOLOGY_CLASS, QUANTITY, RATIO ] From 64d40dfa04132266530f11ac448cbb595e7b5634 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Thu, 26 Mar 2020 13:51:13 -0400 Subject: [PATCH 58/95] migrations: django generates new migrations since JsonSchemaValidator was altered --- .../migrations/0002_auto_20200326_1339.py | 25 +++++++++++ .../migrations/0005_auto_20200326_1339.py | 45 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 chord_metadata_service/experiments/migrations/0002_auto_20200326_1339.py create mode 100644 chord_metadata_service/mcode/migrations/0005_auto_20200326_1339.py diff --git a/chord_metadata_service/experiments/migrations/0002_auto_20200326_1339.py b/chord_metadata_service/experiments/migrations/0002_auto_20200326_1339.py new file mode 100644 index 000000000..1acd665d8 --- /dev/null +++ b/chord_metadata_service/experiments/migrations/0002_auto_20200326_1339.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.10 on 2020-03-26 17:39 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='experiment', + name='experiment_ontology', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), + ), + migrations.AlterField( + model_name='experiment', + name='molecule_ontology', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), + ), + ] diff --git a/chord_metadata_service/mcode/migrations/0005_auto_20200326_1339.py b/chord_metadata_service/mcode/migrations/0005_auto_20200326_1339.py new file mode 100644 index 000000000..b934f2173 --- /dev/null +++ b/chord_metadata_service/mcode/migrations/0005_auto_20200326_1339.py @@ -0,0 +1,45 @@ +# Generated by Django 2.2.10 on 2020-03-26 17:39 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mcode', '0004_auto_20200325_1920'), + ] + + operations = [ + migrations.AlterField( + model_name='cancerrelatedprocedure', + name='occurence_time_or_period', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The date/time that a procedure was performed.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:time_or_period', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Time of Period schema.', 'properties': {'value': {'anyOf': [{'format': 'date-time', 'type': 'string'}, {'$id': 'chord_metadata_service:period_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Period schema.', 'properties': {'end': {'format': 'date-time', 'type': 'string'}, 'start': {'format': 'date-time', 'type': 'string'}}, 'title': 'Period', 'type': 'object'}]}}, 'title': 'Time of Period', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='labsvital', + name='tumor_marker_test', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify tumor marker test.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:tumor_marker_test', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Tumor marker test schema.', 'properties': {'code': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'data_value': {'anyOf': [{'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, {'$id': 'chord_metadata_service:ratio', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Ratio schema.', 'properties': {'denominator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, 'numerator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}}, 'title': 'Ratio', 'type': 'object'}]}}, 'required': ['code'], 'title': 'Tumor marker test', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='tnmstaging', + name='distant_metastases_category', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category describing the presence or absence of metastases in remote anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='tnmstaging', + name='primary_tumor_category', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the primary tumor, based on its size and extent. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='tnmstaging', + name='regional_nodes_category', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the presence or absence of metastases in regional lymph nodes. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='tnmstaging', + name='stage_group', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system.Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), + ), + ] From 6b891ef6b900aebd1cdadd4e35832ac7a5db73d1 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Thu, 26 Mar 2020 19:52:07 -0400 Subject: [PATCH 59/95] add nested serializers --- chord_metadata_service/mcode/serializers.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index 2b9a37c61..bd2f90c02 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -1,4 +1,5 @@ from chord_metadata_service.restapi.serializers import GenericSerializer +from chord_metadata_service.patients.serializers import IndividualSerializer from .models import * @@ -17,6 +18,8 @@ class Meta: class GenomicsReportSerializer(GenericSerializer): + genetic_variant_tested = GeneticVariantTestedSerializer(read_only=True, many=True) + genetic_variant_found = GeneticVariantTestedSerializer(read_only=True, many=True) class Meta: model = GenomicsReport @@ -30,17 +33,18 @@ class Meta: fields = '__all__' -class CancerConditionSerializer(GenericSerializer): +class TNMStagingSerializer(GenericSerializer): class Meta: - model = CancerCondition + model = TNMStaging fields = '__all__' -class TNMStagingSerializer(GenericSerializer): +class CancerConditionSerializer(GenericSerializer): + tnm_staging = TNMStagingSerializer(source='tnmstaging_set', read_only=True, many=True) class Meta: - model = TNMStaging + model = CancerCondition fields = '__all__' @@ -59,6 +63,11 @@ class Meta: class MCodePacketSerializer(GenericSerializer): + subject = IndividualSerializer() + genomics_report = GenomicsReportSerializer() + cancer_condition = CancerConditionSerializer() + cancer_related_procedures = CancerRelatedProcedureSerializer(many=True) + medication_statement = MedicationStatementSerializer() class Meta: model = MCodePacket From 9e3a478180ffaa774e54e977e84065e0e71e4675 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Fri, 27 Mar 2020 11:48:44 -0400 Subject: [PATCH 60/95] override to_representation for nested serializers --- chord_metadata_service/mcode/serializers.py | 34 ++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index bd2f90c02..2699a7fd9 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -18,13 +18,23 @@ class Meta: class GenomicsReportSerializer(GenericSerializer): - genetic_variant_tested = GeneticVariantTestedSerializer(read_only=True, many=True) - genetic_variant_found = GeneticVariantTestedSerializer(read_only=True, many=True) class Meta: model = GenomicsReport fields = '__all__' + def to_representation(self, instance): + """" + Overriding this method to allow post Primary Key for FK and M2M + objects and return their nested serialization. + """ + response = super().to_representation(instance) + response['genetic_variant_tested'] = GeneticVariantTestedSerializer(instance.genetic_variant_tested, + many=True, required=False).data + response['genetic_variant_found'] = GeneticVariantTestedSerializer(instance.genetic_variant_found, + many=True, required=False).data + return response + class LabsVitalSerializer(GenericSerializer): @@ -63,11 +73,21 @@ class Meta: class MCodePacketSerializer(GenericSerializer): - subject = IndividualSerializer() - genomics_report = GenomicsReportSerializer() - cancer_condition = CancerConditionSerializer() - cancer_related_procedures = CancerRelatedProcedureSerializer(many=True) - medication_statement = MedicationStatementSerializer() + + def to_representation(self, instance): + """" + Overriding this method to allow post Primary Key for FK and M2M + objects and return their nested serialization. + """ + response = super().to_representation(instance) + response['subject'] = IndividualSerializer(instance.subject).data + response['genomics_report'] = GenomicsReportSerializer(instance.genomics_report, required=False).data + response['cancer_condition'] = CancerConditionSerializer(instance.cancer_condition, required=False).data + response['cancer_related_procedures'] = CancerRelatedProcedureSerializer(instance.cancer_related_procedures, + many=True, required=False).data + response['medication_statement'] = MedicationStatementSerializer(instance.medication_statement, + required=False).data + return response class Meta: model = MCodePacket From 854e3f3a00145afc240f21e967727e2141b51163 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Fri, 27 Mar 2020 12:43:06 -0400 Subject: [PATCH 61/95] add mcode example json --- examples/mcode_example.json | 154 ++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 examples/mcode_example.json diff --git a/examples/mcode_example.json b/examples/mcode_example.json new file mode 100644 index 000000000..fe2ae4e7c --- /dev/null +++ b/examples/mcode_example.json @@ -0,0 +1,154 @@ +{ + "id": "mcodepacket:01", + "subject": { + "id": "ind:HG00096", + "taxonomy": { + "id": "NCBITaxon:9606", + "label": "Homo sapiens" + }, + "date_of_birth": "1923-12-07", + "sex": "MALE", + "karyotypic_sex": "XY", + "created": "2020-03-25T18:10:14.017215Z", + "updated": "2020-03-25T18:10:14.017215Z" + }, + "genomics_report": { + "id": "genomics_resport:01", + "test_name": { + "id": "GTR000511179.13", + "label": "Clinical Exome" + }, + "performing_ogranization_name": "Test organization", + "specimen_type": { + "id": "BDY", + "label": "Whole body" + }, + "genetic_variant_tested": [ + { + "id": "variant:03", + "method": { + "id": "FISH", + "label": "Fluorescent in situ hybridization (FISH)" + }, + "variant_tested_identifier": { + "id": "t12", + "label": "test" + }, + "variant_tested_description": "test" + } + ], + "genetic_variant_found": [ + { + "id": "variant_found:01", + "method": { + "id": "t12", + "label": "test" + } + } + ] + }, + "cancer_condition": { + "id": "cancer_condition:01", + "tnm_staging": [ + { + "id": "tnmstaging:01", + "tnm_type": "clinical", + "stage_group": { + "data_value": { + "id": "test", + "label": "t12" + } + }, + "primary_tumor_category": { + "data_value": { + "id": "test", + "label": "t12" + } + }, + "regional_nodes_category": { + "data_value": { + "id": "test", + "label": "t12" + } + }, + "distant_metastases_category": { + "data_value": { + "id": "test", + "label": "t12" + } + }, + "cancer_condition": "cancer_condition:01" + } + ], + "condition_type": "primary", + "body_location_code": [ + { + "id": "442083009", + "label": "Anatomical or acquired body structure (body structure)" + } + ], + "clinical_status": { + "id": "active", + "label": "Active" + }, + "condition_code": { + "id": "404087009", + "label": "Carcinosarcoma of skin (disorder)" + }, + "date_of_diagnosis": "2018-11-13T20:20:39Z", + "histology_morphology_behavior": { + "id": "372147008", + "label": "Kaposi's sarcoma - category (morphologic abnormality)" + } + }, + "medication_statement": { + "id": "medication_statement:02", + "medication_code": { + "id": "92052", + "label": "Verapamil Oral Tablet [Calan]" + }, + "termination_reason": [ + { + "id": "182992009", + "label": "Treatment completed" + }, + { + "id": "1821212992009", + "label": "Treatment completed" + } + ], + "treatment_intent": { + "id": "373808002", + "label": "Curative - procedure intent" + }, + "start_date": "2018-11-13T20:20:39Z", + "end_date": "2019-04-13T20:20:39Z", + "date_time": "2019-04-13T20:20:39Z" + }, + "cancer_related_procedures": [ + { + "id": "cancer_related_procedure:03", + "procedure_type": "radiation", + "code": { + "id": "33356009", + "label": "Betatron teleradiotherapy (procedure)" + }, + "occurence_time_or_period": { + "value": { + "end": "2019-04-13T20:20:39+00:00", + "start": "2018-11-13T20:20:39+00:00" + } + }, + "target_body_site": [ + { + "id": "74805009", + "label": "Mammary gland sinus" + } + ], + "treatment_intent": { + "id": "373808002", + "label": "Curative - procedure intent" + } + } + ] +} From a75c89d9e9a93d6e6a56256c107f001dbc440288 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 27 Mar 2020 13:16:48 -0400 Subject: [PATCH 62/95] validators: refactor: move common validators in module --- chord_metadata_service/experiments/models.py | 5 +---- chord_metadata_service/restapi/validators.py | 6 ++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 9a1d56e3c..55898eda5 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -5,13 +5,10 @@ from django.contrib.postgres.fields import JSONField, ArrayField from chord_metadata_service.restapi.models import IndexableMixin from chord_metadata_service.restapi.description_utils import rec_help -from chord_metadata_service.restapi.validators import JsonSchemaValidator +from chord_metadata_service.restapi.validators import ontologyListValidator, keyValueValidator from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT import chord_metadata_service.experiments.descriptions as d -ontologyListValidator = JsonSchemaValidator(ONTOLOGY_CLASS_LIST) -keyValueValidator = JsonSchemaValidator(KEY_VALUE_OBJECT) - class Experiment(models.Model, IndexableMixin): """ Class to store Experiment information """ diff --git a/chord_metadata_service/restapi/validators.py b/chord_metadata_service/restapi/validators.py index 0287e9879..9f87d2ef4 100644 --- a/chord_metadata_service/restapi/validators.py +++ b/chord_metadata_service/restapi/validators.py @@ -1,5 +1,6 @@ from rest_framework import serializers from jsonschema import Draft7Validator +from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT, AGE_OR_AGE_RANGE class JsonSchemaValidator(object): """ Custom class based validator to validate against Json schema for JSONField """ @@ -22,3 +23,8 @@ def deconstruct(self): [self.schema], {} ) + +ontologyValidator = JsonSchemaValidator(ONTOLOGY_CLASS) +ontologyListValidator = JsonSchemaValidator(ONTOLOGY_CLASS_LIST) +keyValueValidator = JsonSchemaValidator(KEY_VALUE_OBJECT) +ageOrAgeRangeValidator = JsonSchemaValidator(AGE_OR_AGE_RANGE) From 2cbcd66c4af4fa5f7aa40699952aa8ac54e99c44 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Fri, 27 Mar 2020 13:19:58 -0400 Subject: [PATCH 63/95] replace codeableConcept to ontology_class --- chord_metadata_service/patients/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chord_metadata_service/patients/serializers.py b/chord_metadata_service/patients/serializers.py index cda74dc43..9e7b26725 100644 --- a/chord_metadata_service/patients/serializers.py +++ b/chord_metadata_service/patients/serializers.py @@ -30,12 +30,12 @@ class IndividualSerializer(GenericSerializer): required=False ) ecog_performance_status = serializers.JSONField( - validators=[JsonSchemaValidator(schema=CODEABLE_CONCEPT)], + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], allow_null=True, required=False ) karnofsky = serializers.JSONField( - validators=[JsonSchemaValidator(schema=CODEABLE_CONCEPT)], + validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], allow_null=True, required=False ) From 0568a30ca2c99be1cf64b8518219a11338d06d41 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 27 Mar 2020 13:22:25 -0400 Subject: [PATCH 64/95] experiments: add api routes --- .../experiments/api_views.py | 25 +++++++++++++++++++ .../experiments/serializers.py | 11 ++++++++ chord_metadata_service/restapi/urls.py | 4 +++ 3 files changed, 40 insertions(+) create mode 100644 chord_metadata_service/experiments/api_views.py create mode 100644 chord_metadata_service/experiments/serializers.py diff --git a/chord_metadata_service/experiments/api_views.py b/chord_metadata_service/experiments/api_views.py new file mode 100644 index 000000000..097767b6d --- /dev/null +++ b/chord_metadata_service/experiments/api_views.py @@ -0,0 +1,25 @@ +from rest_framework import viewsets +from rest_framework.settings import api_settings +from .serializers import ExperimentSerializer +from .models import Experiment +from chord_metadata_service.phenopackets.api_views import BIOSAMPLE_PREFETCH, PHENOPACKET_PREFETCH +from chord_metadata_service.restapi.api_renderers import ( + FHIRRenderer, + PhenopacketsRenderer +) +from chord_metadata_service.restapi.pagination import LargeResultsSetPagination + + +class ExperimentViewSet(viewsets.ModelViewSet): + """ + get: + Return a list of all existing experiments + + post: + Create a new experiment + """ + + queryset = Experiment.objects.all().order_by("id") + serializer_class = ExperimentSerializer + pagination_class = LargeResultsSetPagination + renderer_classes = tuple(api_settings.DEFAULT_RENDERER_CLASSES) diff --git a/chord_metadata_service/experiments/serializers.py b/chord_metadata_service/experiments/serializers.py new file mode 100644 index 000000000..3a652e307 --- /dev/null +++ b/chord_metadata_service/experiments/serializers.py @@ -0,0 +1,11 @@ +from rest_framework import serializers +from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, AGE_OR_AGE_RANGE +from chord_metadata_service.restapi.validators import JsonSchemaValidator, ontologyListValidator, keyValueValidator +from chord_metadata_service.restapi.serializers import GenericSerializer +from .models import Experiment + + +class ExperimentSerializer(GenericSerializer): + class Meta: + model = Experiment + fields = '__all__' diff --git a/chord_metadata_service/restapi/urls.py b/chord_metadata_service/restapi/urls.py index 75b4c3797..1bc49a3cc 100644 --- a/chord_metadata_service/restapi/urls.py +++ b/chord_metadata_service/restapi/urls.py @@ -1,6 +1,7 @@ from django.urls import path, include from rest_framework import routers from chord_metadata_service.chord import api_views as chord_views +from chord_metadata_service.experiments import api_views as experiment_views from chord_metadata_service.patients import api_views as individual_views from chord_metadata_service.phenopackets import api_views as phenopacket_views @@ -10,6 +11,9 @@ router = routers.DefaultRouter(trailing_slash=False) +# Experiments app urls +router.register(r'experiments', experiment_views.ExperimentViewSet) + # Patients app urls router.register(r'individuals', individual_views.IndividualViewSet) From 6a74c21f4c60ad89280c64b883b7328e684c88e7 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 27 Mar 2020 13:28:22 -0400 Subject: [PATCH 65/95] experiments: update models --- .../migrations/0002_auto_20200327_1728.py | 40 +++++++++++++++++++ chord_metadata_service/experiments/models.py | 14 +++---- 2 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 chord_metadata_service/experiments/migrations/0002_auto_20200327_1728.py diff --git a/chord_metadata_service/experiments/migrations/0002_auto_20200327_1728.py b/chord_metadata_service/experiments/migrations/0002_auto_20200327_1728.py new file mode 100644 index 000000000..a651e05d5 --- /dev/null +++ b/chord_metadata_service/experiments/migrations/0002_auto_20200327_1728.py @@ -0,0 +1,40 @@ +# Generated by Django 2.2.11 on 2020-03-27 17:28 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='experiment', + name='experiment_ontology', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='(Ontology: OBI) links to experiment ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'ONTOLOGY_CLASS', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), + ), + migrations.AlterField( + model_name='experiment', + name='molecule', + field=models.CharField(choices=[('total RNA', 'total RNA'), ('polyA RNA', 'polyA RNA'), ('cytoplasmic RNA', 'cytoplasmic RNA'), ('nuclear RNA', 'nuclear RNA'), ('small RNA', 'small RNA'), ('genomic DNA', 'genomic DNA'), ('protein', 'protein'), ('other', 'other')], help_text='(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.', max_length=20, null=True), + ), + migrations.AlterField( + model_name='experiment', + name='molecule_ontology', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='(Ontology: SO) links to molecule ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'ONTOLOGY_CLASS', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), + ), + migrations.AlterField( + model_name='experiment', + name='other_fields', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The other fields for the experiment', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'KEY_VALUE_OBJECT', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'The schema represents a key-value object.', 'patternProperties': {'^.*$': {'type': 'string'}}, 'title': 'Key-value object', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='experiment', + name='reference_registry_id', + field=models.CharField(help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.', max_length=30, null=True), + ), + ] diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 55898eda5..722039581 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -38,16 +38,16 @@ class Experiment(models.Model, IndexableMixin): id = CharField(primary_key=True, max_length=200, help_text=rec_help(d.EXPERIMENT, 'id')) - reference_registry_id = CharField(max_length=30, null=True, blank=True, help_text=rec_help(d.EXPERIMENT, 'reference_registry_id')) + reference_registry_id = CharField(max_length=30, null=True, help_text=rec_help(d.EXPERIMENT, 'reference_registry_id')) qc_flags = ArrayField(CharField(max_length=100, help_text=rec_help(d.EXPERIMENT, 'qc_flags')), null=True, default=list) - experiment_type = CharField(max_length=30, null=False, blank=False, help_text=rec_help(d.EXPERIMENT, 'experiment_type')) - experiment_ontology = JSONField(null=True, blank=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) - molecule_ontology = JSONField(null=True, blank=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) - molecule = CharField(choices=MOLECULE, max_length=20, null=True, blank=True, help_text=rec_help(d.EXPERIMENT, 'molecule')) + experiment_type = CharField(max_length=30, help_text=rec_help(d.EXPERIMENT, 'experiment_type')) + experiment_ontology = JSONField(null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) + molecule_ontology = JSONField(null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) + molecule = CharField(choices=MOLECULE, max_length=20, null=True, help_text=rec_help(d.EXPERIMENT, 'molecule')) - library_strategy = CharField(choices=LIBRARY_STRATEGY, max_length=25, null=False, blank=False, help_text=rec_help(d.EXPERIMENT, 'library_strategy')) + library_strategy = CharField(choices=LIBRARY_STRATEGY, max_length=25, help_text=rec_help(d.EXPERIMENT, 'library_strategy')) - other_fields = JSONField(blank=True, null=True, validators=[keyValueValidator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) + other_fields = JSONField(null=True, validators=[keyValueValidator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) def __str__(self): return str(self.id) From e758a0a5021426b32272f945d8e70fbff7a5c17c Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Fri, 27 Mar 2020 13:39:13 -0400 Subject: [PATCH 66/95] small fix --- chord_metadata_service/mcode/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chord_metadata_service/mcode/serializers.py b/chord_metadata_service/mcode/serializers.py index 2699a7fd9..0ddc501ae 100644 --- a/chord_metadata_service/mcode/serializers.py +++ b/chord_metadata_service/mcode/serializers.py @@ -31,7 +31,7 @@ def to_representation(self, instance): response = super().to_representation(instance) response['genetic_variant_tested'] = GeneticVariantTestedSerializer(instance.genetic_variant_tested, many=True, required=False).data - response['genetic_variant_found'] = GeneticVariantTestedSerializer(instance.genetic_variant_found, + response['genetic_variant_found'] = GeneticVariantFoundSerializer(instance.genetic_variant_found, many=True, required=False).data return response From 52109c1788bbbbce401527245b5e0237a7cb0138 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 30 Mar 2020 14:10:38 -0400 Subject: [PATCH 67/95] experiments: remove unused imports --- chord_metadata_service/experiments/api_views.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/chord_metadata_service/experiments/api_views.py b/chord_metadata_service/experiments/api_views.py index 097767b6d..157980c75 100644 --- a/chord_metadata_service/experiments/api_views.py +++ b/chord_metadata_service/experiments/api_views.py @@ -2,11 +2,6 @@ from rest_framework.settings import api_settings from .serializers import ExperimentSerializer from .models import Experiment -from chord_metadata_service.phenopackets.api_views import BIOSAMPLE_PREFETCH, PHENOPACKET_PREFETCH -from chord_metadata_service.restapi.api_renderers import ( - FHIRRenderer, - PhenopacketsRenderer -) from chord_metadata_service.restapi.pagination import LargeResultsSetPagination From 7fc0e1483069d1725b76b89d83bbf1be1eb7d657 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 30 Mar 2020 15:16:42 -0400 Subject: [PATCH 68/95] examples: add experiments file --- examples/experiments.json | 5281 +++++++++++++++++++++++++++++++++++++ 1 file changed, 5281 insertions(+) create mode 100644 examples/experiments.json diff --git a/examples/experiments.json b/examples/experiments.json new file mode 100644 index 000000000..2ab5e7213 --- /dev/null +++ b/examples/experiments.json @@ -0,0 +1,5281 @@ +[ + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0019_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Yip Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "BP255 - Blueprint Grade", + "chip_antibody_lot" : "A1723-0041d", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "4.3 ug", + "chip_protocol_crosslink_time" : "5 min", + "collection_method" : "Snap frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "https://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20Metathesaurus&code=C0346286", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "10 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "female", + "ihec_reference_epigenome_identifier" : "CEMT0019", + "library_generation_fragment_size_range" : "174:514", + "phenotype" : "http://purl.obolibrary.org/obo/UBERON_0000955", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000240" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0021_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Yip Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "BP255 - Blueprint Grade", + "chip_antibody_lot" : "A1723-0041d", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "collection_method" : "Snap frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0017636", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "10 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "male", + "ihec_reference_epigenome_identifier" : "CEMT0021", + "library_generation_fragment_size_range" : "174:514", + "phenotype" : "http://purl.obolibrary.org/obo/UBERON_0000955", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000238" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0022_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Cairncross Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "BP255 - Blueprint Grade", + "chip_antibody_lot" : "A1723-0041d", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "collection_method" : "Snap frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0334590", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "15 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "male", + "ihec_reference_epigenome_identifier" : "CEMT0022", + "library_generation_fragment_size_range" : "174:514", + "phenotype" : "http://purl.obolibrary.org/obo/UBERON_0000955", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000243" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0023_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Cairncross Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "BP255 - Blueprint Grade", + "chip_antibody_lot" : "A1723-0041d", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "collection_method" : "Snap frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0017636", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "10 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "female", + "ihec_reference_epigenome_identifier" : "CEMT0023", + "library_generation_fragment_size_range" : "174:514", + "phenotype" : "http://purl.obolibrary.org/obo/UBERON_0000955", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000221" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0025_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Connors/Weng Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "ab4729", + "chip_antibody_lot" : "GR55451-1", + "chip_antibody_provider" : "Abcam", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "2.5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0861884", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "15 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "female", + "ihec_reference_epigenome_identifier" : "CEMT0025", + "library_generation_fragment_size_range" : "174:514", + "markers" : "CD19", + "origin_sample" : "B cells", + "origin_sample_ontology_uri" : "http://purl.obolibrary.org/obo/CL_0000236", + "passage_if_expanded" : "NA", + "phenotype" : "http://purl.obolibrary.org/obo/CL_0000236", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000247" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0026_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Connors/Weng Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "ab4729", + "chip_antibody_lot" : "GR55451-1", + "chip_antibody_provider" : "Abcam", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "2.5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0861884", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "10 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "male", + "ihec_reference_epigenome_identifier" : "CEMT0026", + "library_generation_fragment_size_range" : "174:514", + "markers" : "CD19", + "origin_sample" : "B cells", + "origin_sample_ontology_uri" : "http://purl.obolibrary.org/obo/CL_0000236", + "passage_if_expanded" : "NA", + "phenotype" : "http://purl.obolibrary.org/obo/CL_0000236", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000239" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0027_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Connors/Weng Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "ab4729", + "chip_antibody_lot" : "GR55451-1", + "chip_antibody_provider" : "Abcam", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "2.5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/pages/concept_details.jsf?dictionary=NCI%20Metathesaurus&code=C0023434", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "10 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "male", + "ihec_reference_epigenome_identifier" : "CEMT0027", + "library_generation_fragment_size_range" : "174:489", + "markers" : "CD19", + "origin_sample" : "B cells", + "origin_sample_ontology_uri" : "http://purl.obolibrary.org/obo/CL_0000236", + "passage_if_expanded" : "NA", + "phenotype" : "http://purl.obolibrary.org/obo/CL_0000236", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000234" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0028_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Connors/Weng Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "ab4729", + "chip_antibody_lot" : "GR55451-1", + "chip_antibody_provider" : "Abcam", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "2.5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C2854093", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "10 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "female", + "ihec_reference_epigenome_identifier" : "CEMT0028", + "library_generation_fragment_size_range" : "174:489", + "markers" : "CD19", + "origin_sample" : "B cells", + "origin_sample_ontology_uri" : "http://purl.obolibrary.org/obo/CL_0000236", + "passage_if_expanded" : "NA", + "phenotype" : "http://purl.obolibrary.org/obo/CL_0000236", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000232" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0029_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Connors/Weng Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "ab4729", + "chip_antibody_lot" : "GR55451-1", + "chip_antibody_provider" : "Abcam", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "2.5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C2854093", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "10 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "male", + "ihec_reference_epigenome_identifier" : "CEMT0029", + "library_generation_fragment_size_range" : "174:489", + "markers" : "CD19", + "origin_sample" : "B cells", + "origin_sample_ontology_uri" : "http://purl.obolibrary.org/obo/CL_0000236", + "passage_if_expanded" : "NA", + "phenotype" : "http://purl.obolibrary.org/obo/CL_0000236", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000244" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0030_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Connors/Weng Lab", + "chip_antibody" : "H3K27ac", + "chip_antibody_catalog" : "ab4729", + "chip_antibody_lot" : "GR55451-1", + "chip_antibody_provider" : "Abcam", + "chip_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "chip_protocol_antibody_amount" : "2.5 ug", + "chip_protocol_bead_amount" : "20 ul", + "chip_protocol_bead_type" : "Sepharose A/G Bead Mix", + "chip_protocol_chromatin_amount" : "5 ug", + "chip_protocol_crosslink_time" : "10 min", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C2854093", + "extraction_protocol" : "BCCAGSC ChIP Standard Operating Procedure", + "extraction_protocol_sonication_cycles" : "10 min", + "extraction_protocol_type_of_sonicator" : "Fisher 550 Sonic Dismembrator", + "gender" : "female", + "ihec_reference_epigenome_identifier" : "CEMT0030", + "library_generation_fragment_size_range" : "174:489", + "markers" : "CD19", + "origin_sample" : "B cells", + "origin_sample_ontology_uri" : "http://purl.obolibrary.org/obo/CL_0000236", + "passage_if_expanded" : "NA", + "phenotype" : "http://purl.obolibrary.org/obo/CL_0000236", + "species" : "Homo sapiens", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000226" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001925", + "label" : "chromatin immunoprecipitation with exonuclease sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0047_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Yip Lab", + "collection_method" : "Snap frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0280790", + "gender" : "male", + "phenotype" : "http://purl.obolibrary.org/obo/CL_0000128", + "species" : "Homo sapiens", + "subject_id" : "VNA96", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001002" + }, + { + "experiment_ontology" : [], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0075_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Yip Lab", + "collection_method" : "Flash Frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0280790", + "gender" : "male", + "phenotype" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0280790 , http://purl.obolibrary.org/obo/CL_0000128", + "species" : "Homo sapiens", + "subject_id" : "VNA169", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00003677" + }, + { + "experiment_ontology" : [], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0076_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Yip Lab", + "collection_method" : "Flash Frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0751396", + "gender" : "female", + "phenotype" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0751396 , http://purl.obolibrary.org/obo/CL_0000128", + "species" : "Homo sapiens", + "subject_id" : "VNA168", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00003678" + }, + { + "experiment_ontology" : [], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0078_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Yip Lab", + "collection_method" : "Flash Frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0751396", + "gender" : "male", + "phenotype" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0751396 , http://purl.obolibrary.org/obo/CL_0000128", + "species" : "Homo sapiens", + "subject_id" : "VNA146", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00003679" + }, + { + "experiment_ontology" : [], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0079_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Yip Lab", + "collection_method" : "Flash Frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0751396", + "gender" : "male", + "phenotype" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0751396 , http://purl.obolibrary.org/obo/CL_0000128", + "species" : "Homo sapiens", + "subject_id" : "VNA136", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00003680" + }, + { + "experiment_ontology" : [], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0081_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Yip Lab", + "collection_method" : "Flash Frozen", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0280790", + "gender" : "female", + "phenotype" : "http://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20MetaThesaurus&code=C0280790 , http://purl.obolibrary.org/obo/CL_0000128", + "species" : "Homo sapiens", + "subject_id" : "VNA118", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00003682" + }, + { + "experiment_ontology" : [], + "experiment_type" : "Histone H3K27ac", + "id" : "CEMT0153_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : null, + "molecule_ontology" : null, + "other_fields" : { + "biomaterial_provider" : "Pascal Lavoie", + "description_url" : "http://www.epigenomes.ca/data/CEMT/methods/hg38/hg38v1.html", + "donor_health_status_ontology_uri" : "https://ncimeta.nci.nih.gov/ncimbrowser/ConceptReport.jsp?dictionary=NCI%20Metathesaurus&code=C0549184", + "gender" : "male", + "markers" : "CD3+ CD4+ CCR7+ CD45RO- CD25- CD235- 7AAD-", + "origin_sample" : "T cells", + "origin_sample_ontology_uri" : "http://purl.obolibrary.org/obo/UBERON_0010393", + "passage_if_expanded" : "NA", + "phenotype" : "http://purl.obolibrary.org/obo/UBERON_0010393", + "species" : "Homo sapiens", + "subject_id" : "000N3", + "taxon_id" : "9606" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00003822" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS010LQV_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000976.2" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS182XKP_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001001.2" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS287UWO_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001024.3" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS558KIK_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001024.3" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS673INN_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001034.3" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS674MPN_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : {}, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001887.2" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS715VCP_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : {}, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001892.2" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS836CYW_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00003707.2" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS921VGK_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00004662.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS934MZY_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000954.2" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0000716", + "label" : "ChIP-seq assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ENCBS976AOQ_H3K27ac", + "library_strategy" : "ChIP-Seq", + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "culture_conditions" : "unknwon" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000973.2" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1023620_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000267" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1027405_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001431" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1027406_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001516" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1027407_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001243" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1027408_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001319" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1027409_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001525" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1027410_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001450" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1027411_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001359" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1043999_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001474" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044001_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001438" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044002_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001492" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044003_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001383" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044005_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001300" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044006_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001484" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044007_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001381" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044008_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001499" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044009_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001314" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044010_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001377" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044011_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001427" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044012_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001263" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044013_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001528" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044014_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001252" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044015_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001461" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044016_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001435" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044017_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001551" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044018_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001493" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044019_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001286" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044020_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001372" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044021_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001332" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044022_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001538" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044023_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001502" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044024_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001250" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044025_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001277" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044026_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001323" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044027_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001268" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044028_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001424" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044029_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001522" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044030_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001444" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044105_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001365" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044106_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001370" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044107_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001366" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044108_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001468" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044109_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001368" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044110_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001292" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044111_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001310" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044112_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001392" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044114_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001437" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044115_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001449" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044116_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001380" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044117_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001322" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1044118_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001309" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1066181_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001369" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1077455_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001339" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1077456_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001288" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1077457_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001238" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1077458_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001410" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1077459_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001507" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1077460_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001473" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1077461_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001239" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1077462_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001501" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1138464_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001483" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1138465_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001338" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS1138466_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001409" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS150368_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000109" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS150369_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000027" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS150388_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000097" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS150389_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000159" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS150390_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000096" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS150391_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000004" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS150411_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000094" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS150413_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000090" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS158621_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001251" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS158622_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000083" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS158623_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000135" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS164466_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000178" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS164468_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000095" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS164472_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000009" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS164473_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000075" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS164474_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000102" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS164475_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000035" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS202663_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000101" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206373_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000179" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206389_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000187" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206391_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000010" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206434_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000090" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206477_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000124" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206510_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000054" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206512_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000017" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206567_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000155" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206573_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000171" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206577_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000016" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206595_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000024" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS206601_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000076" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS208290_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000048" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS208296_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000140" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS214724_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000034" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS214725_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000191" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS214737_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000019" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS222273_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000174" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS222302_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000084" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS222313_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000043" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS222366_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000161" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS222371_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000177" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS227598_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/index.cfm?p=7BF8A4B6-F4FE-861A-2AD57A08D63D0B58" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000193" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS227606_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/index.cfm?p=7BF8A4B6-F4FE-861A-2AD57A08D63D0B58" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000112" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS227675_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000008" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255945_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000031" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255946_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000175" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255947_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000051" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255948_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000073" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255949_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000053" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255951_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000026" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255952_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000041" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255954_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001477" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS255955_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000118" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS257244_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/index.cfm?p=7BF8A4B6-F4FE-861A-2AD57A08D63D0B58" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000105" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS257253_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000125" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS257333_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000049" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS257336_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000129" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS257378_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000059" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS257387_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000070" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS257432_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000147" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS257461_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000261" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS317193_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000195" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS337154_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000160" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS337160_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000194" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS353039_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000014" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS353040_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000058" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS358671_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000121" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS358674_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/index.cfm?p=7BF8A4B6-F4FE-861A-2AD57A08D63D0B58" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000099" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS358678_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/index.cfm?p=7BF8A4B6-F4FE-861A-2AD57A08D63D0B58" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000184" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS358684_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000114" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS358691_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000013" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS358696_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000055" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS358697_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000022" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS365962_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000087" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS365963_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000011" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS365964_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000002" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS365965_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000181" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS365966_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000132" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS365967_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000141" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS377497_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/index.cfm?p=7BF8A4B6-F4FE-861A-2AD57A08D63D0B58" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000126" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS377537_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000060" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS422247_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000071" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS422353_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000318" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS462582_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000294" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS462583_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000262" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS462584_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000271" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS462585_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000251" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS462586_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000265" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS462587_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000346" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS523839_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000016" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS534009_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "PBS supplemented with 1% PBA and antibodies: anti-CD45-PeCy7 (Beckman Coulter), anti-CD14-FITC (Beckman Coulter), anti-CD3-PC5 (Biolegend (ITK)), anti-CD19-ECD (Beckman Coulter) and anti-56-PE (BD Bioscience)" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000123" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS534010_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "PBS supplemented with 1% PBA and antibodies: anti-CD45-PeCy7 (Beckman Coulter), anti-CD14-FITC (Beckman Coulter), anti-CD3-PC5 (Biolegend (ITK)), anti-CD19-ECD (Beckman Coulter) and anti-56-PE (BD Bioscience)" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000162" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS534011_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "PBS supplemented with 1% PBA and antibodies: anti-CD45-PeCy7 (Beckman Coulter), anti-CD14-FITC (Beckman Coulter), anti-CD3-PC5 (Biolegend (ITK)), anti-CD19-ECD (Beckman Coulter) and anti-56-PE (BD Bioscience)" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000085" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS534012_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "PBS supplemented with 1% PBA and antibodies: anti-CD45-PeCy7 (Beckman Coulter), anti-CD14-FITC (Beckman Coulter), anti-CD3-PC5 (Biolegend (ITK)), anti-CD19-ECD (Beckman Coulter) and anti-56-PE (BD Bioscience)" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000044" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS534013_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "PBS supplemented with 1% PBA and antibodies: anti-CD45-PeCy7 (Beckman Coulter), anti-CD14-FITC (Beckman Coulter), anti-CD3-PC5 (Biolegend (ITK)), anti-CD19-ECD (Beckman Coulter) and anti-56-PE (BD Bioscience)" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000185" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS534014_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "PBS supplemented with 1% PBA and antibodies: anti-CD45-PeCy7 (Beckman Coulter), anti-CD14-FITC (Beckman Coulter), anti-CD3-PC5 (Biolegend (ITK)), anti-CD19-ECD (Beckman Coulter) and anti-56-PE (BD Bioscience)" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000190" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS534015_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "PBS supplemented with 1% PBA and antibodies: anti-CD45-PeCy7 (Beckman Coulter), anti-CD14-FITC (Beckman Coulter), anti-CD3-PC5 (Biolegend (ITK)), anti-CD19-ECD (Beckman Coulter) and anti-56-PE (BD Bioscience)" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000117" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS534016_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "PBS supplemented with 1% PBA and antibodies: anti-CD45-PeCy7 (Beckman Coulter), anti-CD14-FITC (Beckman Coulter), anti-CD3-PC5 (Biolegend (ITK)), anti-CD19-ECD (Beckman Coulter) and anti-56-PE (BD Bioscience)" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000156" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS631529_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000310" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS631530_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000309" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS631531_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000304" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS631532_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000301" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS631533_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000284" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS631534_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000353" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS631535_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000256" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS631536_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000308" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS640348_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000330" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS640349_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000248" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS640350_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000285" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS640351_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000317" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS642875_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000269" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS642990_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000303" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS643064_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/index.cfm?p=7BF8A4B6-F4FE-861A-2AD57A08D63D0B58" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000257" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS643233_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000250" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS643304_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001540" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655112_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000327" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655113_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000343" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655114_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000311" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655115_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000315" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655116_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000348" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655117_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000339" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655118_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000260" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655119_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000270" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655120_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000321" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS655121_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000316" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS663722_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000341" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS663725_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000349" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS663727_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000280" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS663728_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000258" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS663729_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000324" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS663730_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000279" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS663732_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000347" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS693841_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000252" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS697025_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001510" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS697064_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/index.cfm?p=7BF8A4B6-F4FE-861A-2AD57A08D63D0B58" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000158" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS697069_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000133" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS699839_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000249" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS699840_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000263" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS699842_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000295" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS699843_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000296" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS699844_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000277" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS699845_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001326" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS699846_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001518" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS715363_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001549" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS715364_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000329" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS715365_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001478" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS715366_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001486" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS728716_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001255" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS728717_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000335" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS728718_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000354" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS729251_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001413" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS729252_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001554" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS753992_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001508" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS753993_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001542" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS753994_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001536" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS753995_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001244" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS753996_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001378" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS753997_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001246" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS753998_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001482" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS753999_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001306" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS754000_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001557" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS754001_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001275" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS763495_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001335" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS763554_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000142" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS791875_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001398" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS791876_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001293" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS852681_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001297" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS852686_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000298" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS852687_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001362" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS902474_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001391" + }, + { + "experiment_ontology" : [ + { + "id" : "http://www.ebi.ac.uk/efo/EFO_0002692", + "label" : "ChIP-seq" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "ERS902490_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "assay" : "ChIP-Seq", + "culture_conditions" : "http://www.blueprint-epigenome.eu/UserFiles/file/Protocols/UCAM_BluePrint_Macrophage.pdf" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00001289" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS000201_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000828.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS000401_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000810.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS000501_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000743.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS000601_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000886.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS000701_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000728.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS000801_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000666.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS000901_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000808.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS001001_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000867.1" + }, + { + "experiment_ontology" : [ + { + "id" : "http://purl.obolibrary.org/obo/OBI_0001858", + "label" : "cross-linking immunoprecipitation high-throughput sequencing assay" + } + ], + "experiment_type" : "Histone H3K27ac", + "id" : "MS001101_H3K27ac", + "library_strategy" : null, + "molecule" : "genomic DNA", + "molecule_ontology" : null, + "other_fields" : { + "chip_antibody" : "anti-H3K27ac", + "chip_antibody_catalog" : "pAB-196-050", + "chip_antibody_lot" : "A1723-0041D", + "chip_antibody_provider" : "Diagenode", + "chip_protocol" : "The fragmented chromatin samples are used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies). Libraries were prepared according to the Illumina TruSeq DNA protocols with adaptations for low amounts of material; changes includes PCR enrichment prior to Size Selection instead of after. The libraries get size selected between 200-400 bp using automated electrophoresis gels (SAGE Science). Completed libraries are checked on the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "chip_protocol_antibody_amount" : "8ug", + "chip_protocol_bead_amount" : "32uL", + "chip_protocol_bead_type" : "Protein A beads (Diagenode)", + "chip_protocol_chromatin_amount" : "4 million cells", + "chip_protocol_crosslink_time" : "10 minutes", + "extraction_protocol" : "Samples are crosslinked with formaldehyde, quenched glycine and washed with PBS. The crosslinked samples are then sonicated using the waterbath-based Bioruptor (Diagenode) to create chromatin fragments containing 100-300 bp DNA fragments. The fragmented chromatin samples are then used for immunoprecipitations using the IP-Star (Diagenode) with validated histone antibodies. The immunoprecipitated chromatin pulldowns and inputs (without immunoprecipitation) are then reverse crosslinked, RNase treated, proteinase K treated and DNA recovered using silica-based purification columns (QIAgen). The purified samples are then quantified and profiled using the Agilent 2100 BioAnalyzer (Agilent Technologies).", + "extraction_protocol_sonication_cycles" : "NA", + "extraction_protocol_type_of_sonicator" : "Bioruptor (Diagenode)", + "library_generation_fragment_size_range" : "200-400 bp" + }, + "qc_flags" : [], + "reference_registry_id" : "IHECRE00000704.1" + } +] From 5e8711e1b599e9e49689c620db741323e6727081 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Tue, 31 Mar 2020 10:58:10 -0400 Subject: [PATCH 69/95] remove migrations 0002 migration for experiments, remove empty test file --- .../migrations/0002_auto_20200326_1339.py | 25 ------------------- chord_metadata_service/mcode/tests.py | 3 --- 2 files changed, 28 deletions(-) delete mode 100644 chord_metadata_service/experiments/migrations/0002_auto_20200326_1339.py delete mode 100644 chord_metadata_service/mcode/tests.py diff --git a/chord_metadata_service/experiments/migrations/0002_auto_20200326_1339.py b/chord_metadata_service/experiments/migrations/0002_auto_20200326_1339.py deleted file mode 100644 index 1acd665d8..000000000 --- a/chord_metadata_service/experiments/migrations/0002_auto_20200326_1339.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.2.10 on 2020-03-26 17:39 - -import chord_metadata_service.restapi.validators -import django.contrib.postgres.fields.jsonb -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('experiments', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='experiment', - name='experiment_ontology', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), - ), - migrations.AlterField( - model_name='experiment', - name='molecule_ontology', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), - ), - ] diff --git a/chord_metadata_service/mcode/tests.py b/chord_metadata_service/mcode/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/chord_metadata_service/mcode/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. From 6e28434a96246efd294e0638a0ad30ed05f19aac Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 31 Mar 2020 13:30:55 -0400 Subject: [PATCH 70/95] experiments: define biosamples & donor --- chord_metadata_service/experiments/descriptions.py | 3 +++ chord_metadata_service/experiments/models.py | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/chord_metadata_service/experiments/descriptions.py b/chord_metadata_service/experiments/descriptions.py index 7e620fc3b..291df259f 100644 --- a/chord_metadata_service/experiments/descriptions.py +++ b/chord_metadata_service/experiments/descriptions.py @@ -15,6 +15,9 @@ "other_fields": "The other fields for the experiment", + "biosamples": "Biosamples on which this experiment was done", + "donor": "Donor on which this experiment was done", + **EXTRA_PROPERTIES } } diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 722039581..4ba2e1623 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -1,6 +1,5 @@ from django.db import models from django.db.models import CharField -from django.core.validators import RegexValidator from django.core.exceptions import ValidationError from django.contrib.postgres.fields import JSONField, ArrayField from chord_metadata_service.restapi.models import IndexableMixin @@ -44,10 +43,16 @@ class Experiment(models.Model, IndexableMixin): experiment_ontology = JSONField(null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) molecule_ontology = JSONField(null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) molecule = CharField(choices=MOLECULE, max_length=20, null=True, help_text=rec_help(d.EXPERIMENT, 'molecule')) - library_strategy = CharField(choices=LIBRARY_STRATEGY, max_length=25, help_text=rec_help(d.EXPERIMENT, 'library_strategy')) other_fields = JSONField(null=True, validators=[keyValueValidator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) + biosamples = models.ManyToManyField(Biosample, null=True, help_text=rec_help(d.EXPERIMENT, 'biosamples')) + donor = models.ForeignKey("Individual", on_delete=models.SET_NULL, null=True, help_text=rec_help(d.EXPERIMENT, 'donor')) + + def clean(self): + if not (self.biosamples or self.donor): + raise ValidationError('Either Biosamples or Donor must be specified') + def __str__(self): return str(self.id) From baf13df2d38435686281d604194134aa2f533ea9 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 31 Mar 2020 14:04:48 -0400 Subject: [PATCH 71/95] experiments: donor => individual --- .../experiments/descriptions.py | 2 +- chord_metadata_service/experiments/models.py | 8 +++++--- .../experiments/tests/test_models.py | 19 ++++++++++++++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/chord_metadata_service/experiments/descriptions.py b/chord_metadata_service/experiments/descriptions.py index 291df259f..1759c4814 100644 --- a/chord_metadata_service/experiments/descriptions.py +++ b/chord_metadata_service/experiments/descriptions.py @@ -16,7 +16,7 @@ "other_fields": "The other fields for the experiment", "biosamples": "Biosamples on which this experiment was done", - "donor": "Donor on which this experiment was done", + "individual": "Donor on which this experiment was done", **EXTRA_PROPERTIES } diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 4ba2e1623..77d5ab847 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -6,6 +6,8 @@ from chord_metadata_service.restapi.description_utils import rec_help from chord_metadata_service.restapi.validators import ontologyListValidator, keyValueValidator from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT +from chord_metadata_service.patients.models import Individual +from chord_metadata_service.phenopackets.models import Biosample import chord_metadata_service.experiments.descriptions as d class Experiment(models.Model, IndexableMixin): @@ -48,11 +50,11 @@ class Experiment(models.Model, IndexableMixin): other_fields = JSONField(null=True, validators=[keyValueValidator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) biosamples = models.ManyToManyField(Biosample, null=True, help_text=rec_help(d.EXPERIMENT, 'biosamples')) - donor = models.ForeignKey("Individual", on_delete=models.SET_NULL, null=True, help_text=rec_help(d.EXPERIMENT, 'donor')) + individual = models.ForeignKey(Individual, on_delete=models.SET_NULL, null=True, help_text=rec_help(d.EXPERIMENT, 'individual')) def clean(self): - if not (self.biosamples or self.donor): - raise ValidationError('Either Biosamples or Donor must be specified') + if not (self.biosamples or self.individual): + raise ValidationError('Either Biosamples or Individual must be specified') def __str__(self): return str(self.id) diff --git a/chord_metadata_service/experiments/tests/test_models.py b/chord_metadata_service/experiments/tests/test_models.py index 774cbd96d..d01fb0d6b 100644 --- a/chord_metadata_service/experiments/tests/test_models.py +++ b/chord_metadata_service/experiments/tests/test_models.py @@ -1,5 +1,6 @@ from django.test import TestCase from rest_framework import serializers +from chord_metadata_service.patients.models import Individual from ..models import Experiment @@ -7,6 +8,7 @@ class ExperimentTest(TestCase): """ Test module for Experiment model """ def setUp(self): + Individual.objects.create(id='patient:1', sex='FEMALE', age={"age": "P25Y3M2D"}) Experiment.objects.create( id='experiment:1', reference_registry_id='', @@ -25,23 +27,38 @@ def create(self, **kwargs): e.save() def test_validation(self): + individual_one = Individual.objects.get(id='patient:1') + + # Invalid experiment_ontology self.assertRaises(serializers.ValidationError, self.create, id='experiment:2', library_strategy='Bisulfite-Seq', experiment_type='Chromatin Accessibility', experiment_ontology=["invalid_value"], + individual=individual_one ) + # Invalid molecule_ontology self.assertRaises(serializers.ValidationError, self.create, id='experiment:2', library_strategy='Bisulfite-Seq', experiment_type='Chromatin Accessibility', molecule_ontology=[{"id": "some_id"}], + individual=individual_one ) + # Invalid value in other_fields self.assertRaises(serializers.ValidationError, self.create, id='experiment:2', library_strategy='Bisulfite-Seq', experiment_type='Chromatin Accessibility', - other_fields={"some_field": "value", "invalid_value": 42} + other_fields={"some_field": "value", "invalid_value": 42}, + individual=individual_one + ) + + # Missing individual or biosamples + self.assertRaises(serializers.ValidationError, self.create, + id='experiment:2', + library_strategy='Bisulfite-Seq', + experiment_type='Chromatin Accessibility' ) From f58d2eed58f63e24b6de488611c7c645d21f5127 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 31 Mar 2020 14:43:05 -0400 Subject: [PATCH 72/95] experiments: fix relations --- .../experiments/descriptions.py | 2 +- .../migrations/0003_auto_20200331_1841.py | 59 +++++++++++++++++++ chord_metadata_service/experiments/models.py | 18 +++--- .../experiments/tests/test_models.py | 5 +- 4 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 chord_metadata_service/experiments/migrations/0003_auto_20200331_1841.py diff --git a/chord_metadata_service/experiments/descriptions.py b/chord_metadata_service/experiments/descriptions.py index 1759c4814..993acf257 100644 --- a/chord_metadata_service/experiments/descriptions.py +++ b/chord_metadata_service/experiments/descriptions.py @@ -15,7 +15,7 @@ "other_fields": "The other fields for the experiment", - "biosamples": "Biosamples on which this experiment was done", + "biosample": "Biosamples on which this experiment was done", "individual": "Donor on which this experiment was done", **EXTRA_PROPERTIES diff --git a/chord_metadata_service/experiments/migrations/0003_auto_20200331_1841.py b/chord_metadata_service/experiments/migrations/0003_auto_20200331_1841.py new file mode 100644 index 000000000..6a16c6c2b --- /dev/null +++ b/chord_metadata_service/experiments/migrations/0003_auto_20200331_1841.py @@ -0,0 +1,59 @@ +# Generated by Django 2.2.11 on 2020-03-31 18:41 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('patients', '0004_auto_20200129_1537'), + ('phenopackets', '0004_auto_20200129_1537'), + ('experiments', '0002_auto_20200327_1728'), + ] + + operations = [ + migrations.AddField( + model_name='experiment', + name='biosample', + field=models.ForeignKey(blank=True, help_text='Biosamples on which this experiment was done', null=True, on_delete=django.db.models.deletion.SET_NULL, to='phenopackets.Biosample'), + ), + migrations.AddField( + model_name='experiment', + name='individual', + field=models.ForeignKey(blank=True, help_text='Donor on which this experiment was done', null=True, on_delete=django.db.models.deletion.SET_NULL, to='patients.Individual'), + ), + migrations.AlterField( + model_name='experiment', + name='experiment_ontology', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'ONTOLOGY_CLASS', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), + ), + migrations.AlterField( + model_name='experiment', + name='molecule', + field=models.CharField(blank=True, choices=[('total RNA', 'total RNA'), ('polyA RNA', 'polyA RNA'), ('cytoplasmic RNA', 'cytoplasmic RNA'), ('nuclear RNA', 'nuclear RNA'), ('small RNA', 'small RNA'), ('genomic DNA', 'genomic DNA'), ('protein', 'protein'), ('other', 'other')], help_text='(Controlled Vocabulary) The type of molecule that was extracted from the biological material. Include one of the following: total RNA, polyA RNA, cytoplasmic RNA, nuclear RNA, small RNA, genomic DNA, protein, or other.', max_length=20, null=True), + ), + migrations.AlterField( + model_name='experiment', + name='molecule_ontology', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'ONTOLOGY_CLASS', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), + ), + migrations.AlterField( + model_name='experiment', + name='other_fields', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The other fields for the experiment', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'KEY_VALUE_OBJECT', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'The schema represents a key-value object.', 'patternProperties': {'^.*$': {'type': 'string'}}, 'title': 'Key-value object', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='experiment', + name='qc_flags', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(help_text='Any quanlity control observations can be noted here. This field can be omitted if empty', max_length=100), blank=True, default=list, null=True, size=None), + ), + migrations.AlterField( + model_name='experiment', + name='reference_registry_id', + field=models.CharField(blank=True, help_text='The IHEC EpiRR ID for this dataset, only for IHEC Reference Epigenome datasets. Otherwise leave empty.', max_length=30, null=True), + ), + ] diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 77d5ab847..13a2f78e8 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -39,21 +39,21 @@ class Experiment(models.Model, IndexableMixin): id = CharField(primary_key=True, max_length=200, help_text=rec_help(d.EXPERIMENT, 'id')) - reference_registry_id = CharField(max_length=30, null=True, help_text=rec_help(d.EXPERIMENT, 'reference_registry_id')) - qc_flags = ArrayField(CharField(max_length=100, help_text=rec_help(d.EXPERIMENT, 'qc_flags')), null=True, default=list) + reference_registry_id = CharField(max_length=30, blank=True, null=True, help_text=rec_help(d.EXPERIMENT, 'reference_registry_id')) + qc_flags = ArrayField(CharField(max_length=100, help_text=rec_help(d.EXPERIMENT, 'qc_flags')), null=True, blank=True, default=list) experiment_type = CharField(max_length=30, help_text=rec_help(d.EXPERIMENT, 'experiment_type')) - experiment_ontology = JSONField(null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) - molecule_ontology = JSONField(null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) - molecule = CharField(choices=MOLECULE, max_length=20, null=True, help_text=rec_help(d.EXPERIMENT, 'molecule')) + experiment_ontology = JSONField(blank=True, null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) + molecule_ontology = JSONField(blank=True, null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) + molecule = CharField(choices=MOLECULE, max_length=20, blank=True, null=True, help_text=rec_help(d.EXPERIMENT, 'molecule')) library_strategy = CharField(choices=LIBRARY_STRATEGY, max_length=25, help_text=rec_help(d.EXPERIMENT, 'library_strategy')) - other_fields = JSONField(null=True, validators=[keyValueValidator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) + other_fields = JSONField(blank=True, null=True, validators=[keyValueValidator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) - biosamples = models.ManyToManyField(Biosample, null=True, help_text=rec_help(d.EXPERIMENT, 'biosamples')) - individual = models.ForeignKey(Individual, on_delete=models.SET_NULL, null=True, help_text=rec_help(d.EXPERIMENT, 'individual')) + biosample = models.ForeignKey(Biosample, on_delete=models.SET_NULL, blank=True, null=True, help_text=rec_help(d.EXPERIMENT, 'biosample')) + individual = models.ForeignKey(Individual, on_delete=models.SET_NULL, blank=True, null=True, help_text=rec_help(d.EXPERIMENT, 'individual')) def clean(self): - if not (self.biosamples or self.individual): + if not (self.biosample or self.individual): raise ValidationError('Either Biosamples or Individual must be specified') def __str__(self): diff --git a/chord_metadata_service/experiments/tests/test_models.py b/chord_metadata_service/experiments/tests/test_models.py index d01fb0d6b..cd5e5b947 100644 --- a/chord_metadata_service/experiments/tests/test_models.py +++ b/chord_metadata_service/experiments/tests/test_models.py @@ -1,4 +1,5 @@ from django.test import TestCase +from django.core.exceptions import ValidationError from rest_framework import serializers from chord_metadata_service.patients.models import Individual from ..models import Experiment @@ -11,7 +12,7 @@ def setUp(self): Individual.objects.create(id='patient:1', sex='FEMALE', age={"age": "P25Y3M2D"}) Experiment.objects.create( id='experiment:1', - reference_registry_id='', + reference_registry_id='some_id', qc_flags=['flag 1', 'flag 2'], experiment_type='Chromatin Accessibility', experiment_ontology=[{"id": "ontology:1", "label": "Ontology term 1"}], @@ -57,7 +58,7 @@ def test_validation(self): ) # Missing individual or biosamples - self.assertRaises(serializers.ValidationError, self.create, + self.assertRaises(ValidationError, self.create, id='experiment:2', library_strategy='Bisulfite-Seq', experiment_type='Chromatin Accessibility' From 49ffee7a961d8d88cc83ec025ff2678d813f6e02 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Tue, 31 Mar 2020 18:44:54 -0400 Subject: [PATCH 73/95] changes according to code review comments --- chord_metadata_service/mcode/api_views.py | 18 +-- chord_metadata_service/mcode/descriptions.py | 2 +- .../mcode/migrations/0001_initial.py | 138 ++++++++++------ .../migrations/0002_auto_20200311_1610.py | 101 ------------ .../migrations/0003_auto_20200324_1625.py | 44 ----- .../migrations/0004_auto_20200325_1920.py | 151 ------------------ .../migrations/0005_auto_20200326_1339.py | 45 ------ chord_metadata_service/mcode/models.py | 99 ++++++------ .../mcode/tests/constants.py | 2 +- chord_metadata_service/restapi/validators.py | 9 +- examples/mcode_example.json | 2 +- setup.py | 2 +- 12 files changed, 161 insertions(+), 452 deletions(-) delete mode 100644 chord_metadata_service/mcode/migrations/0002_auto_20200311_1610.py delete mode 100644 chord_metadata_service/mcode/migrations/0003_auto_20200324_1625.py delete mode 100644 chord_metadata_service/mcode/migrations/0004_auto_20200325_1920.py delete mode 100644 chord_metadata_service/mcode/migrations/0005_auto_20200326_1339.py diff --git a/chord_metadata_service/mcode/api_views.py b/chord_metadata_service/mcode/api_views.py index c805936bb..4e3c26105 100644 --- a/chord_metadata_service/mcode/api_views.py +++ b/chord_metadata_service/mcode/api_views.py @@ -13,45 +13,45 @@ class McodeModelViewSet(viewsets.ModelViewSet): class GeneticVariantTestedViewSet(McodeModelViewSet): - queryset = GeneticVariantTested.objects.all().order_by("id") + queryset = GeneticVariantTested.objects.all() serializer_class = GeneticVariantTestedSerializer class GeneticVariantFoundViewSet(McodeModelViewSet): - queryset = GeneticVariantFound.objects.all().order_by("id") + queryset = GeneticVariantFound.objects.all() serializer_class = GeneticVariantFoundSerializer class GenomicsReportViewSet(McodeModelViewSet): - queryset = GenomicsReport.objects.all().order_by("id") + queryset = GenomicsReport.objects.all() serializer_class = GenomicsReportSerializer class LabsVitalViewSet(McodeModelViewSet): - queryset = LabsVital.objects.all().order_by("id") + queryset = LabsVital.objects.all() serializer_class = LabsVitalSerializer class CancerConditionViewSet(McodeModelViewSet): - queryset = CancerCondition.objects.all().order_by("id") + queryset = CancerCondition.objects.all() serializer_class = CancerConditionSerializer class TNMStagingViewSet(McodeModelViewSet): - queryset = TNMStaging.objects.all().order_by("id") + queryset = TNMStaging.objects.all() serializer_class = TNMStagingSerializer class CancerRelatedProcedureViewSet(McodeModelViewSet): - queryset = CancerRelatedProcedure.objects.all().order_by("id") + queryset = CancerRelatedProcedure.objects.all() serializer_class = CancerRelatedProcedureSerializer class MedicationStatementViewSet(McodeModelViewSet): - queryset = MedicationStatement.objects.all().order_by("id") + queryset = MedicationStatement.objects.all() serializer_class = MedicationStatementSerializer class MCodePacketViewSet(McodeModelViewSet): - queryset = MCodePacket.objects.all().order_by("id") + queryset = MCodePacket.objects.all() serializer_class = MCodePacketSerializer diff --git a/chord_metadata_service/mcode/descriptions.py b/chord_metadata_service/mcode/descriptions.py index 738a199a6..828820ad5 100644 --- a/chord_metadata_service/mcode/descriptions.py +++ b/chord_metadata_service/mcode/descriptions.py @@ -48,7 +48,7 @@ "id": "An arbitrary identifier for the genetics report.", "test_name": "An ontology or controlled vocabulary term to identify the laboratory test. " "Accepted value sets: LOINC, GTR.", - "performing_ogranization_name": "The name of the organization producing the genomics report.", + "performing_organization_name": "The name of the organization producing the genomics report.", "specimen_type": "An ontology or controlled vocabulary term to identify the type of material the specimen " "contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.", "genetic_variant_tested": "A test for a specific mutation on a particular gene.", diff --git a/chord_metadata_service/mcode/migrations/0001_initial.py b/chord_metadata_service/mcode/migrations/0001_initial.py index 912a4d296..e44b7d689 100644 --- a/chord_metadata_service/mcode/migrations/0001_initial.py +++ b/chord_metadata_service/mcode/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 2.2.10 on 2020-03-09 19:32 +# Generated by Django 2.2.10 on 2020-03-31 22:39 import chord_metadata_service.restapi.models +import chord_metadata_service.restapi.validators import django.contrib.postgres.fields import django.contrib.postgres.fields.jsonb from django.db import migrations, models @@ -12,8 +13,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('patients', '0004_auto_20200129_1537'), ('phenopackets', '0004_auto_20200129_1537'), + ('patients', '0005_auto_20200311_1610'), ] operations = [ @@ -22,106 +23,141 @@ class Migration(migrations.Migration): fields=[ ('id', models.CharField(help_text='An arbitrary identifier for the cancer condition.', max_length=200, primary_key=True, serialize=False)), ('condition_type', models.CharField(choices=[('primary', 'primary'), ('secondary', 'secondary')], help_text='Cancer condition type: primary or secondary.', max_length=200)), - ('body_location_code', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), blank=True, help_text='Code for the body location, optionally pre-coordinating laterality or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True, size=None)), - ('clinical_status', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A flag indicating whether the condition is active or inactive, recurring, in remission, or resolved (as of the last update of the Condition). Accepted code system: http://terminology.hl7.org/CodeSystem/condition-clinical', null=True)), - ('condition_code', django.contrib.postgres.fields.jsonb.JSONField(help_text='A code describing the type of primary or secondary malignant neoplastic disease.')), + ('body_location_code', django.contrib.postgres.fields.jsonb.JSONField(help_text='Code for the body location, optionally pre-coordinating laterality or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})])), + ('clinical_status', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A flag indicating whether the condition is active or inactive, recurring, in remission, or resolved (as of the last update of the Condition). Accepted code system: http://terminology.hl7.org/CodeSystem/condition-clinical', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), + ('condition_code', django.contrib.postgres.fields.jsonb.JSONField(help_text='A code describing the type of primary or secondary malignant neoplastic disease.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), ('date_of_diagnosis', models.DateTimeField(blank=True, help_text='The date the disease was first clinically recognized with sufficient certainty, regardless of whether it was fully characterized at that time.', null=True)), - ('histology_morphology_behavior', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A description of the morphologic and behavioral characteristics of the cancer. Accepted ontologies:SNOMED CT, ICD-O-3 and others.', null=True)), - ('subject', models.ForeignKey(help_text='The subject of the study that has a cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ('histology_morphology_behavior', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A description of the morphologic and behavioral characteristics of the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), ], + options={ + 'ordering': ['id'], + }, + bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), + ), + migrations.CreateModel( + name='CancerRelatedProcedure', + fields=[ + ('id', models.CharField(help_text='An arbitrary identifier for the procedure.', max_length=200, primary_key=True, serialize=False)), + ('procedure_type', models.CharField(choices=[('radiation', 'radiation'), ('surgical', 'surgical')], help_text='Type of cancer related procedure: radion or surgical.', max_length=200)), + ('code', django.contrib.postgres.fields.jsonb.JSONField(help_text='Code for the procedure performed.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), + ('occurence_time_or_period', django.contrib.postgres.fields.jsonb.JSONField(help_text='The date/time that a procedure was performed.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:time_or_period', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Time of Period schema.', 'properties': {'value': {'anyOf': [{'format': 'date-time', 'type': 'string'}, {'$id': 'chord_metadata_service:period_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Period schema.', 'properties': {'end': {'format': 'date-time', 'type': 'string'}, 'start': {'format': 'date-time', 'type': 'string'}}, 'title': 'Period', 'type': 'object'}]}}, 'title': 'Time of Period', 'type': 'object'})])), + ('target_body_site', django.contrib.postgres.fields.jsonb.JSONField(help_text='The body location(s) where the procedure was performed.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})])), + ('treatment_intent', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), + ], + options={ + 'ordering': ['id'], + }, bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), migrations.CreateModel( name='GeneticVariantFound', fields=[ ('id', models.CharField(help_text='An arbitrary identifier for the genetic variant found.', max_length=200, primary_key=True, serialize=False)), - ('method', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify the method used to perform the genetic test. Accepted value set: NCIT', null=True)), - ('variant_found_identifier', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). Accepted value set: ClinVar.', null=True)), + ('method', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), + ('variant_found_identifier', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). Accepted value set: ClinVar.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), ('variant_found_hgvs_name', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Symbolic representation of the variant used in HGVS, for example, NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.', null=True, size=None)), ('variant_found_description', models.CharField(blank=True, help_text='Description of the variant.', max_length=200)), - ('genomic_source_class', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify the genomic class of the specimen being analyzed.', null=True)), + ('genomic_source_class', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the genomic class of the specimen being analyzed.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), ], + options={ + 'ordering': ['id'], + }, bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), migrations.CreateModel( name='GeneticVariantTested', fields=[ ('id', models.CharField(help_text='An arbitrary identifier for the genetic variant tested.', max_length=200, primary_key=True, serialize=False)), - ('method', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify the method used to perform the genetic test. Accepted value set: NCIT', null=True)), - ('variant_tested_identifier', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR).', null=True)), + ('method', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), + ('variant_tested_identifier', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR).', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), ('variant_tested_hgvs_name', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Symbolic representation of the variant used in HGVS, for example, NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.', null=True, size=None)), ('variant_tested_description', models.CharField(blank=True, help_text='Description of the variant.', max_length=200)), - ('data_value', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify positive or negative value for the mutation. Accepted value set: SNOMED CT.', null=True)), + ('data_value', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify positive or negative value forthe mutation. Accepted value set: SNOMED CT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), ('gene_studied', models.ForeignKey(blank=True, help_text='A gene targeted for mutation analysis, identified in HUGO Gene Nomenclature Committee (HGNC) notation.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='phenopackets.Gene')), ], + options={ + 'ordering': ['id'], + }, bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), migrations.CreateModel( - name='TNMStaging', + name='GenomicsReport', fields=[ - ('id', models.CharField(help_text='An arbitrary identifier for the TNM staging.', max_length=200, primary_key=True, serialize=False)), - ('tnm_type', models.CharField(choices=[('clinical', 'clinical'), ('pathologic', 'pathologic')], help_text='TNM type: clinical or pathological.', max_length=200)), - ('stage_group', django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system. Accepted ontologies: SNOMED CT, AJCC and others.')), - ('primary_tumor_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the primary tumor, based on its size and extent. Accepted ontologies: SNOMED CT, AJCC and others.')), - ('regional_nodes_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the presence or absence of metastases in regional lymph nodes. Accepted ontologies: SNOMED CT, AJCC and others.')), - ('distant_metastases_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category describing the presence or absence of metastases in remote anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.')), - ('cancer_condition', models.ForeignKey(help_text='Cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='mcode.CancerCondition')), + ('id', models.CharField(help_text='An arbitrary identifier for the genetics report.', max_length=200, primary_key=True, serialize=False)), + ('test_name', django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test. Accepted value sets: LOINC, GTR.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), + ('performing_organization_name', models.CharField(blank=True, help_text='The name of the organization producing the genomics report.', max_length=200)), + ('specimen_type', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the type of material the specimen contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), + ('genetic_variant_found', models.ManyToManyField(blank=True, help_text='Records an alteration in the most common DNA nucleotide sequence.', to='mcode.GeneticVariantFound')), + ('genetic_variant_tested', models.ManyToManyField(blank=True, help_text='A test for a specific mutation on a particular gene.', to='mcode.GeneticVariantTested')), ], + options={ + 'ordering': ['id'], + }, bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), migrations.CreateModel( name='MedicationStatement', fields=[ ('id', models.CharField(help_text='An arbitrary identifier for the medication statement.', max_length=200, primary_key=True, serialize=False)), - ('medication_code', django.contrib.postgres.fields.jsonb.JSONField(help_text='A code for medication. Accepted code systems:Medication Clinical Drug (RxNorm) and other.')), - ('termination_reason', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), blank=True, help_text='A code explaining unplanned or premature termination of a course ofmedication. Accepted ontologies: SNOMED CT.', null=True, size=None)), - ('treatment_intent', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment.Accepted ontologies: SNOMED CT.', null=True)), + ('medication_code', django.contrib.postgres.fields.jsonb.JSONField(help_text='A code for medication. Accepted code systems: Medication Clinical Drug (RxNorm) and other.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), + ('termination_reason', django.contrib.postgres.fields.jsonb.JSONField(help_text='A code explaining unplanned or premature termination of a course of medication. Accepted ontologies: SNOMED CT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})])), + ('treatment_intent', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment. Accepted ontologies: SNOMED CT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})])), ('start_date', models.DateTimeField(blank=True, help_text='The start date/time of the medication.', null=True)), ('end_date', models.DateTimeField(blank=True, help_text='The end date/time of the medication.', null=True)), ('date_time', models.DateTimeField(blank=True, help_text='The date/time the medication was administered.', null=True)), - ('subject', models.ForeignKey(help_text='Subject of medication statement.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), ], + options={ + 'ordering': ['id'], + }, bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), migrations.CreateModel( - name='LabsVital', + name='TNMStaging', fields=[ - ('id', models.CharField(help_text='An arbitrary identifier for the labs/vital tests.', max_length=200, primary_key=True, serialize=False)), - ('body_height', django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's height.")), - ('body_weight', django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's weight.")), - ('cbc_with_auto_differential_panel', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Reference to a laboratory observation in the CBC with Auto Differential Panel test. ', null=True, size=None)), - ('comprehensive_metabolic_2000', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Reference to a laboratory observation in the CMP 2000 test.', null=True, size=None)), - ('blood_pressure_diastolic', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure after the contraction of the heart while the chambers of the heart refill with blood, when the pressure is lowest.', null=True)), - ('blood_pressure_systolic', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure during the contraction of the left ventricle of the heart, when blood pressure is at its highest.', null=True)), - ('tumor_marker_test', django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to indetify tumor marker test.')), - ('individual', models.ForeignKey(help_text='The individual who is the subject of the tests.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ('id', models.CharField(help_text='An arbitrary identifier for the TNM staging.', max_length=200, primary_key=True, serialize=False)), + ('tnm_type', models.CharField(choices=[('clinical', 'clinical'), ('pathologic', 'pathologic')], help_text='TNM type: clinical or pathological.', max_length=200)), + ('stage_group', django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system.Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})])), + ('primary_tumor_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the primary tumor, based on its size and extent. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})])), + ('regional_nodes_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the presence or absence of metastases in regional lymph nodes. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})])), + ('distant_metastases_category', django.contrib.postgres.fields.jsonb.JSONField(help_text='Category describing the presence or absence of metastases in remote anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})])), + ('cancer_condition', models.ForeignKey(help_text='Cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='mcode.CancerCondition')), ], + options={ + 'ordering': ['id'], + }, bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), migrations.CreateModel( - name='GenomicsReport', + name='MCodePacket', fields=[ - ('id', models.CharField(help_text='An arbitrary identifier for the genetics report.', max_length=200, primary_key=True, serialize=False)), - ('test_name', django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test.Accepted value sets: LOINC, GTR')), - ('performing_ogranization_name', models.CharField(blank=True, help_text='The name of the organization producing the genomics report.', max_length=200)), - ('specimen_type', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to indetify the type of material the specimen contains or consists of.Accepted value set: HL7 Version 2 and Specimen Type.', null=True)), - ('genetic_variant_found', models.ManyToManyField(blank=True, help_text='Records an alteration in the most common DNA nucleotide sequence.', to='mcode.GeneticVariantFound')), - ('genetic_variant_tested', models.ManyToManyField(blank=True, help_text='A test for a specific mutation on a particular gene.', to='mcode.GeneticVariantTested')), - ('subject', models.ForeignKey(help_text='Subject of genomics report.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ('id', models.CharField(help_text='An arbitrary identifier for the mcodepacket.', max_length=200, primary_key=True, serialize=False)), + ('cancer_condition', models.ForeignKey(blank=True, help_text="An Individual's cancer condition.", null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.CancerCondition')), + ('cancer_related_procedures', models.ManyToManyField(blank=True, help_text='A radiological or surgical procedures addressing a cancer condition.', to='mcode.CancerRelatedProcedure')), + ('genomics_report', models.ForeignKey(blank=True, help_text='A genomics report associated with an Individual.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.GenomicsReport')), + ('medication_statement', models.ForeignKey(blank=True, help_text='Medication treatment addressed to an Individual.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.MedicationStatement')), + ('subject', models.ForeignKey(help_text='An individual who is a subject of mcodepacket.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), ], + options={ + 'ordering': ['id'], + }, bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), migrations.CreateModel( - name='CancerRelatedProcedure', + name='LabsVital', fields=[ - ('id', models.CharField(help_text='An arbitrary identifier for the procedure.', max_length=200, primary_key=True, serialize=False)), - ('procedure_type', models.CharField(choices=[('radiation', 'radiation'), ('surgical', 'surgical')], help_text='Type of cancer related procedure: radion or surgical procedure.', max_length=200)), - ('code', django.contrib.postgres.fields.jsonb.JSONField(help_text='Code for the procedure performed.')), - ('occurence_time_or_period', django.contrib.postgres.fields.jsonb.JSONField(help_text='The date/time that a procedure was performed.')), - ('target_body_site', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), blank=True, help_text='The body location(s) where the procedure was performed.', null=True, size=None)), - ('treatment_intent', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment.', null=True)), - ('subject', models.ForeignKey(help_text='The patient who has a cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), + ('id', models.CharField(help_text='An arbitrary identifier for the labs/vital tests.', max_length=200, primary_key=True, serialize=False)), + ('body_height', django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's height.", validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})])), + ('body_weight', django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's weight.", validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})])), + ('cbc_with_auto_differential_panel', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Reference to a laboratory observation in the CBC with Auto DifferentialPanel test.', null=True, size=None)), + ('comprehensive_metabolic_2000', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Reference to a laboratory observation in the CMP 2000 test.', null=True, size=None)), + ('blood_pressure_diastolic', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure after the contraction of the heart while the chambers of the heart refill with blood, when the pressure is lowest.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})])), + ('blood_pressure_systolic', django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure during the contraction of the left ventricle of the heart, when blood pressure is at its highest.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})])), + ('tumor_marker_test', django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify tumor marker test.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:tumor_marker_test', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Tumor marker test schema.', 'properties': {'code': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'data_value': {'anyOf': [{'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, {'$id': 'chord_metadata_service:ratio', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Ratio schema.', 'properties': {'denominator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, 'numerator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}}, 'title': 'Ratio', 'type': 'object'}]}}, 'required': ['code'], 'title': 'Tumor marker test', 'type': 'object'})])), + ('individual', models.ForeignKey(help_text='The individual who is the subject of the tests.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), ], + options={ + 'ordering': ['id'], + }, bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), ), ] diff --git a/chord_metadata_service/mcode/migrations/0002_auto_20200311_1610.py b/chord_metadata_service/mcode/migrations/0002_auto_20200311_1610.py deleted file mode 100644 index bfc0ee4f0..000000000 --- a/chord_metadata_service/mcode/migrations/0002_auto_20200311_1610.py +++ /dev/null @@ -1,101 +0,0 @@ -# Generated by Django 2.2.10 on 2020-03-11 20:10 - -import django.contrib.postgres.fields -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('mcode', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='cancercondition', - name='histology_morphology_behavior', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A description of the morphologic and behavioral characteristics of the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True), - ), - migrations.AlterField( - model_name='cancercondition', - name='subject', - field=models.ForeignKey(help_text='The subject (Patient) of the study that has a cancer condition.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual'), - ), - migrations.AlterField( - model_name='cancerrelatedprocedure', - name='procedure_type', - field=models.CharField(choices=[('radiation', 'radiation'), ('surgical', 'surgical')], help_text='Type of cancer related procedure: radion or surgical.', max_length=200), - ), - migrations.AlterField( - model_name='geneticvariantfound', - name='genomic_source_class', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the genomic class of the specimen being analyzed.', null=True), - ), - migrations.AlterField( - model_name='geneticvariantfound', - name='method', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True), - ), - migrations.AlterField( - model_name='geneticvarianttested', - name='data_value', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify positive or negative value forthe mutation. Accepted value set: SNOMED CT.', null=True), - ), - migrations.AlterField( - model_name='geneticvarianttested', - name='method', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True), - ), - migrations.AlterField( - model_name='genomicsreport', - name='performing_ogranization_name', - field=models.CharField(blank=True, help_text='The name of the organization producing the genomics report.', max_length=200), - ), - migrations.AlterField( - model_name='genomicsreport', - name='specimen_type', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the type of material the specimen contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.', null=True), - ), - migrations.AlterField( - model_name='genomicsreport', - name='subject', - field=models.ForeignKey(help_text='Subject (Patient) of genomics report.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual'), - ), - migrations.AlterField( - model_name='genomicsreport', - name='test_name', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test. Accepted value sets: LOINC, GTR.'), - ), - migrations.AlterField( - model_name='labsvital', - name='cbc_with_auto_differential_panel', - field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, help_text='Reference to a laboratory observation in the CBC with Auto DifferentialPanel test.', null=True, size=None), - ), - migrations.AlterField( - model_name='labsvital', - name='tumor_marker_test', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify tumor marker test.'), - ), - migrations.AlterField( - model_name='medicationstatement', - name='medication_code', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='A code for medication. Accepted code systems: Medication Clinical Drug (RxNorm) and other.'), - ), - migrations.AlterField( - model_name='medicationstatement', - name='termination_reason', - field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True), blank=True, help_text='A code explaining unplanned or premature termination of a course of medication. Accepted ontologies: SNOMED CT.', null=True, size=None), - ), - migrations.AlterField( - model_name='medicationstatement', - name='treatment_intent', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment. Accepted ontologies: SNOMED CT.', null=True), - ), - migrations.AlterField( - model_name='tnmstaging', - name='stage_group', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system.Accepted ontologies: SNOMED CT, AJCC and others.'), - ), - ] diff --git a/chord_metadata_service/mcode/migrations/0003_auto_20200324_1625.py b/chord_metadata_service/mcode/migrations/0003_auto_20200324_1625.py deleted file mode 100644 index 80877efda..000000000 --- a/chord_metadata_service/mcode/migrations/0003_auto_20200324_1625.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 2.2.10 on 2020-03-24 20:25 - -import chord_metadata_service.restapi.models -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('patients', '0005_auto_20200311_1610'), - ('mcode', '0002_auto_20200311_1610'), - ] - - operations = [ - migrations.RemoveField( - model_name='cancercondition', - name='subject', - ), - migrations.RemoveField( - model_name='cancerrelatedprocedure', - name='subject', - ), - migrations.RemoveField( - model_name='genomicsreport', - name='subject', - ), - migrations.RemoveField( - model_name='medicationstatement', - name='subject', - ), - migrations.CreateModel( - name='MCodePacket', - fields=[ - ('id', models.CharField(help_text='An arbitrary identifier for the mcodepacket.', max_length=200, primary_key=True, serialize=False)), - ('cancer_condition', models.ForeignKey(blank=True, help_text="An Individual's cancer condition.", null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.CancerCondition')), - ('cancer_related_procedures', models.ManyToManyField(blank=True, help_text='A radiological or surgical procedures addressing a cancer condition.', to='mcode.CancerRelatedProcedure')), - ('genomics_report', models.ForeignKey(blank=True, help_text='A genomics report associated with an Individual.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.GenomicsReport')), - ('medication_statement', models.ForeignKey(blank=True, help_text='Medication treatment addressed to an Individual.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='mcode.MedicationStatement')), - ('subject', models.ForeignKey(help_text='An individual who is a subject of mcodepacket.', on_delete=django.db.models.deletion.CASCADE, to='patients.Individual')), - ], - bases=(models.Model, chord_metadata_service.restapi.models.IndexableMixin), - ), - ] diff --git a/chord_metadata_service/mcode/migrations/0004_auto_20200325_1920.py b/chord_metadata_service/mcode/migrations/0004_auto_20200325_1920.py deleted file mode 100644 index 3e820e154..000000000 --- a/chord_metadata_service/mcode/migrations/0004_auto_20200325_1920.py +++ /dev/null @@ -1,151 +0,0 @@ -# Generated by Django 2.2.10 on 2020-03-25 23:20 - -import chord_metadata_service.restapi.validators -import django.contrib.postgres.fields -import django.contrib.postgres.fields.jsonb -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('mcode', '0003_auto_20200324_1625'), - ] - - operations = [ - migrations.AlterField( - model_name='cancercondition', - name='body_location_code', - field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='Code for the body location, optionally pre-coordinating laterality or direction. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True, size=None), - ), - migrations.AlterField( - model_name='cancercondition', - name='clinical_status', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A flag indicating whether the condition is active or inactive, recurring, in remission, or resolved (as of the last update of the Condition). Accepted code system: http://terminology.hl7.org/CodeSystem/condition-clinical', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='cancercondition', - name='condition_code', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='A code describing the type of primary or secondary malignant neoplastic disease.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='cancercondition', - name='histology_morphology_behavior', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A description of the morphologic and behavioral characteristics of the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='cancerrelatedprocedure', - name='code', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Code for the procedure performed.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='cancerrelatedprocedure', - name='target_body_site', - field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='The body location(s) where the procedure was performed.', null=True, size=None), - ), - migrations.AlterField( - model_name='cancerrelatedprocedure', - name='treatment_intent', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='geneticvariantfound', - name='genomic_source_class', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the genomic class of the specimen being analyzed.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='geneticvariantfound', - name='method', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='geneticvariantfound', - name='variant_found_identifier', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). Accepted value set: ClinVar.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='geneticvarianttested', - name='data_value', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify positive or negative value forthe mutation. Accepted value set: SNOMED CT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='geneticvarianttested', - name='method', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the method used to perform the genetic test. Accepted value set: NCIT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='geneticvarianttested', - name='variant_tested_identifier', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The variation ID assigned by HGVS, for example, 360448 is the identifier for NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR).', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='genomicsreport', - name='specimen_type', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology or controlled vocabulary term to identify the type of material the specimen contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='genomicsreport', - name='test_name', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify the laboratory test. Accepted value sets: LOINC, GTR.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='labsvital', - name='blood_pressure_diastolic', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure after the contraction of the heart while the chambers of the heart refill with blood, when the pressure is lowest.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='labsvital', - name='blood_pressure_systolic', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The blood pressure during the contraction of the left ventricle of the heart, when blood pressure is at its highest.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='labsvital', - name='body_height', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's height.", validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='labsvital', - name='body_weight', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text="The patient's weight.", validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='labsvital', - name='tumor_marker_test', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify tumor marker test.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:tumor_marker_test', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Tumor marker test schema.', 'properties': {'code': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'data_value': {'anyOf': [{'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, {'$id': 'chord_metadata_service:ratio', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Ratio schema.', 'properties': {'denominator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, 'numerator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}}, 'title': 'Ratio', 'type': 'object'}]}}, 'required': ['code'], 'title': 'Tumor marker test', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='medicationstatement', - name='medication_code', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='A code for medication. Accepted code systems: Medication Clinical Drug (RxNorm) and other.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='medicationstatement', - name='termination_reason', - field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='A code explaining unplanned or premature termination of a course of medication. Accepted ontologies: SNOMED CT.', null=True, size=None), - ), - migrations.AlterField( - model_name='medicationstatement', - name='treatment_intent', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The purpose of a treatment. Accepted ontologies: SNOMED CT.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='tnmstaging', - name='distant_metastases_category', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category describing the presence or absence of metastases in remote anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='tnmstaging', - name='primary_tumor_category', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the primary tumor, based on its size and extent. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='tnmstaging', - name='regional_nodes_category', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the presence or absence of metastases in regional lymph nodes. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='tnmstaging', - name='stage_group', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system.Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:codeable_concept_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Concept.', 'properties': {'coding': {'items': {'properties': {'code': {'type': 'string'}, 'display': {'type': 'string'}, 'system': {'format': 'uri', 'type': 'string'}, 'user_selected': {'type': 'boolean'}, 'version': {'type': 'string'}}, 'type': 'object'}, 'type': 'array'}, 'text': {'type': 'string'}}, 'title': 'Codeable Concept schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), - ), - ] diff --git a/chord_metadata_service/mcode/migrations/0005_auto_20200326_1339.py b/chord_metadata_service/mcode/migrations/0005_auto_20200326_1339.py deleted file mode 100644 index b934f2173..000000000 --- a/chord_metadata_service/mcode/migrations/0005_auto_20200326_1339.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 2.2.10 on 2020-03-26 17:39 - -import chord_metadata_service.restapi.validators -import django.contrib.postgres.fields.jsonb -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('mcode', '0004_auto_20200325_1920'), - ] - - operations = [ - migrations.AlterField( - model_name='cancerrelatedprocedure', - name='occurence_time_or_period', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The date/time that a procedure was performed.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:time_or_period', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Time of Period schema.', 'properties': {'value': {'anyOf': [{'format': 'date-time', 'type': 'string'}, {'$id': 'chord_metadata_service:period_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Period schema.', 'properties': {'end': {'format': 'date-time', 'type': 'string'}, 'start': {'format': 'date-time', 'type': 'string'}}, 'title': 'Period', 'type': 'object'}]}}, 'title': 'Time of Period', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='labsvital', - name='tumor_marker_test', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology or controlled vocabulary term to identify tumor marker test.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:tumor_marker_test', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Tumor marker test schema.', 'properties': {'code': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'data_value': {'anyOf': [{'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, {'$id': 'chord_metadata_service:ratio', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Ratio schema.', 'properties': {'denominator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}, 'numerator': {'$id': 'chord_metadata_service:quantity_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the datatype Quantity.', 'properties': {'code': {'type': 'string'}, 'comparator': {'enum': ['<', '>', '<=', '>=', '=']}, 'system': {'format': 'uri', 'type': 'string'}, 'unit': {'type': 'string'}, 'value': {'type': 'number'}}, 'title': 'Quantity schema', 'type': 'object'}}, 'title': 'Ratio', 'type': 'object'}]}}, 'required': ['code'], 'title': 'Tumor marker test', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='tnmstaging', - name='distant_metastases_category', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category describing the presence or absence of metastases in remote anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='tnmstaging', - name='primary_tumor_category', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the primary tumor, based on its size and extent. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='tnmstaging', - name='regional_nodes_category', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='Category of the presence or absence of metastases in regional lymph nodes. Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), - ), - migrations.AlterField( - model_name='tnmstaging', - name='stage_group', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The extent of the cancer in the body, according to the TNM classification system.Accepted ontologies: SNOMED CT, AJCC and others.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:complex_ontology_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Complex object to combine data value and staging system.', 'properties': {'data_value': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'staging_system': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': ['data_value'], 'title': 'Complex ontology', 'type': 'object'})]), - ), - ] diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index 12dae6abb..eb96e1e10 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -6,23 +6,12 @@ from django.core.exceptions import ValidationError from chord_metadata_service.restapi.description_utils import rec_help import chord_metadata_service.mcode.descriptions as d -from chord_metadata_service.restapi.validators import JsonSchemaValidator -from chord_metadata_service.restapi.schemas import ( - ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY, TIME_OR_PERIOD, TUMOR_MARKER_TEST +from chord_metadata_service.restapi.validators import ( + ontologyValidator, quantity_validator, tumor_marker_test_validator, + complex_ontology_validator, time_or_period_validator, ontologyListValidator ) -############################ Field validators ############################# - -ontology_class_validator = JsonSchemaValidator(ONTOLOGY_CLASS) -quantity_validator = JsonSchemaValidator(schema=QUANTITY, format_checker=['uri']) -tumor_marker_test_validator = JsonSchemaValidator(schema=TUMOR_MARKER_TEST) -complex_ontology_validator = JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri']) -time_or_period_validator = JsonSchemaValidator(schema=TIME_OR_PERIOD, format_checker=['date-time']) - -################################# Genomics ################################# - - class GeneticVariantTested(models.Model, IndexableMixin): """ Class to record an alteration in the most common DNA nucleotide sequence. @@ -34,18 +23,21 @@ class GeneticVariantTested(models.Model, IndexableMixin): # make writable if it doesn't exist gene_studied = models.ForeignKey(Gene, blank=True, null=True, on_delete=models.SET_NULL, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "gene_studied")) - method = JSONField(blank=True, null=True, validators=[ontology_class_validator], + method = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "method")) - variant_tested_identifier = JSONField(blank=True, null=True, validators=[ontology_class_validator], + variant_tested_identifier = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_identifier")) variant_tested_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_hgvs_name")) variant_tested_description = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_description")) - data_value = JSONField(blank=True, null=True, validators=[ontology_class_validator], + data_value = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "data_value")) + class Meta: + ordering = ['id'] + def __str__(self): return str(self.id) @@ -64,18 +56,21 @@ class GeneticVariantFound(models.Model, IndexableMixin): # TODO Discuss: Connection to Gene from Phenopackets id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "id")) - method = JSONField(blank=True, null=True, validators=[ontology_class_validator], + method = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "method")) - variant_found_identifier = JSONField(blank=True, null=True, validators=[ontology_class_validator], + variant_found_identifier = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_identifier")) variant_found_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_hgvs_name")) variant_found_description = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_description")) # loinc value set https://loinc.org/48002-0/ - genomic_source_class = JSONField(blank=True, null=True, validators=[ontology_class_validator], + genomic_source_class = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "genomic_source_class")) + class Meta: + ordering = ['id'] + def __str__(self): return str(self.id) @@ -91,16 +86,18 @@ class GenomicsReport(models.Model, IndexableMixin): """ id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.GENOMICS_REPORT, "id")) - test_name = JSONField(validators=[ontology_class_validator], help_text=rec_help(d.GENOMICS_REPORT, "test_name")) - performing_ogranization_name = models.CharField(max_length=200, blank=True, - help_text=rec_help(d.GENOMICS_REPORT, "performing_ogranization_name")) - specimen_type = JSONField(blank=True, null=True, validators=[ontology_class_validator], + test_name = JSONField(validators=[ontologyValidator], help_text=rec_help(d.GENOMICS_REPORT, "test_name")) + performing_organization_name = models.CharField(max_length=200, blank=True, + help_text=rec_help(d.GENOMICS_REPORT, "performing_organization_name")) + specimen_type = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENOMICS_REPORT, "specimen_type")) genetic_variant_tested = models.ManyToManyField(GeneticVariantTested, blank=True, help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_tested")) genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_found")) - # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.GENOMICS_REPORT, "subject")) + + class Meta: + ordering = ['id'] def __str__(self): return str(self.id) @@ -133,6 +130,9 @@ class LabsVital(models.Model, IndexableMixin): tumor_marker_test = JSONField(validators=[tumor_marker_test_validator], help_text=rec_help(d.LABS_VITAL, "tumor_marker_test")) + class Meta: + ordering = ['id'] + def __str__(self): return str(self.id) @@ -156,18 +156,19 @@ class CancerCondition(models.Model, IndexableMixin): condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, max_length=200, help_text=rec_help(d.CANCER_CONDITION, "condition_type")) # TODO add body_location_code validator array of json - body_location_code = ArrayField(JSONField(null=True, blank=True, validators=[ontology_class_validator]), - blank=True, null=True, - help_text=rec_help(d.CANCER_CONDITION, "body_location_code")) - clinical_status = JSONField(blank=True, null=True, validators=[ontology_class_validator], + body_location_code = JSONField(null=True, validators=[ontologyListValidator], + help_text=rec_help(d.CANCER_CONDITION, 'body_location_code')) + clinical_status = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.CANCER_CONDITION, "clinical_status")) - condition_code = JSONField(validators=[ontology_class_validator], + condition_code = JSONField(validators=[ontologyValidator], help_text=rec_help(d.CANCER_CONDITION, "condition_code")) date_of_diagnosis = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "date_of_diagnosis")) - histology_morphology_behavior = JSONField(blank=True, null=True, validators=[ontology_class_validator], + histology_morphology_behavior = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.CANCER_CONDITION, "histology_morphology_behavior")) - # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, help_text=rec_help(d.CANCER_CONDITION, "subject")) + + class Meta: + ordering = ['id'] def __str__(self): return str(self.id) @@ -195,6 +196,9 @@ class TNMStaging(models.Model, IndexableMixin): cancer_condition = models.ForeignKey(CancerCondition, on_delete=models.CASCADE, help_text=rec_help(d.TNM_STAGING, "cancer_condition")) + class Meta: + ordering = ['id'] + def __str__(self): return str(self.id) @@ -215,16 +219,16 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "id")) procedure_type = models.CharField(choices=PROCEDURE_TYPES, max_length=200, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "procedure_type")) - code = JSONField(validators=[ontology_class_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "code")) + code = JSONField(validators=[ontologyValidator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "code")) occurence_time_or_period = JSONField(validators=[time_or_period_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "occurence_time_or_period")) - target_body_site = ArrayField(JSONField(null=True, blank=True, validators=[ontology_class_validator]), - blank=True, null=True, - help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "target_body_site")) - treatment_intent = JSONField(blank=True, null=True, validators=[ontology_class_validator], + target_body_site = JSONField(null=True, validators=[ontologyListValidator], + help_text=rec_help(d.CANCER_RELATED_PROCEDURE, 'target_body_site')) + treatment_intent = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "treatment_intent")) - # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, - # help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "subject")) + + class Meta: + ordering = ['id'] def __str__(self): return str(self.id) @@ -238,18 +242,18 @@ class MedicationStatement(models.Model, IndexableMixin): id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.MEDICATION_STATEMENT, "id")) # list http://hl7.org/fhir/us/core/STU3.1/ValueSet-us-core-medication-codes.html - medication_code = JSONField(validators=[ontology_class_validator], + medication_code = JSONField(validators=[ontologyValidator], help_text=rec_help(d.MEDICATION_STATEMENT, "medication_code")) - termination_reason = ArrayField(JSONField(null=True, blank=True, validators=[ontology_class_validator]), - blank=True, null=True, - help_text=rec_help(d.MEDICATION_STATEMENT, "termination_reason")) - treatment_intent = JSONField(blank=True, null=True, validators=[ontology_class_validator], + termination_reason = JSONField(null=True, validators=[ontologyListValidator], + help_text=rec_help(d.MEDICATION_STATEMENT, 'termination_reason')) + treatment_intent = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.MEDICATION_STATEMENT, "treatment_intent")) start_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "start_date")) end_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "end_date")) date_time = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "date_time")) - # subject = models.ForeignKey(Individual, on_delete=models.CASCADE, - # help_text=rec_help(d.MEDICATION_STATEMENT, "subject")) + + class Meta: + ordering = ['id'] def __str__(self): return str(self.id) @@ -272,5 +276,8 @@ class MCodePacket(models.Model, IndexableMixin): medication_statement = models.ForeignKey(MedicationStatement, blank=True, null=True, on_delete=models.SET_NULL, help_text=rec_help(d.MCODEPACKET, "medication_statement")) + class Meta: + ordering = ['id'] + def __str__(self): return str(self.id) diff --git a/chord_metadata_service/mcode/tests/constants.py b/chord_metadata_service/mcode/tests/constants.py index 2f0d4ff31..7136643e0 100644 --- a/chord_metadata_service/mcode/tests/constants.py +++ b/chord_metadata_service/mcode/tests/constants.py @@ -120,7 +120,7 @@ def valid_genetic_report(): "id": "GTR000567625.2", "label": "PREVENTEST", }, - "performing_ogranization_name": "Test organization", + "performing_organization_name": "Test organization", "specimen_type": { "id": "119342007 ", "label": "SAL (Saliva)", diff --git a/chord_metadata_service/restapi/validators.py b/chord_metadata_service/restapi/validators.py index d99a97a3b..b24b293d5 100644 --- a/chord_metadata_service/restapi/validators.py +++ b/chord_metadata_service/restapi/validators.py @@ -1,6 +1,9 @@ from rest_framework import serializers from jsonschema import Draft7Validator, FormatChecker -from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT, AGE_OR_AGE_RANGE +from chord_metadata_service.restapi.schemas import ( + ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY, TIME_OR_PERIOD, TUMOR_MARKER_TEST, + ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT, AGE_OR_AGE_RANGE +) class JsonSchemaValidator(object): @@ -31,3 +34,7 @@ def deconstruct(self): ontologyListValidator = JsonSchemaValidator(ONTOLOGY_CLASS_LIST) keyValueValidator = JsonSchemaValidator(KEY_VALUE_OBJECT) ageOrAgeRangeValidator = JsonSchemaValidator(AGE_OR_AGE_RANGE) +quantity_validator = JsonSchemaValidator(schema=QUANTITY, format_checker=['uri']) +tumor_marker_test_validator = JsonSchemaValidator(schema=TUMOR_MARKER_TEST) +complex_ontology_validator = JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri']) +time_or_period_validator = JsonSchemaValidator(schema=TIME_OR_PERIOD, format_checker=['date-time']) diff --git a/examples/mcode_example.json b/examples/mcode_example.json index fe2ae4e7c..aa59b5e6c 100644 --- a/examples/mcode_example.json +++ b/examples/mcode_example.json @@ -18,7 +18,7 @@ "id": "GTR000511179.13", "label": "Clinical Exome" }, - "performing_ogranization_name": "Test organization", + "performing_organization_name": "Test organization", "specimen_type": { "id": "BDY", "label": "Whole body" diff --git a/setup.py b/setup.py index ce1ad029b..8298e672b 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,8 @@ "rdflib==4.2.2", "rdflib-jsonld==0.4.0", "requests>=2.23,<3.0", - "uritemplate>=3.0,<4.0", "rfc3987==1.3.8", + "uritemplate>=3.0,<4.0", ], author=config["package"]["authors"], From f836f615dc47eafda2cced125f10e47ac54fd142 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Wed, 1 Apr 2020 10:18:51 -0400 Subject: [PATCH 74/95] add extra_properties and created, updated fields --- chord_metadata_service/mcode/descriptions.py | 46 ++--- .../migrations/0002_auto_20200401_1008.py | 159 ++++++++++++++++++ chord_metadata_service/mcode/models.py | 36 ++++ 3 files changed, 219 insertions(+), 22 deletions(-) create mode 100644 chord_metadata_service/mcode/migrations/0002_auto_20200401_1008.py diff --git a/chord_metadata_service/mcode/descriptions.py b/chord_metadata_service/mcode/descriptions.py index 828820ad5..550c39ef6 100644 --- a/chord_metadata_service/mcode/descriptions.py +++ b/chord_metadata_service/mcode/descriptions.py @@ -4,6 +4,7 @@ # Portions of this text copyright (c) 2019-2020 the Canadian Centre for Computational Genomics; licensed under the # GNU Lesser General Public License version 3. +from chord_metadata_service.restapi.description_utils import EXTRA_PROPERTIES GENETIC_VARIANT_TESTED = { "description": "A description of an alteration in the most common DNA nucleotide sequence.", @@ -19,11 +20,11 @@ "NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.", "variant_tested_description": "Description of the variant.", "data_value": "An ontology or controlled vocabulary term to identify positive or negative value for" - "the mutation. Accepted value set: SNOMED CT." + "the mutation. Accepted value set: SNOMED CT.", + **EXTRA_PROPERTIES } } - GENETIC_VARIANT_FOUND = { "description": "Description of single discrete variant tested.", "properties": { @@ -34,14 +35,14 @@ "NM_005228.4(EGFR):c.-237A>G (single nucleotide variant in EGFR). " "Accepted value set: ClinVar.", "variant_found_hgvs_name": "Symbolic representation of the variant used in HGVS, for example, " - "NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.", + "NM_005228.4(EGFR):c.-237A>G for HVGS variation ID 360448.", "variant_found_description": "Description of the variant.", "genomic_source_class": "An ontology or controlled vocabulary term to identify the genomic class of the " - "specimen being analyzed." + "specimen being analyzed.", + **EXTRA_PROPERTIES } } - GENOMICS_REPORT = { "description": "Genetic Analysis Summary.", "properties": { @@ -52,11 +53,11 @@ "specimen_type": "An ontology or controlled vocabulary term to identify the type of material the specimen " "contains or consists of. Accepted value set: HL7 Version 2 and Specimen Type.", "genetic_variant_tested": "A test for a specific mutation on a particular gene.", - "genetic_variant_found": "Records an alteration in the most common DNA nucleotide sequence." + "genetic_variant_found": "Records an alteration in the most common DNA nucleotide sequence.", + **EXTRA_PROPERTIES } } - LABS_VITAL = { "description": "A description of tests performed on patient.", "properties": { @@ -70,12 +71,12 @@ "blood_pressure_diastolic": "The blood pressure after the contraction of the heart while the chambers of " "the heart refill with blood, when the pressure is lowest.", "blood_pressure_systolic": "The blood pressure during the contraction of the left ventricle of the heart, " - "when blood pressure is at its highest.", - "tumor_marker_test": "An ontology or controlled vocabulary term to identify tumor marker test." + "when blood pressure is at its highest.", + "tumor_marker_test": "An ontology or controlled vocabulary term to identify tumor marker test.", + **EXTRA_PROPERTIES } } - CANCER_CONDITION = { "description": "A description of history of primary or secondary cancer conditions.", "properties": { @@ -85,34 +86,34 @@ "Accepted ontologies: SNOMED CT, ICD-O-3 and others.", "clinical_status": "A flag indicating whether the condition is active or inactive, recurring, in remission, " "or resolved (as of the last update of the Condition). Accepted code system: " - "http://terminology.hl7.org/CodeSystem/condition-clinical", + "http://terminology.hl7.org/CodeSystem/condition-clinical", "condition_code": "A code describing the type of primary or secondary malignant neoplastic disease.", "date_of_diagnosis": "The date the disease was first clinically recognized with sufficient certainty, " "regardless of whether it was fully characterized at that time.", "histology_morphology_behavior": "A description of the morphologic and behavioral characteristics of " - "the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others." + "the cancer. Accepted ontologies: SNOMED CT, ICD-O-3 and others.", + **EXTRA_PROPERTIES } } - TNM_STAGING = { "description": "A description of the cancer spread in a patient's body.", "properties": { "id": "An arbitrary identifier for the TNM staging.", "tnm_type": "TNM type: clinical or pathological.", "stage_group": "The extent of the cancer in the body, according to the TNM classification system." - "Accepted ontologies: SNOMED CT, AJCC and others.", + "Accepted ontologies: SNOMED CT, AJCC and others.", "primary_tumor_category": "Category of the primary tumor, based on its size and extent. " "Accepted ontologies: SNOMED CT, AJCC and others.", "regional_nodes_category": "Category of the presence or absence of metastases in regional lymph nodes. " - "Accepted ontologies: SNOMED CT, AJCC and others.", + "Accepted ontologies: SNOMED CT, AJCC and others.", "distant_metastases_category": "Category describing the presence or absence of metastases in remote " "anatomical locations. Accepted ontologies: SNOMED CT, AJCC and others.", - "cancer_condition": "Cancer condition." + "cancer_condition": "Cancer condition.", + **EXTRA_PROPERTIES } } - CANCER_RELATED_PROCEDURE = { "description": "Description of radiological treatment or surgical action addressing a cancer condition.", "properties": { @@ -121,11 +122,11 @@ "code": "Code for the procedure performed.", "occurence_time_or_period": "The date/time that a procedure was performed.", "target_body_site": "The body location(s) where the procedure was performed.", - "treatment_intent": "The purpose of a treatment." + "treatment_intent": "The purpose of a treatment.", + **EXTRA_PROPERTIES } } - MEDICATION_STATEMENT = { "description": "Description of medication use.", "properties": { @@ -136,11 +137,11 @@ "treatment_intent": "The purpose of a treatment. Accepted ontologies: SNOMED CT.", "start_date": "The start date/time of the medication.", "end_date": "The end date/time of the medication.", - "date_time": "The date/time the medication was administered." + "date_time": "The date/time the medication was administered.", + **EXTRA_PROPERTIES } } - MCODEPACKET = { "description": "Collection of cancer related metadata.", "properties": { @@ -149,6 +150,7 @@ "genomics_report": "A genomics report associated with an Individual.", "cancer_condition": "An Individual's cancer condition.", "cancer_related_procedures": "A radiological or surgical procedures addressing a cancer condition.", - "medication_statement": "Medication treatment addressed to an Individual." + "medication_statement": "Medication treatment addressed to an Individual.", + **EXTRA_PROPERTIES } } diff --git a/chord_metadata_service/mcode/migrations/0002_auto_20200401_1008.py b/chord_metadata_service/mcode/migrations/0002_auto_20200401_1008.py new file mode 100644 index 000000000..9756f449e --- /dev/null +++ b/chord_metadata_service/mcode/migrations/0002_auto_20200401_1008.py @@ -0,0 +1,159 @@ +# Generated by Django 2.2.10 on 2020-04-01 14:08 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mcode', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='cancercondition', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='cancercondition', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='cancercondition', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='cancerrelatedprocedure', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='cancerrelatedprocedure', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='cancerrelatedprocedure', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='geneticvariantfound', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='geneticvariantfound', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='geneticvariantfound', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='geneticvarianttested', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='geneticvarianttested', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='geneticvarianttested', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='genomicsreport', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='genomicsreport', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='genomicsreport', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='labsvital', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='labsvital', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='labsvital', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='mcodepacket', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='mcodepacket', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='mcodepacket', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='medicationstatement', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='medicationstatement', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='medicationstatement', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='tnmstaging', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='tnmstaging', + name='extra_properties', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Extra properties that are not supported by current schema.', null=True), + ), + migrations.AddField( + model_name='tnmstaging', + name='updated', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + ] diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index eb96e1e10..cf061444c 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -34,6 +34,10 @@ class GeneticVariantTested(models.Model, IndexableMixin): "variant_tested_description")) data_value = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "data_value")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.GENETIC_VARIANT_TESTED, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] @@ -67,6 +71,10 @@ class GeneticVariantFound(models.Model, IndexableMixin): # loinc value set https://loinc.org/48002-0/ genomic_source_class = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "genomic_source_class")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.GENETIC_VARIANT_FOUND, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] @@ -95,6 +103,10 @@ class GenomicsReport(models.Model, IndexableMixin): help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_tested")) genetic_variant_found = models.ManyToManyField(GeneticVariantFound, blank=True, help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_found")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.GENOMICS_REPORT, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] @@ -129,6 +141,10 @@ class LabsVital(models.Model, IndexableMixin): #TODO Change CodeableConcept to Ontology class tumor_marker_test = JSONField(validators=[tumor_marker_test_validator], help_text=rec_help(d.LABS_VITAL, "tumor_marker_test")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.LABS_VITAL, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] @@ -166,6 +182,10 @@ class CancerCondition(models.Model, IndexableMixin): help_text=rec_help(d.CANCER_CONDITION, "date_of_diagnosis")) histology_morphology_behavior = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.CANCER_CONDITION, "histology_morphology_behavior")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.CANCER_CONDITION, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] @@ -195,6 +215,10 @@ class TNMStaging(models.Model, IndexableMixin): # TODO check if one cancer condition has many TNM Staging cancer_condition = models.ForeignKey(CancerCondition, on_delete=models.CASCADE, help_text=rec_help(d.TNM_STAGING, "cancer_condition")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.TNM_STAGING, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] @@ -226,6 +250,10 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): help_text=rec_help(d.CANCER_RELATED_PROCEDURE, 'target_body_site')) treatment_intent = JSONField(blank=True, null=True, validators=[ontologyValidator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "treatment_intent")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] @@ -251,6 +279,10 @@ class MedicationStatement(models.Model, IndexableMixin): start_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "start_date")) end_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "end_date")) date_time = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "date_time")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.MEDICATION_STATEMENT, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] @@ -275,6 +307,10 @@ class MCodePacket(models.Model, IndexableMixin): help_text=rec_help(d.MCODEPACKET, "cancer_related_procedures")) medication_statement = models.ForeignKey(MedicationStatement, blank=True, null=True, on_delete=models.SET_NULL, help_text=rec_help(d.MCODEPACKET, "medication_statement")) + extra_properties = JSONField(blank=True, null=True, + help_text=rec_help(d.MCODEPACKET, "extra_properties")) + created = models.DateTimeField(auto_now=True) + updated = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] From 2bc72cd046d6313e0727386d4f0c8a3a5fb9d016 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 1 Apr 2020 12:12:05 -0400 Subject: [PATCH 75/95] Update dependencies --- requirements.txt | 16 ++++++++-------- setup.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index fe8c2fab1..f5218d1d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,12 +3,12 @@ attrs==19.3.0 Babel==2.8.0 certifi==2019.11.28 chardet==3.0.4 -chord-lib==0.6.0 -codecov==2.0.16 +chord-lib==0.7.0 +codecov==2.0.22 colorama==0.4.3 coreapi==2.3.3 coreschema==0.0.4 -coverage==5.0.3 +coverage==5.0.4 Django==2.2.11 django-filter==2.2.0 django-nose==1.4.6 @@ -16,11 +16,11 @@ django-rest-swagger==2.2.0 djangorestframework==3.10.3 djangorestframework-camel-case==1.1.2 docutils==0.16 -elasticsearch==7.1.0 +elasticsearch==7.6.0 fhirclient==3.2.0 idna==2.9 imagesize==1.2.0 -importlib-metadata==1.5.0 +importlib-metadata==1.6.0 isodate==0.6.0 itypes==1.1.0 Jinja2==2.11.1 @@ -34,10 +34,10 @@ packaging==20.3 psycopg2-binary==2.8.4 Pygments==2.6.1 pyparsing==2.4.6 -pyrsistent==0.15.7 +pyrsistent==0.16.0 python-dateutil==2.8.1 pytz==2019.3 -PyYAML==5.3 +PyYAML==5.3.1 rdflib==4.2.2 rdflib-jsonld==0.4.0 redis==3.4.1 @@ -58,6 +58,6 @@ sqlparse==0.3.1 strict-rfc3339==0.7 uritemplate==3.0.1 urllib3==1.25.8 -Werkzeug==1.0.0 +Werkzeug==1.0.1 wincertstore==0.2 zipp==3.1.0 diff --git a/setup.py b/setup.py index 8298e672b..a99f8de15 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ python_requires=">=3.6", install_requires=[ - "chord_lib[django]==0.6.0", + "chord_lib[django]==0.7.0", "Django>=2.2,<3.0", "django-filter>=2.2,<3.0", "django-nose>=1.4,<2.0", From 2f45b4a3315c571e18bd51d92fffe6f248c6c155 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Wed, 1 Apr 2020 14:52:17 -0400 Subject: [PATCH 76/95] change validators names to snake_case new migrations for experiments because id in ontology_class_schema was changed during the merge --- .../migrations/0004_auto_20200401_1445.py | 25 ++++++++++++ chord_metadata_service/experiments/models.py | 9 ++--- .../experiments/serializers.py | 3 -- chord_metadata_service/mcode/models.py | 40 +++++++++---------- chord_metadata_service/restapi/validators.py | 8 ++-- 5 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 chord_metadata_service/experiments/migrations/0004_auto_20200401_1445.py diff --git a/chord_metadata_service/experiments/migrations/0004_auto_20200401_1445.py b/chord_metadata_service/experiments/migrations/0004_auto_20200401_1445.py new file mode 100644 index 000000000..2acccced4 --- /dev/null +++ b/chord_metadata_service/experiments/migrations/0004_auto_20200401_1445.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.10 on 2020-04-01 18:45 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0003_auto_20200331_1841'), + ] + + operations = [ + migrations.AlterField( + model_name='experiment', + name='experiment_ontology', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: OBI) links to experiment ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), + ), + migrations.AlterField( + model_name='experiment', + name='molecule_ontology', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='(Ontology: SO) links to molecule ontology information.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'ONTOLOGY_CLASS_LIST', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Ontology class list', 'items': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'title': 'Ontology class list', 'type': 'array'})]), + ), + ] diff --git a/chord_metadata_service/experiments/models.py b/chord_metadata_service/experiments/models.py index 13a2f78e8..8e0a0bc22 100644 --- a/chord_metadata_service/experiments/models.py +++ b/chord_metadata_service/experiments/models.py @@ -4,8 +4,7 @@ from django.contrib.postgres.fields import JSONField, ArrayField from chord_metadata_service.restapi.models import IndexableMixin from chord_metadata_service.restapi.description_utils import rec_help -from chord_metadata_service.restapi.validators import ontologyListValidator, keyValueValidator -from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT +from chord_metadata_service.restapi.validators import ontology_list_validator, key_value_validator from chord_metadata_service.patients.models import Individual from chord_metadata_service.phenopackets.models import Biosample import chord_metadata_service.experiments.descriptions as d @@ -42,12 +41,12 @@ class Experiment(models.Model, IndexableMixin): reference_registry_id = CharField(max_length=30, blank=True, null=True, help_text=rec_help(d.EXPERIMENT, 'reference_registry_id')) qc_flags = ArrayField(CharField(max_length=100, help_text=rec_help(d.EXPERIMENT, 'qc_flags')), null=True, blank=True, default=list) experiment_type = CharField(max_length=30, help_text=rec_help(d.EXPERIMENT, 'experiment_type')) - experiment_ontology = JSONField(blank=True, null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) - molecule_ontology = JSONField(blank=True, null=True, validators=[ontologyListValidator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) + experiment_ontology = JSONField(blank=True, null=True, validators=[ontology_list_validator], help_text=rec_help(d.EXPERIMENT, 'experiment_ontology')) + molecule_ontology = JSONField(blank=True, null=True, validators=[ontology_list_validator], help_text=rec_help(d.EXPERIMENT, 'molecule_ontology')) molecule = CharField(choices=MOLECULE, max_length=20, blank=True, null=True, help_text=rec_help(d.EXPERIMENT, 'molecule')) library_strategy = CharField(choices=LIBRARY_STRATEGY, max_length=25, help_text=rec_help(d.EXPERIMENT, 'library_strategy')) - other_fields = JSONField(blank=True, null=True, validators=[keyValueValidator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) + other_fields = JSONField(blank=True, null=True, validators=[key_value_validator], help_text=rec_help(d.EXPERIMENT, 'other_fields')) biosample = models.ForeignKey(Biosample, on_delete=models.SET_NULL, blank=True, null=True, help_text=rec_help(d.EXPERIMENT, 'biosample')) individual = models.ForeignKey(Individual, on_delete=models.SET_NULL, blank=True, null=True, help_text=rec_help(d.EXPERIMENT, 'individual')) diff --git a/chord_metadata_service/experiments/serializers.py b/chord_metadata_service/experiments/serializers.py index 3a652e307..22a0d5014 100644 --- a/chord_metadata_service/experiments/serializers.py +++ b/chord_metadata_service/experiments/serializers.py @@ -1,6 +1,3 @@ -from rest_framework import serializers -from chord_metadata_service.restapi.schemas import ONTOLOGY_CLASS, AGE_OR_AGE_RANGE -from chord_metadata_service.restapi.validators import JsonSchemaValidator, ontologyListValidator, keyValueValidator from chord_metadata_service.restapi.serializers import GenericSerializer from .models import Experiment diff --git a/chord_metadata_service/mcode/models.py b/chord_metadata_service/mcode/models.py index cf061444c..a38e5d1ab 100644 --- a/chord_metadata_service/mcode/models.py +++ b/chord_metadata_service/mcode/models.py @@ -7,8 +7,8 @@ from chord_metadata_service.restapi.description_utils import rec_help import chord_metadata_service.mcode.descriptions as d from chord_metadata_service.restapi.validators import ( - ontologyValidator, quantity_validator, tumor_marker_test_validator, - complex_ontology_validator, time_or_period_validator, ontologyListValidator + ontology_validator, quantity_validator, tumor_marker_test_validator, + complex_ontology_validator, time_or_period_validator, ontology_list_validator ) @@ -23,16 +23,16 @@ class GeneticVariantTested(models.Model, IndexableMixin): # make writable if it doesn't exist gene_studied = models.ForeignKey(Gene, blank=True, null=True, on_delete=models.SET_NULL, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "gene_studied")) - method = JSONField(blank=True, null=True, validators=[ontologyValidator], + method = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "method")) - variant_tested_identifier = JSONField(blank=True, null=True, validators=[ontologyValidator], + variant_tested_identifier = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_identifier")) variant_tested_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_hgvs_name")) variant_tested_description = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "variant_tested_description")) - data_value = JSONField(blank=True, null=True, validators=[ontologyValidator], + data_value = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.GENETIC_VARIANT_TESTED, "data_value")) extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_TESTED, "extra_properties")) @@ -60,16 +60,16 @@ class GeneticVariantFound(models.Model, IndexableMixin): # TODO Discuss: Connection to Gene from Phenopackets id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "id")) - method = JSONField(blank=True, null=True, validators=[ontologyValidator], + method = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "method")) - variant_found_identifier = JSONField(blank=True, null=True, validators=[ontologyValidator], + variant_found_identifier = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_identifier")) variant_found_hgvs_name = ArrayField(models.CharField(max_length=200), blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_hgvs_name")) variant_found_description = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "variant_found_description")) # loinc value set https://loinc.org/48002-0/ - genomic_source_class = JSONField(blank=True, null=True, validators=[ontologyValidator], + genomic_source_class = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.GENETIC_VARIANT_FOUND, "genomic_source_class")) extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.GENETIC_VARIANT_FOUND, "extra_properties")) @@ -94,10 +94,10 @@ class GenomicsReport(models.Model, IndexableMixin): """ id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.GENOMICS_REPORT, "id")) - test_name = JSONField(validators=[ontologyValidator], help_text=rec_help(d.GENOMICS_REPORT, "test_name")) + test_name = JSONField(validators=[ontology_validator], help_text=rec_help(d.GENOMICS_REPORT, "test_name")) performing_organization_name = models.CharField(max_length=200, blank=True, help_text=rec_help(d.GENOMICS_REPORT, "performing_organization_name")) - specimen_type = JSONField(blank=True, null=True, validators=[ontologyValidator], + specimen_type = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.GENOMICS_REPORT, "specimen_type")) genetic_variant_tested = models.ManyToManyField(GeneticVariantTested, blank=True, help_text=rec_help(d.GENOMICS_REPORT, "genetic_variant_tested")) @@ -172,15 +172,15 @@ class CancerCondition(models.Model, IndexableMixin): condition_type = models.CharField(choices=CANCER_CONDITION_TYPE, max_length=200, help_text=rec_help(d.CANCER_CONDITION, "condition_type")) # TODO add body_location_code validator array of json - body_location_code = JSONField(null=True, validators=[ontologyListValidator], + body_location_code = JSONField(null=True, validators=[ontology_list_validator], help_text=rec_help(d.CANCER_CONDITION, 'body_location_code')) - clinical_status = JSONField(blank=True, null=True, validators=[ontologyValidator], + clinical_status = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.CANCER_CONDITION, "clinical_status")) - condition_code = JSONField(validators=[ontologyValidator], + condition_code = JSONField(validators=[ontology_validator], help_text=rec_help(d.CANCER_CONDITION, "condition_code")) date_of_diagnosis = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "date_of_diagnosis")) - histology_morphology_behavior = JSONField(blank=True, null=True, validators=[ontologyValidator], + histology_morphology_behavior = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.CANCER_CONDITION, "histology_morphology_behavior")) extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.CANCER_CONDITION, "extra_properties")) @@ -243,12 +243,12 @@ class CancerRelatedProcedure(models.Model, IndexableMixin): id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "id")) procedure_type = models.CharField(choices=PROCEDURE_TYPES, max_length=200, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "procedure_type")) - code = JSONField(validators=[ontologyValidator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "code")) + code = JSONField(validators=[ontology_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "code")) occurence_time_or_period = JSONField(validators=[time_or_period_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "occurence_time_or_period")) - target_body_site = JSONField(null=True, validators=[ontologyListValidator], + target_body_site = JSONField(null=True, validators=[ontology_list_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, 'target_body_site')) - treatment_intent = JSONField(blank=True, null=True, validators=[ontologyValidator], + treatment_intent = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "treatment_intent")) extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.CANCER_RELATED_PROCEDURE, "extra_properties")) @@ -270,11 +270,11 @@ class MedicationStatement(models.Model, IndexableMixin): id = models.CharField(primary_key=True, max_length=200, help_text=rec_help(d.MEDICATION_STATEMENT, "id")) # list http://hl7.org/fhir/us/core/STU3.1/ValueSet-us-core-medication-codes.html - medication_code = JSONField(validators=[ontologyValidator], + medication_code = JSONField(validators=[ontology_validator], help_text=rec_help(d.MEDICATION_STATEMENT, "medication_code")) - termination_reason = JSONField(null=True, validators=[ontologyListValidator], + termination_reason = JSONField(null=True, validators=[ontology_list_validator], help_text=rec_help(d.MEDICATION_STATEMENT, 'termination_reason')) - treatment_intent = JSONField(blank=True, null=True, validators=[ontologyValidator], + treatment_intent = JSONField(blank=True, null=True, validators=[ontology_validator], help_text=rec_help(d.MEDICATION_STATEMENT, "treatment_intent")) start_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "start_date")) end_date = models.DateTimeField(blank=True, null=True, help_text=rec_help(d.MEDICATION_STATEMENT, "end_date")) diff --git a/chord_metadata_service/restapi/validators.py b/chord_metadata_service/restapi/validators.py index b24b293d5..0c5106bf2 100644 --- a/chord_metadata_service/restapi/validators.py +++ b/chord_metadata_service/restapi/validators.py @@ -30,10 +30,10 @@ def deconstruct(self): ) -ontologyValidator = JsonSchemaValidator(ONTOLOGY_CLASS) -ontologyListValidator = JsonSchemaValidator(ONTOLOGY_CLASS_LIST) -keyValueValidator = JsonSchemaValidator(KEY_VALUE_OBJECT) -ageOrAgeRangeValidator = JsonSchemaValidator(AGE_OR_AGE_RANGE) +ontology_validator = JsonSchemaValidator(ONTOLOGY_CLASS) +ontology_list_validator = JsonSchemaValidator(ONTOLOGY_CLASS_LIST) +key_value_validator = JsonSchemaValidator(KEY_VALUE_OBJECT) +age_or_age_range_validator = JsonSchemaValidator(AGE_OR_AGE_RANGE) quantity_validator = JsonSchemaValidator(schema=QUANTITY, format_checker=['uri']) tumor_marker_test_validator = JsonSchemaValidator(schema=TUMOR_MARKER_TEST) complex_ontology_validator = JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri']) From 7505e8b9773c117ece49524e0b41b96f2fad0dcc Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Wed, 1 Apr 2020 15:06:10 -0400 Subject: [PATCH 77/95] move validation to models in patients --- .../migrations/0006_auto_20200401_1504.py | 40 +++++++++++++++++++ chord_metadata_service/patients/models.py | 23 +++++++---- .../patients/serializers.py | 31 -------------- chord_metadata_service/restapi/validators.py | 3 +- 4 files changed, 57 insertions(+), 40 deletions(-) create mode 100644 chord_metadata_service/patients/migrations/0006_auto_20200401_1504.py diff --git a/chord_metadata_service/patients/migrations/0006_auto_20200401_1504.py b/chord_metadata_service/patients/migrations/0006_auto_20200401_1504.py new file mode 100644 index 000000000..b45c7ab94 --- /dev/null +++ b/chord_metadata_service/patients/migrations/0006_auto_20200401_1504.py @@ -0,0 +1,40 @@ +# Generated by Django 2.2.10 on 2020-04-01 19:04 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('patients', '0005_auto_20200311_1610'), + ] + + operations = [ + migrations.AlterField( + model_name='individual', + name='age', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The age or age range of the individual.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:age_or_age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age object describing the age of the individual at the time of collection of biospecimens or phenotypic observations.', 'properties': {'age': {'anyOf': [{'description': 'An ISO8601 string represent age.', 'type': 'string'}, {'$id': 'chord_metadata_service:age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age range of a subject.', 'properties': {'end': {'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'type': 'object'}, 'start': {'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'type': 'object'}}, 'required': ['start', 'end'], 'title': 'Age schema', 'type': 'object'}]}}, 'title': 'Age schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='individual', + name='comorbid_condition', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='One or more conditions that occur with primary condition.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:comorbid_condition_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Comorbid condition schema.', 'properties': {'clinical_status': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}, 'code': {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}}, 'required': [], 'title': 'Comorbid Condition schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='individual', + name='ecog_performance_status', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Value representing the Eastern Cooperative Oncology Group performance status.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='individual', + name='karnofsky', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Value representing the Karnofsky Performance status.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='individual', + name='taxonomy', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Ontology resource representing the species (e.g., NCBITaxon:9615).', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + ] diff --git a/chord_metadata_service/patients/models.py b/chord_metadata_service/patients/models.py index 5230874b9..a2fcb4b5e 100644 --- a/chord_metadata_service/patients/models.py +++ b/chord_metadata_service/patients/models.py @@ -1,6 +1,9 @@ from django.db import models from django.contrib.postgres.fields import JSONField, ArrayField from chord_metadata_service.restapi.models import IndexableMixin +from chord_metadata_service.restapi.validators import ( + ontology_validator, age_or_age_range_validator, comorbid_condition_validator +) class Individual(models.Model, IndexableMixin): @@ -33,24 +36,28 @@ class Individual(models.Model, IndexableMixin): help_text='A list of alternative identifiers for the individual.') date_of_birth = models.DateField(null=True, blank=True, help_text='A timestamp either exact or imprecise.') # An ISO8601 string represent age - age = JSONField(blank=True, null=True, help_text='The age or age range of the individual.') + age = JSONField(blank=True, null=True, validators=[age_or_age_range_validator], + help_text='The age or age range of the individual.') sex = models.CharField(choices=SEX, max_length=200, blank=True, null=True, help_text='Observed apparent sex of the individual.') karyotypic_sex = models.CharField(choices=KARYOTYPIC_SEX, max_length=200, default='UNKNOWN_KARYOTYPE', help_text='The karyotypic sex of the individual.') - taxonomy = JSONField(blank=True, null=True, + taxonomy = JSONField(blank=True, null=True, validators=[ontology_validator], help_text='Ontology resource representing the species (e.g., NCBITaxon:9615).') # FHIR specific active = models.BooleanField(default=False, help_text='Whether this patient\'s record is in active use.') deceased = models.BooleanField(default=False, help_text='Indicates if the individual is deceased or not.') # mCode specific # this field should be complex Ontology - clinical status and code - two Codeable concept - single, cl status has enum list of values - comorbid_condition = JSONField(blank=True, null=True, help_text='One or more conditions that occur with primary' - ' condition.') - #TODO decide use ONTOLOGY_CLASS vs. CODEABLE_CONCEPT - ecog_performance_status = JSONField(blank=True, null=True, help_text='Value representing the Eastern Cooperative ' - 'Oncology Group performance status.') - karnofsky = JSONField(blank=True, null=True, help_text='Value representing the Karnofsky Performance status.') + # TODO add these fields to FHIR converter ? + comorbid_condition = JSONField(blank=True, null=True, validators=[comorbid_condition_validator], + help_text='One or more conditions that occur with primary condition.') + #TODO decide use ONTOLOGY_CLASS vs. CODEABLE_CONCEPT - currently Ontology class + ecog_performance_status = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text='Value representing the Eastern Cooperative ' + 'Oncology Group performance status.') + karnofsky = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text='Value representing the Karnofsky Performance status.') race = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s race.') ethnicity = models.CharField(max_length=200, blank=True, help_text='A code for the person\'s ethnicity.') # extra diff --git a/chord_metadata_service/patients/serializers.py b/chord_metadata_service/patients/serializers.py index 9e7b26725..2505d1e22 100644 --- a/chord_metadata_service/patients/serializers.py +++ b/chord_metadata_service/patients/serializers.py @@ -1,44 +1,13 @@ -from rest_framework import serializers from chord_metadata_service.phenopackets.serializers import ( BiosampleSerializer, SimplePhenopacketSerializer ) -from chord_metadata_service.restapi.schemas import ( - ONTOLOGY_CLASS, AGE_OR_AGE_RANGE, COMORBID_CONDITION, CODEABLE_CONCEPT -) -from chord_metadata_service.restapi.validators import JsonSchemaValidator from chord_metadata_service.restapi.serializers import GenericSerializer from chord_metadata_service.restapi.fhir_utils import fhir_patient from .models import Individual class IndividualSerializer(GenericSerializer): - taxonomy = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, - required=False - ) - age = serializers.JSONField( - validators=[JsonSchemaValidator(schema=AGE_OR_AGE_RANGE)], - allow_null=True, - required=False - ) - #TODO add these fields to FHIR converter ? - comorbid_condition = serializers.JSONField( - validators=[JsonSchemaValidator(schema=COMORBID_CONDITION)], - allow_null=True, - required=False - ) - ecog_performance_status = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, - required=False - ) - karnofsky = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, - required=False - ) biosamples = BiosampleSerializer( read_only=True, many=True, exclude_when_nested=['individual']) diff --git a/chord_metadata_service/restapi/validators.py b/chord_metadata_service/restapi/validators.py index 0c5106bf2..78e7478b1 100644 --- a/chord_metadata_service/restapi/validators.py +++ b/chord_metadata_service/restapi/validators.py @@ -2,7 +2,7 @@ from jsonschema import Draft7Validator, FormatChecker from chord_metadata_service.restapi.schemas import ( ONTOLOGY_CLASS, QUANTITY, COMPLEX_ONTOLOGY, TIME_OR_PERIOD, TUMOR_MARKER_TEST, - ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT, AGE_OR_AGE_RANGE + ONTOLOGY_CLASS_LIST, KEY_VALUE_OBJECT, AGE_OR_AGE_RANGE, COMORBID_CONDITION ) @@ -38,3 +38,4 @@ def deconstruct(self): tumor_marker_test_validator = JsonSchemaValidator(schema=TUMOR_MARKER_TEST) complex_ontology_validator = JsonSchemaValidator(schema=COMPLEX_ONTOLOGY, format_checker=['uri']) time_or_period_validator = JsonSchemaValidator(schema=TIME_OR_PERIOD, format_checker=['date-time']) +comorbid_condition_validator = JsonSchemaValidator(COMORBID_CONDITION) \ No newline at end of file From d36b70acc47dabc4fa753f151f89530fbac17018 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 1 Apr 2020 15:44:58 -0400 Subject: [PATCH 78/95] Test travis on python 3.6 and 3.8 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0c27e4f49..5df749e32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ dist: bionic language: python python: - - "3.7" + - "3.6" + - "3.8" addons: postgresql: "11" apt: From d2847f710743afe8816b8b3881512063b5b62911 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 1 Apr 2020 15:45:46 -0400 Subject: [PATCH 79/95] Code styling tweaks --- chord_metadata_service/chord/views_search.py | 52 ++++++++++---------- chord_metadata_service/patients/models.py | 5 +- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/chord_metadata_service/chord/views_search.py b/chord_metadata_service/chord/views_search.py index 515d8f141..0e6cfcda8 100644 --- a/chord_metadata_service/chord/views_search.py +++ b/chord_metadata_service/chord/views_search.py @@ -271,42 +271,40 @@ def fhir_search(request, internal_data=False): res = es.search(index=settings.FHIR_INDEX_NAME, body=query) - subject_ids = [hit['_id'].split('|')[1] for hit in res['hits']['hits'] if hit['_source']['resourceType'] == 'Patient'] - htsfile_ids = [hit['_id'].split('|')[1] for hit in res['hits']['hits'] if hit['_source']['resourceType'] == 'DocumentReference'] - disease_ids = [hit['_id'].split('|')[1] for hit in res['hits']['hits'] if hit['_source']['resourceType'] == 'Condition'] - biosample_ids = [hit['_id'].split('|')[1] for hit in res['hits']['hits'] if hit['_source']['resourceType'] == 'Specimen'] - phenotypicfeature_ids = [hit['_id'].split('|')[1] for hit in res['hits']['hits'] if hit['_source']['resourceType'] == 'Observation'] - phenopacket_ids = [hit['_id'].split('|')[1] for hit in res['hits']['hits'] if hit['_source']['resourceType'] == 'Composition'] - - if (not subject_ids and not htsfile_ids and not disease_ids - and not biosample_ids and not phenotypicfeature_ids and not phenopacket_ids): + def hits_for(resource_type: str): + return frozenset(hit["_id"].split("|")[1] for hit in res["hits"]["hits"] + if hit['_source']['resourceType'] == resource_type) + + subject_ids = hits_for('Patient') + htsfile_ids = hits_for('DocumentReference') + disease_ids = hits_for('Condition') + biosample_ids = hits_for('Specimen') + phenotypicfeature_ids = hits_for('Observation') + phenopacket_ids = hits_for('Composition') + + if all((not subject_ids, not htsfile_ids, not disease_ids, not biosample_ids, not phenotypicfeature_ids, + not phenopacket_ids)): return Response(build_search_response([], start)) - else: - phenopackets = phenopacket_filter_results( - subject_ids, - htsfile_ids, - disease_ids, - biosample_ids, - phenotypicfeature_ids, - phenopacket_ids - ) + + phenopackets = phenopacket_filter_results( + subject_ids, + htsfile_ids, + disease_ids, + biosample_ids, + phenotypicfeature_ids, + phenopacket_ids + ) if not internal_data: - datasets = Dataset.objects.filter( - identifier__in = [ - p.dataset_id for p in phenopackets - ] - ) # TODO: Maybe can avoid hitting DB here + # TODO: Maybe can avoid hitting DB here + datasets = Dataset.objects.filter(identifier__in=frozenset(p.dataset_id for p in phenopackets)) return Response(build_search_response([{"id": d.identifier, "data_type": PHENOPACKET_DATA_TYPE_ID} for d in datasets], start)) return Response(build_search_response({ dataset_id: { "data_type": PHENOPACKET_DATA_TYPE_ID, "matches": list(PhenopacketSerializer(p).data for p in dataset_phenopackets) - } for dataset_id, dataset_phenopackets in itertools.groupby( - phenopackets, - key=lambda p: str(p.dataset_id) - ) + } for dataset_id, dataset_phenopackets in itertools.groupby(phenopackets, key=lambda p: str(p.dataset_id)) }, start)) diff --git a/chord_metadata_service/patients/models.py b/chord_metadata_service/patients/models.py index 5230874b9..2d22ede1b 100644 --- a/chord_metadata_service/patients/models.py +++ b/chord_metadata_service/patients/models.py @@ -44,10 +44,11 @@ class Individual(models.Model, IndexableMixin): active = models.BooleanField(default=False, help_text='Whether this patient\'s record is in active use.') deceased = models.BooleanField(default=False, help_text='Indicates if the individual is deceased or not.') # mCode specific - # this field should be complex Ontology - clinical status and code - two Codeable concept - single, cl status has enum list of values + # this field should be complex Ontology - clinical status and code - two Codeable concept - single, cl status has + # enum list of values comorbid_condition = JSONField(blank=True, null=True, help_text='One or more conditions that occur with primary' ' condition.') - #TODO decide use ONTOLOGY_CLASS vs. CODEABLE_CONCEPT + # TODO decide use ONTOLOGY_CLASS vs. CODEABLE_CONCEPT ecog_performance_status = JSONField(blank=True, null=True, help_text='Value representing the Eastern Cooperative ' 'Oncology Group performance status.') karnofsky = JSONField(blank=True, null=True, help_text='Value representing the Karnofsky Performance status.') From bba2d68802645bbaf6d47c4c95792b70d5419e66 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 1 Apr 2020 15:46:00 -0400 Subject: [PATCH 80/95] Remove unneeded workflow_metadata from single_req example --- examples/single_req.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/examples/single_req.json b/examples/single_req.json index 2d4bae995..0d35b6eec 100644 --- a/examples/single_req.json +++ b/examples/single_req.json @@ -1,24 +1,6 @@ { "table_id": "3df5e8b0-3949-4d3c-a37f-1c6a81940d50", "workflow_id": "phenopackets_json", - "workflow_metadata": { - "inputs": [ - { - "id": "json_document", - "type": "file", - "extensions": [ - ".json" - ] - } - ], - "outputs": [ - { - "id": "json_document", - "type": "file", - "value": "{json_document}" - } - ] - }, "workflow_params": { "phenopackets_json.json_document": "/home/dlougheed/git/chord_metadata_service/examples/single.json" }, From 9c97f8196de9b1486bcaba7e57b7b56d5ebaf209 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 1 Apr 2020 15:51:58 -0400 Subject: [PATCH 81/95] Add example dataset JSON --- examples/dataset.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 examples/dataset.json diff --git a/examples/dataset.json b/examples/dataset.json new file mode 100644 index 000000000..043eab4fa --- /dev/null +++ b/examples/dataset.json @@ -0,0 +1,17 @@ +{ + "title": "Test Dataset", + "description": "This is a test dataset", + "project": "uuid-for-project-record", + "data_use": { + "consent_code": { + "primary_category": {"code": "GRU"}, + "secondary_categories": [ + {"code": "GSO"} + ] + }, + "data_use_requirements": [ + {"code": "COL"}, + {"code": "PUB"} + ] + } +} From fd4213c9a3afc7b2eb1c02bbd71c7cc807195c45 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 1 Apr 2020 15:59:30 -0400 Subject: [PATCH 82/95] Make summary code more efficient; add disease counter --- chord_metadata_service/chord/views_search.py | 48 +++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/chord_metadata_service/chord/views_search.py b/chord_metadata_service/chord/views_search.py index 0e6cfcda8..87a2cf3eb 100644 --- a/chord_metadata_service/chord/views_search.py +++ b/chord_metadata_service/chord/views_search.py @@ -14,7 +14,7 @@ from chord_metadata_service.metadata.settings import DEBUG from chord_metadata_service.patients.models import Individual from chord_metadata_service.phenopackets.api_views import PHENOPACKET_PREFETCH -from chord_metadata_service.phenopackets.models import Phenopacket, Biosample +from chord_metadata_service.phenopackets.models import Phenopacket from chord_metadata_service.phenopackets.schemas import PHENOPACKET_SCHEMA from chord_metadata_service.phenopackets.serializers import PhenopacketSerializer from chord_metadata_service.metadata.elastic import es @@ -103,24 +103,40 @@ def chord_table_summary(_request, table_id): table = Dataset.objects.get(identifier=table_id) phenopackets = Phenopacket.objects.filter(dataset=table) - biosamples_set = frozenset( - p["biosamples__id"] for p in phenopackets.prefetch_related("biosamples").values("biosamples__id")) + diseases_counter = Counter() + biosamples_set = set() + individuals_set = set() - biosamples_cs = Counter(b.is_control_sample for b in Biosample.objects.filter(id__in=biosamples_set)) + biosamples_cs = Counter() + biosamples_taxonomy = Counter() - biosamples_taxonomy = Counter(b.taxonomy["id"] for b in Biosample.objects.filter(id__in=biosamples_set) - if b.taxonomy is not None) + individuals_sex = Counter() + individuals_k_sex = Counter() + individuals_taxonomy = Counter() - individuals_set = frozenset({ - *(p["subject"] for p in phenopackets.values("subject")), - *(p["biosamples__individual_id"] - for p in phenopackets.prefetch_related("biosamples").values("biosamples__individual_id")), - }) + def count_individual(ind): + individuals_set.add(ind.id) + individuals_sex.update((ind.sex,)) + individuals_k_sex.update((ind.karyotypic_sex,)) + if ind.taxonomy is not None: + individuals_taxonomy.update((ind.taxonomy["id"],)) + + for p in phenopackets.prefetch_related("biosamples"): + for b in p.biosamples.all(): + biosamples_set.add(b.id) + biosamples_cs.update((b.is_control_sample,)) + + if b.taxonomy is not None: + biosamples_taxonomy.update((b.taxonomy["id"],)) + + if b.individual is not None: + count_individual(b.individual) + + for d in p.diseases.all(): + diseases_counter.update((d.term["id"],)) - individuals_sex = Counter(i.sex for i in Individual.objects.filter(id__in=individuals_set)) - individuals_k_sex = Counter(i.karyotypic_sex for i in Individual.objects.filter(id__in=individuals_set)) - individuals_taxonomy = Counter(i.taxonomy["id"] for i in Individual.objects.filter(id__in=individuals_set) - if i.taxonomy is not None) + # Currently, phenopacket subject is required so we can assume it's not None + count_individual(p.subject) return Response({ "count": phenopackets.count(), @@ -130,11 +146,11 @@ def chord_table_summary(_request, table_id): "is_control_sample": dict(biosamples_cs), "taxonomy": dict(biosamples_taxonomy), }, + "diseases": dict(diseases_counter), "individuals": { "count": len(individuals_set), "sex": {k: individuals_sex[k] for k in (s[0] for s in Individual.SEX)}, "karyotypic_sex": {k: individuals_k_sex[k] for k in (s[0] for s in Individual.KARYOTYPIC_SEX)}, - "diseases": {}, "taxonomy": dict(individuals_taxonomy), # TODO: age histogram }, From 38782785da28bd5ca6c521710147d3e8ca4b9f80 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 1 Apr 2020 16:22:33 -0400 Subject: [PATCH 83/95] Fix a small code style issue --- chord_metadata_service/chord/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chord_metadata_service/chord/models.py b/chord_metadata_service/chord/models.py index c7e7e64a7..59110e052 100644 --- a/chord_metadata_service/chord/models.py +++ b/chord_metadata_service/chord/models.py @@ -122,7 +122,7 @@ def n_of_tables(self): keywords = ArrayField(JSONField(null=True, blank=True), blank=True, null=True, help_text="Tags associated with the dataset, which will help in its discovery.") version = models.CharField(max_length=200, blank=True, default=version_default, - help_text="A release point for the dataset when applicable.") + help_text="A release point for the dataset when applicable.") extra_properties = JSONField(blank=True, null=True, help_text="Extra properties that do not fit in the previous specified attributes.") From 521903a0040ad2a94a4993ad7e4649082716a491 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Wed, 1 Apr 2020 17:05:34 -0400 Subject: [PATCH 84/95] Add phenotypic feature counts to table summary --- chord_metadata_service/chord/views_search.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/chord_metadata_service/chord/views_search.py b/chord_metadata_service/chord/views_search.py index 87a2cf3eb..506a10978 100644 --- a/chord_metadata_service/chord/views_search.py +++ b/chord_metadata_service/chord/views_search.py @@ -104,6 +104,8 @@ def chord_table_summary(_request, table_id): phenopackets = Phenopacket.objects.filter(dataset=table) diseases_counter = Counter() + phenotypic_features_counter = Counter() + biosamples_set = set() individuals_set = set() @@ -132,9 +134,15 @@ def count_individual(ind): if b.individual is not None: count_individual(b.individual) + for pf in b.phenotypic_features.all(): + phenotypic_features_counter.update((pf.pftype["id"],)) + for d in p.diseases.all(): diseases_counter.update((d.term["id"],)) + for pf in p.phenotypic_features.all(): + phenotypic_features_counter.update((pf.pftype["id"],)) + # Currently, phenopacket subject is required so we can assume it's not None count_individual(p.subject) @@ -154,6 +162,7 @@ def count_individual(ind): "taxonomy": dict(individuals_taxonomy), # TODO: age histogram }, + "phenotypic_features": dict(phenotypic_features_counter), } }) From 35be5ef53245704a6cf629755e134411460541c8 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Fri, 24 Apr 2020 16:33:35 -0400 Subject: [PATCH 85/95] move validation to models --- chord_metadata_service/phenopackets/models.py | 75 +++++--- .../phenopackets/serializers.py | 167 ++++++++---------- chord_metadata_service/restapi/tests/utils.py | 1 + 3 files changed, 124 insertions(+), 119 deletions(-) diff --git a/chord_metadata_service/phenopackets/models.py b/chord_metadata_service/phenopackets/models.py index 0eca266a6..3e0f2b25f 100644 --- a/chord_metadata_service/phenopackets/models.py +++ b/chord_metadata_service/phenopackets/models.py @@ -5,7 +5,16 @@ from chord_metadata_service.patients.models import Individual from chord_metadata_service.restapi.description_utils import rec_help from chord_metadata_service.restapi.models import IndexableMixin +from chord_metadata_service.restapi.validators import JsonSchemaValidator +from chord_metadata_service.restapi.schemas import ( + UPDATE_SCHEMA, EXTERNAL_REFERENCE, EVIDENCE, ALLELE_SCHEMA, DISEASE_ONSET +) import chord_metadata_service.phenopackets.descriptions as d +from chord_metadata_service.restapi.validators import ( + ontology_validator, quantity_validator, tumor_marker_test_validator, + complex_ontology_validator, time_or_period_validator, ontology_list_validator, + age_or_age_range_validator +) ############################################################# @@ -51,13 +60,14 @@ class MetaData(models.Model): submitted_by = models.CharField(max_length=200, blank=True, help_text=rec_help(d.META_DATA, "submitted_by")) resources = models.ManyToManyField(Resource, help_text=rec_help(d.META_DATA, "resources")) updates = ArrayField( - JSONField(null=True, blank=True), blank=True, null=True, - help_text=rec_help(d.META_DATA, "updates")) + JSONField(null=True, blank=True, + validators=[JsonSchemaValidator(schema=UPDATE_SCHEMA, format_checker=['date-time'])]), + blank=True, null=True, help_text=rec_help(d.META_DATA, "updates")) phenopacket_schema_version = models.CharField(max_length=200, blank=True, help_text='Schema version of the current phenopacket.') external_references = ArrayField( - JSONField(null=True, blank=True), blank=True, null=True, - help_text=rec_help(d.META_DATA, "external_references")) + JSONField(null=True, blank=True, validators=[JsonSchemaValidator(EXTERNAL_REFERENCE)]), + blank=True, null=True, help_text=rec_help(d.META_DATA, "external_references")) extra_properties = JSONField( blank=True, null=True, help_text=rec_help(d.META_DATA, "extra_properties")) updated = models.DateTimeField(auto_now_add=True) @@ -85,17 +95,21 @@ class PhenotypicFeature(models.Model, IndexableMixin): description = models.CharField( max_length=200, blank=True, help_text=rec_help(d.PHENOTYPIC_FEATURE, "description")) - pftype = JSONField(verbose_name='type', help_text=rec_help(d.PHENOTYPIC_FEATURE, "type")) + pftype = JSONField(verbose_name='type', validators=[ontology_validator], + help_text=rec_help(d.PHENOTYPIC_FEATURE, "type")) negated = models.BooleanField(default=False, help_text=rec_help(d.PHENOTYPIC_FEATURE, "negated")) - severity = JSONField(blank=True, null=True, help_text=rec_help(d.PHENOTYPIC_FEATURE, "severity")) + severity = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text=rec_help(d.PHENOTYPIC_FEATURE, "severity")) modifier = ArrayField( - JSONField(null=True, blank=True), blank=True, null=True, + JSONField(null=True, blank=True, validators=[ontology_validator]), blank=True, null=True, help_text=rec_help(d.PHENOTYPIC_FEATURE, "modifier")) - onset = JSONField(blank=True, null=True, help_text=rec_help(d.PHENOTYPIC_FEATURE, "onset")) + onset = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text=rec_help(d.PHENOTYPIC_FEATURE, "onset")) # evidence can stay here because evidence is given for an observation of PF # JSON schema to check evidence_code is present # FHIR: Condition.evidence - evidence = JSONField(blank=True, null=True, help_text=rec_help(d.PHENOTYPIC_FEATURE, "evidence")) + evidence = JSONField(blank=True, null=True, validators=[JsonSchemaValidator(schema=EVIDENCE)], + help_text=rec_help(d.PHENOTYPIC_FEATURE, "evidence")) biosample = models.ForeignKey( "Biosample", on_delete=models.SET_NULL, blank=True, null=True, related_name='phenotypic_features') phenopacket = models.ForeignKey( @@ -116,8 +130,9 @@ class Procedure(models.Model): FHIR: Procedure """ - code = JSONField(help_text=rec_help(d.PROCEDURE, "code")) - body_site = JSONField(blank=True, null=True, help_text=rec_help(d.PROCEDURE, "body_site")) + code = JSONField(validators=[ontology_validator], help_text=rec_help(d.PROCEDURE, "code")) + body_site = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text=rec_help(d.PROCEDURE, "body_site")) extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.PROCEDURE, "extra_properties")) created = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now_add=True) @@ -199,8 +214,10 @@ class Variant(models.Model): ('iscnAllele', 'iscnAllele') ) allele_type = models.CharField(max_length=200, choices=ALLELE, help_text="One of four allele types.") - allele = JSONField(help_text=rec_help(d.VARIANT, "allele")) - zygosity = JSONField(blank=True, null=True, help_text=rec_help(d.VARIANT, "zygosity")) + allele = JSONField(validators=[JsonSchemaValidator(schema=ALLELE_SCHEMA)], + help_text=rec_help(d.VARIANT, "allele")) + zygosity = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text=rec_help(d.VARIANT, "zygosity")) extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.VARIANT, "extra_properties")) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) @@ -217,7 +234,7 @@ class Disease(models.Model, IndexableMixin): FHIR: Condition """ - term = JSONField(help_text=rec_help(d.DISEASE, "term")) + term = JSONField(validators=[ontology_validator], help_text=rec_help(d.DISEASE, "term")) # "ageOfOnset": { # "age": "P38Y7M" # } @@ -226,9 +243,10 @@ class Disease(models.Model, IndexableMixin): # "id": "HP:0003581", # "label": "Adult onset" # } - onset = JSONField(blank=True, null=True, help_text=rec_help(d.DISEASE, "onset")) - disease_stage = ArrayField( - JSONField(null=True, blank=True), blank=True, null=True, help_text=rec_help(d.DISEASE, "disease_stage")) + onset = JSONField(blank=True, null=True, validators=[JsonSchemaValidator(schema=DISEASE_ONSET)], + help_text=rec_help(d.DISEASE, "onset")) + disease_stage = ArrayField(JSONField(null=True, blank=True, validators=[ontology_validator]), + blank=True, null=True, help_text=rec_help(d.DISEASE, "disease_stage")) tnm_finding = ArrayField( JSONField(null=True, blank=True), blank=True, null=True, help_text=rec_help(d.DISEASE, "tnm_finding")) extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.DISEASE, "extra_properties")) @@ -252,21 +270,24 @@ class Biosample(models.Model, IndexableMixin): Individual, on_delete=models.CASCADE, blank=True, null=True, related_name="biosamples", help_text=rec_help(d.BIOSAMPLE, "individual_id")) description = models.CharField(max_length=200, blank=True, help_text=rec_help(d.BIOSAMPLE, "description")) - sampled_tissue = JSONField(help_text=rec_help(d.BIOSAMPLE, "sampled_tissue")) + sampled_tissue = JSONField(validators=[ontology_validator], help_text=rec_help(d.BIOSAMPLE, "sampled_tissue")) # phenotypic_features = models.ManyToManyField(PhenotypicFeature, blank=True, # help_text='List of phenotypic abnormalities of the sample.') - taxonomy = JSONField(blank=True, null=True, help_text=rec_help(d.BIOSAMPLE, "taxonomy")) + taxonomy = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text=rec_help(d.BIOSAMPLE, "taxonomy")) # An ISO8601 string represent age - individual_age_at_collection = JSONField( - blank=True, null=True, help_text=rec_help("individual_age_at_collection")) + individual_age_at_collection = JSONField(blank=True, null=True, validators=[age_or_age_range_validator], + help_text=rec_help("individual_age_at_collection")) histological_diagnosis = JSONField( - blank=True, null=True, help_text=rec_help(d.BIOSAMPLE, "histological_diagnosis")) + blank=True, null=True, validators=[ontology_validator], + help_text=rec_help(d.BIOSAMPLE, "histological_diagnosis")) # TODO: Lists? - tumor_progression = JSONField(blank=True, null=True, help_text=rec_help(d.BIOSAMPLE, "tumor_progression")) - tumor_grade = JSONField(blank=True, null=True, help_text=rec_help(d.BIOSAMPLE, "tumor_grade")) - diagnostic_markers = ArrayField( - JSONField(null=True, blank=True), blank=True, null=True, - help_text=rec_help(d.BIOSAMPLE, "diagnostic_markers")) + tumor_progression = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text=rec_help(d.BIOSAMPLE, "tumor_progression")) + tumor_grade = JSONField(blank=True, null=True, validators=[ontology_validator], + help_text=rec_help(d.BIOSAMPLE, "tumor_grade")) + diagnostic_markers = ArrayField(JSONField(null=True, blank=True, validators=[ontology_validator]), + blank=True, null=True, help_text=rec_help(d.BIOSAMPLE, "diagnostic_markers")) # CHECK! if Procedure instance is deleted Biosample instance is deleted too procedure = models.ForeignKey(Procedure, on_delete=models.CASCADE, help_text=rec_help(d.BIOSAMPLE, "procedure")) hts_files = models.ManyToManyField( diff --git a/chord_metadata_service/phenopackets/serializers.py b/chord_metadata_service/phenopackets/serializers.py index 8669f16ec..80522cbd3 100644 --- a/chord_metadata_service/phenopackets/serializers.py +++ b/chord_metadata_service/phenopackets/serializers.py @@ -27,30 +27,30 @@ class Meta: model = MetaData fields = '__all__' - def validate_updates(self, value): - """ - Check updates against schema. - Timestamp must follow ISO8601 UTC standard - e.g. 2018-06-10T10:59:06Z - - """ - - if isinstance(value, list): - for item in value: - validation = Draft7Validator( - UPDATE_SCHEMA, format_checker=FormatChecker(formats=['date-time']) - ).is_valid(item) - if not validation: - raise serializers.ValidationError("Update is not valid") - return value - - def validate_external_references(self, value): - if isinstance(value, list): - for item in value: - validation = Draft7Validator(EXTERNAL_REFERENCE).is_valid(item) - if not validation: - raise serializers.ValidationError("Not valid JSON schema for this field.") - return value + # def validate_updates(self, value): + # """ + # Check updates against schema. + # Timestamp must follow ISO8601 UTC standard + # e.g. 2018-06-10T10:59:06Z + # + # """ + # + # if isinstance(value, list): + # for item in value: + # validation = Draft7Validator( + # UPDATE_SCHEMA, format_checker=FormatChecker(formats=['date-time']) + # ).is_valid(item) + # if not validation: + # raise serializers.ValidationError("Update is not valid") + # return value + # + # def validate_external_references(self, value): + # if isinstance(value, list): + # for item in value: + # validation = Draft7Validator(EXTERNAL_REFERENCE).is_valid(item) + # if not validation: + # raise serializers.ValidationError("Not valid JSON schema for this field.") + # return value ############################################################# @@ -60,16 +60,7 @@ def validate_external_references(self, value): ############################################################# class PhenotypicFeatureSerializer(GenericSerializer): - type = serializers.JSONField(source='pftype', validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) - severity = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - onset = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - evidence = serializers.JSONField( - validators=[JsonSchemaValidator(schema=EVIDENCE)], - allow_null=True, required=False) + type = serializers.JSONField(source='pftype') class Meta: model = PhenotypicFeature @@ -78,21 +69,13 @@ class Meta: fhir_datatype_plural = 'observations' class_converter = fhir_observation - def validate_modifier(self, value): - if isinstance(value, list): - for item in value: - validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) - if not validation: - raise serializers.ValidationError("Not valid JSON schema for this field.") - return value - class ProcedureSerializer(GenericSerializer): - code = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) - body_site = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) + # code = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) + # body_site = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + # allow_null=True, required=False) class Meta: model = Procedure @@ -134,11 +117,11 @@ class Meta: class VariantSerializer(GenericSerializer): - allele = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ALLELE_SCHEMA)]) - zygosity = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) + # allele = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ALLELE_SCHEMA)]) + # zygosity = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + # allow_null=True, required=False) class Meta: model = Variant @@ -167,11 +150,11 @@ def to_internal_value(self, data): class DiseaseSerializer(GenericSerializer): - term = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) - onset = serializers.JSONField( - validators=[JsonSchemaValidator(schema=DISEASE_ONSET)], - allow_null=True, required=False) + # term = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) + # onset = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=DISEASE_ONSET)], + # allow_null=True, required=False) class Meta: model = Disease @@ -180,35 +163,35 @@ class Meta: fhir_datatype_plural = 'conditions' class_converter = fhir_condition - def validate_disease_stage(self, value): - if isinstance(value, list): - for item in value: - validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) - if not validation: - raise serializers.ValidationError( - "Not valid JSON schema for this field." - ) - return value + # def validate_disease_stage(self, value): + # if isinstance(value, list): + # for item in value: + # validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) + # if not validation: + # raise serializers.ValidationError( + # "Not valid JSON schema for this field." + # ) + # return value class BiosampleSerializer(GenericSerializer): - sampled_tissue = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) - taxonomy = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - individual_age_at_collection = serializers.JSONField( - validators=[JsonSchemaValidator(schema=AGE_OR_AGE_RANGE)], - allow_null=True, required=False) - histological_diagnosis = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - tumor_progression = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) - tumor_grade = serializers.JSONField( - validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - allow_null=True, required=False) + # sampled_tissue = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) + # taxonomy = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + # allow_null=True, required=False) + # individual_age_at_collection = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=AGE_OR_AGE_RANGE)], + # allow_null=True, required=False) + # histological_diagnosis = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + # allow_null=True, required=False) + # tumor_progression = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + # allow_null=True, required=False) + # tumor_grade = serializers.JSONField( + # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], + # allow_null=True, required=False) phenotypic_features = PhenotypicFeatureSerializer( read_only=True, many=True, exclude_when_nested=['id', 'biosample']) procedure = ProcedureSerializer(exclude_when_nested=['id']) @@ -220,15 +203,15 @@ class Meta: fhir_datatype_plural = 'specimens' class_converter = fhir_specimen - def validate_diagnostic_markers(self, value): - if isinstance(value, list): - for item in value: - validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) - if not validation: - raise serializers.ValidationError( - "Not valid JSON schema for this field." - ) - return value + # def validate_diagnostic_markers(self, value): + # if isinstance(value, list): + # for item in value: + # validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) + # if not validation: + # raise serializers.ValidationError( + # "Not valid JSON schema for this field." + # ) + # return value def create(self, validated_data): procedure_data = validated_data.pop('procedure') diff --git a/chord_metadata_service/restapi/tests/utils.py b/chord_metadata_service/restapi/tests/utils.py index d05a08576..c9dbd05c3 100644 --- a/chord_metadata_service/restapi/tests/utils.py +++ b/chord_metadata_service/restapi/tests/utils.py @@ -8,6 +8,7 @@ def get_response(viewname, obj): """ Generic POST function. """ client = APIClient() + #print(json.dumps(obj)) response = client.post( reverse(viewname), data=json.dumps(obj), From 695a2c7c7553bfc90364e80e90361d79dde9d8f5 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Fri, 24 Apr 2020 16:51:20 -0400 Subject: [PATCH 86/95] cleaning --- chord_metadata_service/phenopackets/models.py | 4 +- .../phenopackets/serializers.py | 80 ------------------- 2 files changed, 1 insertion(+), 83 deletions(-) diff --git a/chord_metadata_service/phenopackets/models.py b/chord_metadata_service/phenopackets/models.py index 3e0f2b25f..6f9fee379 100644 --- a/chord_metadata_service/phenopackets/models.py +++ b/chord_metadata_service/phenopackets/models.py @@ -11,9 +11,7 @@ ) import chord_metadata_service.phenopackets.descriptions as d from chord_metadata_service.restapi.validators import ( - ontology_validator, quantity_validator, tumor_marker_test_validator, - complex_ontology_validator, time_or_period_validator, ontology_list_validator, - age_or_age_range_validator + ontology_validator, ontology_list_validator, age_or_age_range_validator ) diff --git a/chord_metadata_service/phenopackets/serializers.py b/chord_metadata_service/phenopackets/serializers.py index 80522cbd3..f25bc72d2 100644 --- a/chord_metadata_service/phenopackets/serializers.py +++ b/chord_metadata_service/phenopackets/serializers.py @@ -1,9 +1,6 @@ import re from rest_framework import serializers -from jsonschema import Draft7Validator, FormatChecker from .models import * -from chord_metadata_service.restapi.schemas import * -from chord_metadata_service.restapi.validators import JsonSchemaValidator from chord_metadata_service.restapi.serializers import GenericSerializer from chord_metadata_service.restapi.fhir_utils import * @@ -27,31 +24,6 @@ class Meta: model = MetaData fields = '__all__' - # def validate_updates(self, value): - # """ - # Check updates against schema. - # Timestamp must follow ISO8601 UTC standard - # e.g. 2018-06-10T10:59:06Z - # - # """ - # - # if isinstance(value, list): - # for item in value: - # validation = Draft7Validator( - # UPDATE_SCHEMA, format_checker=FormatChecker(formats=['date-time']) - # ).is_valid(item) - # if not validation: - # raise serializers.ValidationError("Update is not valid") - # return value - # - # def validate_external_references(self, value): - # if isinstance(value, list): - # for item in value: - # validation = Draft7Validator(EXTERNAL_REFERENCE).is_valid(item) - # if not validation: - # raise serializers.ValidationError("Not valid JSON schema for this field.") - # return value - ############################################################# # # @@ -71,11 +43,6 @@ class Meta: class ProcedureSerializer(GenericSerializer): - # code = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) - # body_site = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - # allow_null=True, required=False) class Meta: model = Procedure @@ -117,11 +84,6 @@ class Meta: class VariantSerializer(GenericSerializer): - # allele = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ALLELE_SCHEMA)]) - # zygosity = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - # allow_null=True, required=False) class Meta: model = Variant @@ -150,11 +112,6 @@ def to_internal_value(self, data): class DiseaseSerializer(GenericSerializer): - # term = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) - # onset = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=DISEASE_ONSET)], - # allow_null=True, required=False) class Meta: model = Disease @@ -163,35 +120,8 @@ class Meta: fhir_datatype_plural = 'conditions' class_converter = fhir_condition - # def validate_disease_stage(self, value): - # if isinstance(value, list): - # for item in value: - # validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) - # if not validation: - # raise serializers.ValidationError( - # "Not valid JSON schema for this field." - # ) - # return value - class BiosampleSerializer(GenericSerializer): - # sampled_tissue = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)]) - # taxonomy = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - # allow_null=True, required=False) - # individual_age_at_collection = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=AGE_OR_AGE_RANGE)], - # allow_null=True, required=False) - # histological_diagnosis = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - # allow_null=True, required=False) - # tumor_progression = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - # allow_null=True, required=False) - # tumor_grade = serializers.JSONField( - # validators=[JsonSchemaValidator(schema=ONTOLOGY_CLASS)], - # allow_null=True, required=False) phenotypic_features = PhenotypicFeatureSerializer( read_only=True, many=True, exclude_when_nested=['id', 'biosample']) procedure = ProcedureSerializer(exclude_when_nested=['id']) @@ -203,16 +133,6 @@ class Meta: fhir_datatype_plural = 'specimens' class_converter = fhir_specimen - # def validate_diagnostic_markers(self, value): - # if isinstance(value, list): - # for item in value: - # validation = Draft7Validator(ONTOLOGY_CLASS).is_valid(item) - # if not validation: - # raise serializers.ValidationError( - # "Not valid JSON schema for this field." - # ) - # return value - def create(self, validated_data): procedure_data = validated_data.pop('procedure') procedure_model, _ = Procedure.objects.get_or_create(**procedure_data) From dfae31550046fffb969d70d5f7b3096c3f02d684 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Mon, 27 Apr 2020 18:18:27 -0400 Subject: [PATCH 87/95] Bump DRF to 3.11 Update other dependencies in requirements --- requirements.txt | 18 +++++++++--------- setup.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index f5218d1d0..fd17fbc3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,19 @@ alabaster==0.7.12 attrs==19.3.0 Babel==2.8.0 -certifi==2019.11.28 +certifi==2020.4.5.1 chardet==3.0.4 chord-lib==0.7.0 codecov==2.0.22 colorama==0.4.3 coreapi==2.3.3 coreschema==0.0.4 -coverage==5.0.4 -Django==2.2.11 +coverage==5.1 +Django==2.2.12 django-filter==2.2.0 django-nose==1.4.6 django-rest-swagger==2.2.0 -djangorestframework==3.10.3 +djangorestframework==3.11.0 djangorestframework-camel-case==1.1.2 docutils==0.16 elasticsearch==7.6.0 @@ -22,8 +22,8 @@ idna==2.9 imagesize==1.2.0 importlib-metadata==1.6.0 isodate==0.6.0 -itypes==1.1.0 -Jinja2==2.11.1 +itypes==1.2.0 +Jinja2==2.11.2 jsonschema==3.2.0 Markdown==3.2.1 MarkupSafe==1.1.1 @@ -31,9 +31,9 @@ more-itertools==8.2.0 nose==1.3.7 openapi-codec==1.3.2 packaging==20.3 -psycopg2-binary==2.8.4 +psycopg2-binary==2.8.5 Pygments==2.6.1 -pyparsing==2.4.6 +pyparsing==2.4.7 pyrsistent==0.16.0 python-dateutil==2.8.1 pytz==2019.3 @@ -57,7 +57,7 @@ sphinxcontrib-serializinghtml==1.1.4 sqlparse==0.3.1 strict-rfc3339==0.7 uritemplate==3.0.1 -urllib3==1.25.8 +urllib3==1.25.9 Werkzeug==1.0.1 wincertstore==0.2 zipp==3.1.0 diff --git a/setup.py b/setup.py index a99f8de15..a9a19cdc9 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ "Django>=2.2,<3.0", "django-filter>=2.2,<3.0", "django-nose>=1.4,<2.0", - "djangorestframework>=3.10,<3.11", + "djangorestframework>=3.11,<3.12", "djangorestframework-camel-case>=1.1,<2.0", "django-rest-swagger==2.2.0", "elasticsearch==7.1.0", From 863620df7d067b8c670fc2c9ed964afd88d12315 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Tue, 28 Apr 2020 13:49:13 -0400 Subject: [PATCH 88/95] Fix missing package in setup.oy --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a9a19cdc9..6a56dbe96 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ "psycopg2-binary>=2.8,<3.0", "python-dateutil>=2.8,<3.0", "PyYAML>=5.3,<6.0", + "strict-rfc3339==0.7", "rdflib==4.2.2", "rdflib-jsonld==0.4.0", "requests>=2.23,<3.0", From 85503c952be234b544b8c43aee70fe8959b1ed2b Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Tue, 28 Apr 2020 14:58:10 -0400 Subject: [PATCH 89/95] Bump pytz in requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fd17fbc3a..4de3f64c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,7 +36,7 @@ Pygments==2.6.1 pyparsing==2.4.7 pyrsistent==0.16.0 python-dateutil==2.8.1 -pytz==2019.3 +pytz==2020.1 PyYAML==5.3.1 rdflib==4.2.2 rdflib-jsonld==0.4.0 From 7cfdcb0fda577710a9059a1e80829a9db7cf7b59 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Tue, 28 Apr 2020 14:58:48 -0400 Subject: [PATCH 90/95] Bump version to 0.6.0 --- chord_metadata_service/package.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chord_metadata_service/package.cfg b/chord_metadata_service/package.cfg index 7dffac0fc..b60d66a92 100644 --- a/chord_metadata_service/package.cfg +++ b/chord_metadata_service/package.cfg @@ -1,4 +1,4 @@ [package] name = chord_metadata_service -version = 0.5.2 +version = 0.6.0 authors = Ksenia Zaytseva, David Lougheed, Simon Chénard From 31a0af70154172194bf9dcd408f40a85e0c224f8 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Tue, 28 Apr 2020 15:21:07 -0400 Subject: [PATCH 91/95] Update chord_lib to 0.8.0 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index fd17fbc3a..e451889ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ attrs==19.3.0 Babel==2.8.0 certifi==2020.4.5.1 chardet==3.0.4 -chord-lib==0.7.0 +chord-lib==0.8.0 codecov==2.0.22 colorama==0.4.3 coreapi==2.3.3 diff --git a/setup.py b/setup.py index 6a56dbe96..cc4dc0cb8 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ python_requires=">=3.6", install_requires=[ - "chord_lib[django]==0.7.0", + "chord_lib[django]==0.8.0", "Django>=2.2,<3.0", "django-filter>=2.2,<3.0", "django-nose>=1.4,<2.0", From bdc28ef2917391843d3e36b06befe977d11988e5 Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Tue, 28 Apr 2020 15:40:17 -0400 Subject: [PATCH 92/95] small fix --- chord_metadata_service/phenopackets/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chord_metadata_service/phenopackets/models.py b/chord_metadata_service/phenopackets/models.py index 6f9fee379..a8b7cf77a 100644 --- a/chord_metadata_service/phenopackets/models.py +++ b/chord_metadata_service/phenopackets/models.py @@ -66,8 +66,7 @@ class MetaData(models.Model): external_references = ArrayField( JSONField(null=True, blank=True, validators=[JsonSchemaValidator(EXTERNAL_REFERENCE)]), blank=True, null=True, help_text=rec_help(d.META_DATA, "external_references")) - extra_properties = JSONField( - blank=True, null=True, help_text=rec_help(d.META_DATA, "extra_properties")) + extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.META_DATA, "extra_properties")) updated = models.DateTimeField(auto_now_add=True) def __str__(self): From 7d85a7e5c0eedf7f10e58bb9dc1583420c2bf0ad Mon Sep 17 00:00:00 2001 From: ksenia1zaytseva Date: Tue, 28 Apr 2020 16:37:03 -0400 Subject: [PATCH 93/95] add validator to tnm_finding add migrations reformat constants file --- .../migrations/0005_auto_20200428_1633.py | 126 +++++++++++++ chord_metadata_service/phenopackets/models.py | 4 +- .../phenopackets/tests/constants.py | 172 +++++++++--------- 3 files changed, 209 insertions(+), 93 deletions(-) create mode 100644 chord_metadata_service/phenopackets/migrations/0005_auto_20200428_1633.py diff --git a/chord_metadata_service/phenopackets/migrations/0005_auto_20200428_1633.py b/chord_metadata_service/phenopackets/migrations/0005_auto_20200428_1633.py new file mode 100644 index 000000000..e4cd3544c --- /dev/null +++ b/chord_metadata_service/phenopackets/migrations/0005_auto_20200428_1633.py @@ -0,0 +1,126 @@ +# Generated by Django 2.2.12 on 2020-04-28 20:33 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('phenopackets', '0004_auto_20200129_1537'), + ] + + operations = [ + migrations.AlterField( + model_name='biosample', + name='diagnostic_markers', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='A list of ontology terms representing clinically-relevant bio-markers.', null=True, size=None), + ), + migrations.AlterField( + model_name='biosample', + name='histological_diagnosis', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology term representing a refinement of the clinical diagnosis. Normal samples could be tagged with NCIT:C38757, representing a negative finding.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='biosample', + name='individual_age_at_collection', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='individual_age_at_collection', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:age_or_age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age object describing the age of the individual at the time of collection of biospecimens or phenotypic observations.', 'properties': {'age': {'anyOf': [{'description': 'An ISO8601 string represent age.', 'type': 'string'}, {'$id': 'chord_metadata_service:age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age range of a subject.', 'properties': {'end': {'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'type': 'object'}, 'start': {'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'type': 'object'}}, 'required': ['start', 'end'], 'title': 'Age schema', 'type': 'object'}]}}, 'title': 'Age schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='biosample', + name='sampled_tissue', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology term describing the tissue from which the specimen was collected. The use of UBERON is recommended.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='biosample', + name='taxonomy', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology term specified when more than one organism may be studied. It is advised that codesfrom the NCBI Taxonomy resource are used, e.g. NCBITaxon:9606 for humans.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='biosample', + name='tumor_grade', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology term representing the tumour grade. This should be a child term of NCIT:C28076 (Disease Grade Qualifier) or equivalent.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='biosample', + name='tumor_progression', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology term representing if the specimen is from a primary tumour, a metastasis, or a recurrence. There are multiple ways of representing this using ontology terms, and the terms chosen will have a specific meaning that is application specific..', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='disease', + name='disease_stage', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='A list of terms representing the disease stage. Elements should be derived from child terms of NCIT:C28108 (Disease Stage Qualifier) or equivalent hierarchy from another ontology.', null=True, size=None), + ), + migrations.AlterField( + model_name='disease', + name='onset', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A representation of the age of onset of the disease', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:disease_onset_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema for the age of the onset of the disease.', 'properties': {'age': {'anyOf': [{'description': 'An ISO8601 string represent age.', 'type': 'string'}, {'$id': 'chord_metadata_service:age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age range of a subject.', 'properties': {'end': {'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'type': 'object'}, 'start': {'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'type': 'object'}}, 'required': ['start', 'end'], 'title': 'Age schema', 'type': 'object'}, {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}]}}, 'title': 'Onset age', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='disease', + name='term', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text="An ontology term that represents the disease. It's recommended that one of the OMIM, Orphanet, or MONDO ontologies is used for rare human diseases.", validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='disease', + name='tnm_finding', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='A list of terms representing the tumour TNM score. Elements should be derived from child terms of NCIT:C48232 (Cancer TNM Finding) or equivalent hierarchy from another ontology.', null=True, size=None), + ), + migrations.AlterField( + model_name='metadata', + name='external_references', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:external_reference_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'The schema encodes information about an external reference.', 'properties': {'description': {'description': 'An application specific description.', 'type': 'string'}, 'id': {'description': 'An application specific identifier.', 'type': 'string'}}, 'required': ['id'], 'title': 'External reference schema', 'type': 'object'})]), blank=True, help_text='A list of external (non-resource) references.', null=True, size=None), + ), + migrations.AlterField( + model_name='metadata', + name='updates', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:update_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'Schema to check incoming updates format', 'properties': {'comment': {'description': 'Comment about updates or reasons for an update.', 'type': 'string'}, 'timestamp': {'description': 'ISO8601 UTC timestamp at which this record was updated.', 'format': 'date-time', 'type': 'string'}, 'updated_by': {'description': 'Who updated the phenopacket', 'type': 'string'}}, 'required': ['timestamp', 'comment'], 'title': 'Updates schema', 'type': 'object'})]), blank=True, help_text='A list of updates to the phenopacket.', null=True, size=None), + ), + migrations.AlterField( + model_name='phenotypicfeature', + name='evidence', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='One or more pieces of evidence that specify how the phenotype was determined.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:evidence_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'The schema represents the evidence for an assertion such as an observation of a PhenotypicFeature.', 'properties': {'evidence_code': {'additionalProperties': False, 'description': 'An ontology class that represents the evidence type.', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'type': 'object'}, 'reference': {'additionalProperties': False, 'description': 'Representation of the source of the evidence.', 'properties': {'description': {'description': 'An application specific description.', 'type': 'string'}, 'id': {'description': 'An application specific identifier.', 'type': 'string'}}, 'required': ['id'], 'type': 'object'}}, 'required': ['evidence_code'], 'title': 'Evidence schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='phenotypicfeature', + name='modifier', + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), blank=True, help_text='A list of ontology terms that provide more expressive / precise descriptions of a phenotypic feature, including e.g. positionality or external factors.', null=True, size=None), + ), + migrations.AlterField( + model_name='phenotypicfeature', + name='onset', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology term that describes the age at which the phenotypic feature was first noticed or diagnosed, e.g. HP:0003674.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='phenotypicfeature', + name='pftype', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology term which describes the phenotype.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})], verbose_name='type'), + ), + migrations.AlterField( + model_name='phenotypicfeature', + name='severity', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology term that describes the severity of the condition.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='procedure', + name='body_site', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology term that is specified when it is not possible to represent the procedure with a single ontology class.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='procedure', + name='code', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text='An ontology term that represents a clinical procedure performed on a subject.', validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='variant', + name='allele', + field=django.contrib.postgres.fields.jsonb.JSONField(help_text="The variant's corresponding allele", validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:allele_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'dependencies': {'genome_assembly': ['chr', 'pos', 're', 'alt', 'info'], 'seq_id': ['position', 'deleted_sequence', 'inserted_sequence']}, 'description': 'Variant allele types', 'oneOf': [{'required': ['hgvs']}, {'required': ['genome_assembly']}, {'required': ['seq_id']}, {'required': ['iscn']}], 'properties': {'alt': {'type': 'string'}, 'chr': {'type': 'string'}, 'deleted_sequence': {'type': 'string'}, 'genome_assembly': {'type': 'string'}, 'hgvs': {'type': 'string'}, 'id': {'type': 'string'}, 'info': {'type': 'string'}, 'inserted_sequence': {'type': 'string'}, 'iscn': {'type': 'string'}, 'pos': {'type': 'integer'}, 'position': {'type': 'integer'}, 're': {'type': 'string'}, 'seq_id': {'type': 'string'}}, 'title': 'Allele schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='variant', + name='zygosity', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='An ontology term taken from the Genotype Ontology (GENO) representing the zygosity of the variant.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'})]), + ), + ] diff --git a/chord_metadata_service/phenopackets/models.py b/chord_metadata_service/phenopackets/models.py index a8b7cf77a..d641fa1f8 100644 --- a/chord_metadata_service/phenopackets/models.py +++ b/chord_metadata_service/phenopackets/models.py @@ -244,8 +244,8 @@ class Disease(models.Model, IndexableMixin): help_text=rec_help(d.DISEASE, "onset")) disease_stage = ArrayField(JSONField(null=True, blank=True, validators=[ontology_validator]), blank=True, null=True, help_text=rec_help(d.DISEASE, "disease_stage")) - tnm_finding = ArrayField( - JSONField(null=True, blank=True), blank=True, null=True, help_text=rec_help(d.DISEASE, "tnm_finding")) + tnm_finding = ArrayField(JSONField(null=True, blank=True, validators=[ontology_validator]), + blank=True, null=True, help_text=rec_help(d.DISEASE, "tnm_finding")) extra_properties = JSONField(blank=True, null=True, help_text=rec_help(d.DISEASE, "extra_properties")) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) diff --git a/chord_metadata_service/phenopackets/tests/constants.py b/chord_metadata_service/phenopackets/tests/constants.py index 767824f63..1624294d2 100644 --- a/chord_metadata_service/phenopackets/tests/constants.py +++ b/chord_metadata_service/phenopackets/tests/constants.py @@ -9,7 +9,6 @@ } } - VALID_PROCEDURE_2 = { "code": { "id": "NCIT:C28743", @@ -21,129 +20,118 @@ } } - VALID_META_DATA_1 = { "created_by": "David Lougheed", "submitted_by": "David Lougheed" } - VALID_META_DATA_2 = { "created_by": "Ksenia Zaytseva", "submitted_by": "Ksenia Zaytseva", "external_references": [ { - "id": "PMID:30808312", - "description": "Bao M, et al. COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report. BMC Neurol. 2019;19(1):32." + "id": "PMID:30808312", + "description": "Bao M, et al. COL6A1 mutation leading to Bethlem myopathy with recurrent hematuria: a case report. BMC Neurol. 2019;19(1):32." }, { - "id": "PMID:3080844", - "description": "Test" + "id": "PMID:3080844", + "description": "Test" } - ], + ], "updates": [ { - "timestamp": "2018-06-10T10:59:06Z", - "updated_by": "Julius J.", - "comment": "added phenotypic features to individual patient:1" + "timestamp": "2018-06-10T10:59:06Z", + "updated_by": "Julius J.", + "comment": "added phenotypic features to individual patient:1" } ], "phenopacket_schema_version": "0.1" } - VALID_INDIVIDUAL_1 = { "id": "patient:1", "date_of_birth": "1967-01-01", "sex": "MALE" } - VALID_INDIVIDUAL_2 = { "id": "patient:2", "date_of_birth": "1978-01-01", "sex": "FEMALE" } - VALID_HTS_FILE = { "uri": "https://data.example/genomes/germline_wgs.vcf.gz", "description": "Matched normal germline sample", "hts_format": "VCF", "genome_assembly": "GRCh38", "individual_to_sample_identifiers": { - "patient:1": "NA12345" + "patient:1": "NA12345" }, "extra_properties": { "comment": "test data" } } - VALID_GENE_1 = { - "id": "HGNC:347", - "alternate_ids": ["ensembl:ENSRNOG00000019450", "ncbigene:307503"], - "symbol": "ETF1", - "extra_properties": { - "comment": "test data" - } + "id": "HGNC:347", + "alternate_ids": ["ensembl:ENSRNOG00000019450", "ncbigene:307503"], + "symbol": "ETF1", + "extra_properties": { + "comment": "test data" + } } - INVALID_GENE_2 = { - "id": "HGNC:347", - "alternate_ids": "ensembl:ENSRNOG00000019450", - "symbol": "ETF1", - "extra_properties": { - "comment": "test data" - } + "id": "HGNC:347", + "alternate_ids": "ensembl:ENSRNOG00000019450", + "symbol": "ETF1", + "extra_properties": { + "comment": "test data" + } } - DUPLICATE_GENE_2 = { - "id": "HGNC:347", - "symbol": "DYI" + "id": "HGNC:347", + "symbol": "DYI" } - VALID_VARIANT_1 = { "allele_type": "spdiAllele", "allele": { - "id": "clinvar:13294", - "seq_id": "NC_000010.10", - "position": 123256214, - "deleted_sequence": "T", - "inserted_sequence": "G" + "id": "clinvar:13294", + "seq_id": "NC_000010.10", + "position": 123256214, + "deleted_sequence": "T", + "inserted_sequence": "G" }, "zygosity": { - "id": "NCBITaxon:9606", - "label": "human" + "id": "NCBITaxon:9606", + "label": "human" }, "extra_properties": { "comment": "test data" } } - VALID_VARIANT_2 = { "allele_type": "spdiAllele", "spdiAllele": { - "id": "clinvar:13294", - "seq_id": "NC_000010.10", - "position": 123256214, - "deleted_sequence": "T", - "inserted_sequence": "G" + "id": "clinvar:13294", + "seq_id": "NC_000010.10", + "position": 123256214, + "deleted_sequence": "T", + "inserted_sequence": "G" }, "zygosity": { - "id": "NCBITaxon:9606", - "label": "human" + "id": "NCBITaxon:9606", + "label": "human" }, "extra_properties": { "comment": "test data" } } - VALID_DISEASE_1 = { "term": { "id": "OMIM:164400", @@ -154,8 +142,14 @@ }, "disease_stage": [ { - "id": "NCIT:C28091", - "label": "Gleason Score 7" + "id": "NCIT:C48233", + "label": "Cancer TNM Finding by Site" + } + ], + "tnm_finding": [ + { + "id": "NCIT:C28091", + "label": "Gleason Score 7" } ], "extra_properties": { @@ -163,7 +157,6 @@ } } - INVALID_DISEASE_2 = { "term": { "id": "OMIM:164400", @@ -174,7 +167,7 @@ }, "disease_stage": [ { - "id": "NCIT:C28091" + "id": "NCIT:C28091" } ], "extra_properties": { @@ -182,7 +175,6 @@ } } - VALID_RESOURCE_1 = { "id": "so", "name": "Sequence types and features", @@ -192,7 +184,6 @@ "iri_prefix": "http://purl.obolibrary.org/obo/SO_" } - VALID_RESOURCE_2 = { "id": "hgnc", "name": "HUGO Gene Nomenclature Committee", @@ -202,7 +193,6 @@ "iri_prefix": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/" } - DUPLICATE_RESOURCE_3 = { "id": "hgnc", "name": "HUGO Gene Nomenclature Committee", @@ -217,7 +207,7 @@ def valid_phenopacket(subject, meta_data): id='phenopacket:1', subject=subject, meta_data=meta_data - ) + ) def valid_biosample_1(individual, procedure): @@ -234,15 +224,15 @@ def valid_biosample_1(individual, procedure): "label": "Homo sapiens" }, individual_age_at_collection={ - "age": { - "start": { - "age": "P45Y" - }, - "end": { - "age": "P49Y" - } + "age": { + "start": { + "age": "P45Y" + }, + "end": { + "age": "P49Y" } - }, + } + }, histological_diagnosis={ "id": "NCIT:C39853", "label": "Infiltrating Urothelial Carcinoma" @@ -284,15 +274,15 @@ def valid_biosample_2(individual, procedure): "label": "Homo sapiens" }, individual_age_at_collection={ - "age": { - "start": { - "age": "P45Y" - }, - "end": { - "age": "P49Y" - } + "age": { + "start": { + "age": "P45Y" + }, + "end": { + "age": "P49Y" } - }, + } + }, histological_diagnosis={ "id": "NCIT:C39853", "label": "Infiltrating Urothelial Carcinoma" @@ -325,7 +315,7 @@ def valid_phenotypic_feature(biosample=None, phenopacket=None): description='This is a test phenotypic feature', pftype={ "id": "HP:0000520", - "label": "Proptosis" + "label": "Proptosis" }, negated=True, severity={ @@ -334,12 +324,12 @@ def valid_phenotypic_feature(biosample=None, phenopacket=None): }, modifier=[ { - "id": "HP: 0012825 ", - "label": "Mild" + "id": "HP: 0012825 ", + "label": "Mild" }, { - "id": "HP: 0012823 ", - "label": "Semi-mild" + "id": "HP: 0012823 ", + "label": "Semi-mild" } ], onset={ @@ -361,7 +351,7 @@ def valid_phenotypic_feature(biosample=None, phenopacket=None): }, biosample=biosample, phenopacket=phenopacket - ) + ) def invalid_phenotypic_feature(): @@ -374,10 +364,10 @@ def invalid_phenotypic_feature(): }, modifier=[ { - "label": "Mild" + "label": "Mild" }, { - "id": "HP: 0012823 " + "id": "HP: 0012823 " } ], onset={ @@ -397,18 +387,18 @@ def invalid_phenotypic_feature(): extra_properties={ "comment": "test data" } - ) + ) def valid_genomic_interpretation(gene=None, variant=None): return dict( - status='CANDIDATE', - gene=gene, - variant=variant, - extra_properties={ - "comment": "test data" - } - ) + status='CANDIDATE', + gene=gene, + variant=variant, + extra_properties={ + "comment": "test data" + } + ) def valid_diagnosis(disease): @@ -417,7 +407,7 @@ def valid_diagnosis(disease): extra_properties={ "comment": "test data" } - ) + ) def valid_interpretation(phenopacket, meta_data): @@ -429,4 +419,4 @@ def valid_interpretation(phenopacket, meta_data): extra_properties={ "comment": "test data" } - ) + ) From cedb9bdc94209165b89590ab0b8a60d7b6787e41 Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Thu, 30 Apr 2020 10:40:21 -0400 Subject: [PATCH 94/95] Fix age schema Clean up some tests/other code --- .../patients/tests/constants.py | 24 +++----- .../patients/tests/test_api.py | 13 ++-- .../phenopackets/tests/constants.py | 24 +++----- chord_metadata_service/restapi/fhir_utils.py | 8 +-- chord_metadata_service/restapi/schemas.py | 61 ++++++++----------- .../restapi/tests/test_fhir.py | 39 +++++++----- chord_metadata_service/restapi/tests/utils.py | 5 +- 7 files changed, 83 insertions(+), 91 deletions(-) diff --git a/chord_metadata_service/patients/tests/constants.py b/chord_metadata_service/patients/tests/constants.py index 0adccaef9..fc85c3810 100644 --- a/chord_metadata_service/patients/tests/constants.py +++ b/chord_metadata_service/patients/tests/constants.py @@ -6,13 +6,11 @@ }, "date_of_birth": "1960-01-01", "age": { - "age": { - "start": { - "age": "P45Y" - }, - "end": { - "age": "P49Y" - } + "start": { + "age": "P45Y" + }, + "end": { + "age": "P49Y" } }, "sex": "FEMALE", @@ -26,13 +24,11 @@ }, "date_of_birth": "1960-01-01", "age": { - "age": { - "start": { - "age": "P45Y" - }, - "end": { - "age": "P49Y" - } + "start": { + "age": "P45Y" + }, + "end": { + "age": "P49Y" } }, "sex": "FEM", diff --git a/chord_metadata_service/patients/tests/test_api.py b/chord_metadata_service/patients/tests/test_api.py index 3773ffaab..fb3b0550e 100644 --- a/chord_metadata_service/patients/tests/test_api.py +++ b/chord_metadata_service/patients/tests/test_api.py @@ -51,13 +51,12 @@ def setUp(self): "label": "human" }, "date_of_birth": "2001-01-01", - "age": {"age": { - "start": { - "age": "P45Y" - }, - "end": { - "age": "P49Y" - } + "age": { + "start": { + "age": "P45Y" + }, + "end": { + "age": "P49Y" } }, "sex": "FEMALE", diff --git a/chord_metadata_service/phenopackets/tests/constants.py b/chord_metadata_service/phenopackets/tests/constants.py index 1624294d2..bd0810cd5 100644 --- a/chord_metadata_service/phenopackets/tests/constants.py +++ b/chord_metadata_service/phenopackets/tests/constants.py @@ -224,13 +224,11 @@ def valid_biosample_1(individual, procedure): "label": "Homo sapiens" }, individual_age_at_collection={ - "age": { - "start": { - "age": "P45Y" - }, - "end": { - "age": "P49Y" - } + "start": { + "age": "P45Y" + }, + "end": { + "age": "P49Y" } }, histological_diagnosis={ @@ -274,13 +272,11 @@ def valid_biosample_2(individual, procedure): "label": "Homo sapiens" }, individual_age_at_collection={ - "age": { - "start": { - "age": "P45Y" - }, - "end": { - "age": "P49Y" - } + "start": { + "age": "P45Y" + }, + "end": { + "age": "P49Y" } }, histological_diagnosis={ diff --git a/chord_metadata_service/restapi/fhir_utils.py b/chord_metadata_service/restapi/fhir_utils.py index f13bbbc84..2894dd888 100644 --- a/chord_metadata_service/restapi/fhir_utils.py +++ b/chord_metadata_service/restapi/fhir_utils.py @@ -56,13 +56,13 @@ def fhir_age(obj, mapping, field): age_extension = extension.Extension() age_extension.url = mapping - if isinstance(obj[field]['age'], dict): + if "start" in obj[field]: # Is an age range age_extension.valueRange = range.Range() age_extension.valueRange.low = quantity.Quantity() - age_extension.valueRange.low.unit = obj[field]['age']['start']['age'] + age_extension.valueRange.low.unit = obj[field]['start']['age'] age_extension.valueRange.high = quantity.Quantity() - age_extension.valueRange.high.unit = obj[field]['age']['end']['age'] - else: + age_extension.valueRange.high.unit = obj[field]['end']['age'] + else: # Is a precise age age_extension.valueAge = age.Age() age_extension.valueAge.unit = obj[field]['age'] return age_extension diff --git a/chord_metadata_service/restapi/schemas.py b/chord_metadata_service/restapi/schemas.py index 00b726144..96935c9a9 100644 --- a/chord_metadata_service/restapi/schemas.py +++ b/chord_metadata_service/restapi/schemas.py @@ -143,33 +143,36 @@ "description": "The schema represents a key-value object.", "type": "object", "patternProperties": { - "^.*$": { "type": "string" } + "^.*$": {"type": "string"} }, "additionalProperties": False } -AGE = {"type": "string", "description": "An ISO8601 string represent age."} +AGE_STRING = {"type": "string", "description": "An ISO8601 string represent age."} + +AGE = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "chord_metadata_service:age_schema", + "title": "Age schema", + "description": "An age of a subject.", + "type": "object", + "properties": { + "age": AGE_STRING + }, + "additionalProperties": False, + "required": ["age"] +} AGE_RANGE = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "chord_metadata_service:age_range_schema", - "title": "Age schema", + "title": "Age range schema", "description": "An age range of a subject.", "type": "object", "properties": { - "start": { - "type": "object", - "properties": { - "age": AGE - } - }, - "end": { - "type": "object", - "properties": { - "age": AGE - } - } + "start": AGE, + "end": AGE }, "additionalProperties": False, "required": ["start", "end"] @@ -182,15 +185,10 @@ "description": "An age object describing the age of the individual at the time of collection of biospecimens or " "phenotypic observations.", "type": "object", - "properties": { - "age": { - "anyOf": [ - AGE, - AGE_RANGE - ] - } - }, - "additionalProperties": False + "oneOf": [ + AGE, + AGE_RANGE + ] } DISEASE_ONSET = { @@ -199,16 +197,11 @@ "title": "Onset age", "description": "Schema for the age of the onset of the disease.", "type": "object", - "properties": { - "age": { - "anyOf": [ - AGE, - AGE_RANGE, - ONTOLOGY_CLASS - ] - } - }, - "additionalProperties": False + "oneOf": [ + AGE, + AGE_RANGE, + ONTOLOGY_CLASS + ] } ################################## mCode/FHIR based schemas ################################## diff --git a/chord_metadata_service/restapi/tests/test_fhir.py b/chord_metadata_service/restapi/tests/test_fhir.py index ab4731802..3a9dc3561 100644 --- a/chord_metadata_service/restapi/tests/test_fhir.py +++ b/chord_metadata_service/restapi/tests/test_fhir.py @@ -1,10 +1,22 @@ from rest_framework.test import APITestCase -from chord_metadata_service.phenopackets.tests.constants import * -from chord_metadata_service.patients.tests.constants import * +from chord_metadata_service.phenopackets.tests.constants import ( + VALID_INDIVIDUAL_1, + VALID_META_DATA_2, + VALID_PROCEDURE_1, + VALID_HTS_FILE, + VALID_DISEASE_1, + VALID_GENE_1, + VALID_VARIANT_1, + valid_biosample_1, + valid_biosample_2, + valid_phenotypic_feature, +) +from chord_metadata_service.patients.tests.constants import VALID_INDIVIDUAL, VALID_INDIVIDUAL_2 from chord_metadata_service.restapi.tests.utils import get_response from chord_metadata_service.phenopackets.serializers import * from rest_framework import status + # Tests for FHIR conversion functions class FHIRPhenopacketTest(APITestCase): @@ -56,6 +68,7 @@ def setUp(self): def test_get_fhir(self): response_1 = get_response('individual-list', self.individual) response_2 = get_response('individual-list', self.individual_second) + print(response_1.data, response_2.data) get_resp = self.client.get('/api/individuals?format=fhir') self.assertEqual(get_resp.status_code, status.HTTP_200_OK) get_resp_obj = get_resp.json() @@ -80,18 +93,17 @@ def setUp(self): self.phenotypic_feature_2 = PhenotypicFeature.objects.create( **valid_phenotypic_feature(biosample=self.biosample_2)) - def test_get_fhir(self): get_resp = self.client.get('/api/phenotypicfeatures?format=fhir') self.assertEqual(get_resp.status_code, status.HTTP_200_OK) get_resp_obj = get_resp.json() severity = { - 'url':'http://ga4gh.org/fhir/phenopackets/StructureDefinition/phenotypic-feature-severity', - 'valueCodeableConcept':{ - 'coding':[ + 'url': 'http://ga4gh.org/fhir/phenopackets/StructureDefinition/phenotypic-feature-severity', + 'valueCodeableConcept': { + 'coding': [ { - 'code':'HP: 0012825', - 'display':'Mild' + 'code': 'HP: 0012825', + 'display': 'Mild' } ] } @@ -103,7 +115,7 @@ def test_get_fhir(self): self.assertEqual(get_resp_obj['observations'][0]['code']['coding'][0]['display'], 'Proptosis') self.assertEqual(get_resp_obj['observations'][0]['interpretation']['coding'][0]['code'], 'POS') self.assertEqual(get_resp_obj['observations'][0]['extension'][3]['url'], - 'http://ga4gh.org/fhir/phenopackets/StructureDefinition/evidence') + 'http://ga4gh.org/fhir/phenopackets/StructureDefinition/evidence') self.assertEqual(get_resp_obj['observations'][0]['extension'][3]['extension'][1]['extension'][1]['url'], 'description') self.assertIsNotNone(get_resp_obj['observations'][0]['specimen']) @@ -137,7 +149,7 @@ def setUp(self): def test_get_fhir(self): """ POST a new biosample. """ - response = get_response('biosample-list', self.valid_payload) + get_response('biosample-list', self.valid_payload) get_resp = self.client.get('/api/biosamples?format=fhir') self.assertEqual(get_resp.status_code, status.HTTP_200_OK) get_resp_obj = get_resp.json() @@ -148,7 +160,7 @@ def test_get_fhir(self): self.assertEqual(get_resp_obj['specimens'][0]['extension'][4]['url'], 'http://ga4gh.org/fhir/phenopackets/StructureDefinition/biosample-diagnostic-markers') self.assertIsInstance(get_resp_obj['specimens'][0]['extension'][4]['valueCodeableConcept']['coding'], - list) + list) self.assertTrue(get_resp_obj['specimens'][0]['extension'][5]['valueBoolean']) @@ -158,7 +170,7 @@ def setUp(self): self.hts_file = VALID_HTS_FILE def test_get_fhir(self): - response = get_response('htsfile-list', self.hts_file) + get_response('htsfile-list', self.hts_file) get_resp = self.client.get('/api/htsfiles?format=fhir') self.assertEqual(get_resp.status_code, status.HTTP_200_OK) get_resp_obj = get_resp.json() @@ -225,6 +237,5 @@ def test_get_fhir(self): self.assertIsNotNone(get_resp_obj['conditions'][0]['code']['coding'][0]) self.assertIsInstance(get_resp_obj['conditions'][0]['extension'], list) self.assertEqual(get_resp_obj['conditions'][0]['extension'][0]['url'], - 'http://ga4gh.org/fhir/phenopackets/StructureDefinition/disease-tumor-stage') + 'http://ga4gh.org/fhir/phenopackets/StructureDefinition/disease-tumor-stage') self.assertEqual(get_resp_obj['conditions'][0]['subject']['reference'], 'unknown') - \ No newline at end of file diff --git a/chord_metadata_service/restapi/tests/utils.py b/chord_metadata_service/restapi/tests/utils.py index c9dbd05c3..73f4c50fb 100644 --- a/chord_metadata_service/restapi/tests/utils.py +++ b/chord_metadata_service/restapi/tests/utils.py @@ -6,12 +6,9 @@ def get_response(viewname, obj): """ Generic POST function. """ - client = APIClient() - #print(json.dumps(obj)) - response = client.post( + return client.post( reverse(viewname), data=json.dumps(obj), content_type='application/json' ) - return response From 8f808e4789a4579b8a5ec11ab32ae144d949da0a Mon Sep 17 00:00:00 2001 From: David Lougheed Date: Thu, 30 Apr 2020 10:44:58 -0400 Subject: [PATCH 95/95] Add migrations --- .../migrations/0007_auto_20200430_1444.py | 20 +++++++++++++++ .../migrations/0006_auto_20200430_1444.py | 25 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 chord_metadata_service/patients/migrations/0007_auto_20200430_1444.py create mode 100644 chord_metadata_service/phenopackets/migrations/0006_auto_20200430_1444.py diff --git a/chord_metadata_service/patients/migrations/0007_auto_20200430_1444.py b/chord_metadata_service/patients/migrations/0007_auto_20200430_1444.py new file mode 100644 index 000000000..cc0ddae7b --- /dev/null +++ b/chord_metadata_service/patients/migrations/0007_auto_20200430_1444.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.12 on 2020-04-30 14:44 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('patients', '0006_auto_20200401_1504'), + ] + + operations = [ + migrations.AlterField( + model_name='individual', + name='age', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='The age or age range of the individual.', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:age_or_age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'An age object describing the age of the individual at the time of collection of biospecimens or phenotypic observations.', 'oneOf': [{'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}, {'$id': 'chord_metadata_service:age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age range of a subject.', 'properties': {'end': {'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}, 'start': {'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}}, 'required': ['start', 'end'], 'title': 'Age range schema', 'type': 'object'}], 'title': 'Age schema', 'type': 'object'})]), + ), + ] diff --git a/chord_metadata_service/phenopackets/migrations/0006_auto_20200430_1444.py b/chord_metadata_service/phenopackets/migrations/0006_auto_20200430_1444.py new file mode 100644 index 000000000..6e32d8507 --- /dev/null +++ b/chord_metadata_service/phenopackets/migrations/0006_auto_20200430_1444.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.12 on 2020-04-30 14:44 + +import chord_metadata_service.restapi.validators +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('phenopackets', '0005_auto_20200428_1633'), + ] + + operations = [ + migrations.AlterField( + model_name='biosample', + name='individual_age_at_collection', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='individual_age_at_collection', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:age_or_age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'An age object describing the age of the individual at the time of collection of biospecimens or phenotypic observations.', 'oneOf': [{'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}, {'$id': 'chord_metadata_service:age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age range of a subject.', 'properties': {'end': {'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}, 'start': {'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}}, 'required': ['start', 'end'], 'title': 'Age range schema', 'type': 'object'}], 'title': 'Age schema', 'type': 'object'})]), + ), + migrations.AlterField( + model_name='disease', + name='onset', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='A representation of the age of onset of the disease', null=True, validators=[chord_metadata_service.restapi.validators.JsonSchemaValidator({'$id': 'chord_metadata_service:disease_onset_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'description': 'Schema for the age of the onset of the disease.', 'oneOf': [{'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}, {'$id': 'chord_metadata_service:age_range_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age range of a subject.', 'properties': {'end': {'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}, 'start': {'$id': 'chord_metadata_service:age_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'An age of a subject.', 'properties': {'age': {'description': 'An ISO8601 string represent age.', 'type': 'string'}}, 'required': ['age'], 'title': 'Age schema', 'type': 'object'}}, 'required': ['start', 'end'], 'title': 'Age range schema', 'type': 'object'}, {'$id': 'chord_metadata_service:ontology_class_schema', '$schema': 'http://json-schema.org/draft-07/schema#', 'additionalProperties': False, 'description': 'todo', 'properties': {'id': {'description': 'CURIE style identifier.', 'type': 'string'}, 'label': {'description': 'Human-readable class name.', 'type': 'string'}}, 'required': ['id', 'label'], 'title': 'Ontology class schema', 'type': 'object'}], 'title': 'Onset age', 'type': 'object'})]), + ), + ]