diff --git a/.github/workflows/update-schema.yaml b/.github/workflows/update-schema.yaml
index 8ee36d3f9..780dfd167 100644
--- a/.github/workflows/update-schema.yaml
+++ b/.github/workflows/update-schema.yaml
@@ -34,6 +34,7 @@ jobs:
run: |
export DJANGO_SETTINGS_MODULE=config.settings.base
echo "CACHE_DURATION = 0" >> config/settings/base.py
+ echo "AGGREGATE_COUNT_THRESHOLD = 5" >> config/settings/base.py
python manage.py export_openapi_schema --api chord_metadata_service.mohpackets.apis.core.api | python -m json.tool > chord_metadata_service/mohpackets/docs/schema.json
- name: Commit new schema.json
diff --git a/chord_metadata_service/mohpackets/apis/discovery.py b/chord_metadata_service/mohpackets/apis/discovery.py
index 85e1bd7ff..c7bb80327 100644
--- a/chord_metadata_service/mohpackets/apis/discovery.py
+++ b/chord_metadata_service/mohpackets/apis/discovery.py
@@ -1,13 +1,20 @@
-from collections import Counter
-from typing import Any, Dict, List, Type
+from typing import Any, Dict, List
from django.conf import settings
from django.db.models import (
+ Case,
+ CharField,
Count,
- Model,
+ F,
+ Func,
+ IntegerField,
+ Q,
+ Value,
+ When,
)
+from django.db.models.functions import Abs, Cast, Coalesce
+from ninja import Router
from django.views.decorators.cache import cache_page
-from ninja import Query, Router
from ninja.decorators import decorate_view
from chord_metadata_service.mohpackets.models import (
@@ -23,16 +30,19 @@
TREATMENT_TYPE,
)
from chord_metadata_service.mohpackets.schemas.discovery import (
- DiscoverySchema,
+ DiagnosisAgeCountSchema,
+ DiscoveryDonorSchema,
+ GenderCountSchema,
+ PatientPerCohortSchema,
+ PrimarySiteCountSchema,
ProgramDiscoverySchema,
-)
-from chord_metadata_service.mohpackets.schemas.filter import (
- DonorFilterSchema,
+ TreatmentTypeCountSchema,
)
"""
Module with overview APIs for the summary page and discovery APIs.
These APIs do not require authorization but return only donor counts.
+It also masks the value if the data is too small.
Author: Son Chau
"""
@@ -41,43 +51,9 @@
overview_router = Router()
discovery_router.add_router("/overview/", overview_router, tags=["overview"])
-
-##########################################
-# #
-# HELPER FUNCTIONS #
-# #
-##########################################
-def count_terms(terms):
- """
- Return a dictionary of counts for every term in a list, used in overview endpoints
- for fields with lists as entries.
- """
- # Unnest list if nested
- if terms and isinstance(terms[0], list):
- terms = sum(terms, [])
-
- # Convert None values to "null"
- terms = ["null" if term is None else term for term in terms]
- return Counter(terms)
-
-
-def count_donors(model: Type[Model], filters=None) -> Dict[str, int]:
- queryset = model.objects.all()
- if model == Donor:
- count_field = "uuid"
- else:
- count_field = "donor_uuid"
-
- if filters is not None:
- queryset = filters.filter(queryset)
-
- item_counts = (
- queryset.values("program_id")
- .annotate(donor_count=Count(count_field, distinct=True))
- .order_by("program_id")
- )
-
- return {f"{item['program_id']}": item["donor_count"] for item in item_counts}
+# To protect privacy, numbers below a certain threshold will be censored, e.g., <5
+SMALL_NUMBER_THRESHOLD = int(settings.AGGREGATE_COUNT_THRESHOLD)
+SMALL_NUMBER_DISPLAY = "<" + str(SMALL_NUMBER_THRESHOLD)
###############################################
@@ -90,21 +66,42 @@ def count_donors(model: Type[Model], filters=None) -> Dict[str, int]:
@discovery_router.get("/programs/", response=List[ProgramDiscoverySchema])
@decorate_view(cache_page(CACHE_DURATION))
def discover_programs(request):
+ """
+ Return all the programs in the database.
+ """
return Program.objects.only("program_id", "metadata")
-@discovery_router.get("/donors/", response=DiscoverySchema)
-def discover_donors(request, filters: DonorFilterSchema = Query(...)):
- donors = count_donors(Donor, filters)
- return DiscoverySchema(donors_by_cohort=donors)
+@discovery_router.get("/donors/", response=List[DiscoveryDonorSchema])
+def discover_donors(request):
+ """
+ Return the number of donors per cohort in the database.
+ Note: This function is identical to `discover_patients_per_cohort`
+ and is here because the frontend ingest uses it. It's probably best
+ to clean up later.
+ """
+ result = (
+ Donor.objects.values("program_id")
+ .annotate(
+ count=Count("uuid"),
+ donors_count=Case(
+ When(
+ count__lt=SMALL_NUMBER_THRESHOLD,
+ then=Value(SMALL_NUMBER_DISPLAY),
+ ),
+ default=Cast(F("count"), output_field=CharField()),
+ ),
+ )
+ .values("program_id", "donors_count")
+ )
+ return result
@discovery_router.get("/sidebar_list/", response=Dict[str, Any])
@decorate_view(cache_page(CACHE_DURATION))
def discover_sidebar_list(request):
"""
- Retrieve the list of available values for all fields (including for
- datasets that the user is not authorized to view)
+ Retrieve the list of drug names and treatment for frontend usage
"""
# Drugs queryable for chemotherapy
chemotherapy_drug_names = list(
@@ -129,8 +126,7 @@ def discover_sidebar_list(request):
.distinct()
)
- # Create a dictionary of results
- results = {
+ result = {
"treatment_types": TREATMENT_TYPE,
"tumour_primary_sites": PRIMARY_SITE,
"chemotherapy_drug_names": chemotherapy_drug_names,
@@ -138,7 +134,7 @@ def discover_sidebar_list(request):
"hormone_therapy_drug_names": hormone_therapy_drug_names,
}
- return results
+ return result
###############################################
@@ -157,111 +153,166 @@ def discover_cohort_count(request):
return {"cohort_count": Program.objects.count()}
-@overview_router.get("/patients_per_cohort/", response=Dict[str, int])
+@overview_router.get("/patients_per_cohort/", response=List[PatientPerCohortSchema])
@decorate_view(cache_page(CACHE_DURATION))
def discover_patients_per_cohort(request):
"""
Return the number of patients per cohort in the database.
"""
- cohorts = Donor.objects.values_list("program_id", flat=True)
- return count_terms(cohorts)
+ result = (
+ Donor.objects.values("program_id")
+ .annotate(count=Count("uuid"))
+ .annotate(
+ patients_count=Case(
+ When(
+ count__lt=SMALL_NUMBER_THRESHOLD,
+ then=Value(SMALL_NUMBER_DISPLAY),
+ ),
+ default=Cast(F("count"), output_field=CharField()),
+ )
+ )
+ .values("program_id", "patients_count")
+ )
+ return result
-@overview_router.get("/individual_count/", response=Dict[str, int])
+@overview_router.get("/individual_count/", response=Dict[str, str])
@decorate_view(cache_page(CACHE_DURATION))
def discover_individual_count(request):
"""
Return the number of individuals in the database.
"""
- return {"individual_count": Donor.objects.count()}
+ donor_count = Donor.objects.count()
+
+ if donor_count == 0:
+ result = "0"
+ elif donor_count < SMALL_NUMBER_THRESHOLD:
+ result = SMALL_NUMBER_DISPLAY
+ else:
+ result = str(donor_count)
+
+ return {"individual_count": result}
-@overview_router.get("/gender_count/", response=Dict[str, int])
+@overview_router.get("/gender_count/", response=List[GenderCountSchema])
@decorate_view(cache_page(CACHE_DURATION))
def discover_gender_count(request):
"""
Return the count for every gender in the database.
"""
- genders = Donor.objects.values_list("gender", flat=True)
- return count_terms(genders)
+ result = (
+ Donor.objects.values("gender")
+ .annotate(count=Count("uuid"))
+ .annotate(
+ gender_count=Case(
+ When(
+ count__lt=SMALL_NUMBER_THRESHOLD,
+ then=Value(SMALL_NUMBER_DISPLAY),
+ ),
+ default=Cast(F("count"), output_field=CharField()),
+ )
+ )
+ .values("gender", "gender_count")
+ )
+ return result
-@overview_router.get("/cancer_type_count/", response=Dict[str, int])
+@overview_router.get("/primary_site_count/", response=List[PrimarySiteCountSchema])
@decorate_view(cache_page(CACHE_DURATION))
-def discover_cancer_type_count(request):
+def discover_primary_site_count(request):
"""
Return the count for every cancer type in the database.
"""
- cancer_types = list(Donor.objects.values_list("primary_site", flat=True))
-
- # Handle missing values as empty arrays
- for i in range(len(cancer_types)):
- if cancer_types[i] is None:
- cancer_types[i] = [None]
-
- return count_terms(cancer_types)
+ result = (
+ Donor.objects.annotate(
+ primary_site_name=Func(
+ Coalesce(F("primary_site"), Value(["None"])), function="unnest"
+ )
+ )
+ .values("primary_site_name")
+ .annotate(count=Count("uuid"))
+ .annotate(
+ primary_site_count=Case(
+ When(
+ count__lt=SMALL_NUMBER_THRESHOLD,
+ then=Value(SMALL_NUMBER_DISPLAY),
+ ),
+ default=Cast(F("count"), output_field=CharField()),
+ )
+ )
+ .values("primary_site_name", "primary_site_count")
+ )
+ return result
-@overview_router.get("/treatment_type_count/", response=Dict[str, int])
+@overview_router.get("/treatment_type_count/", response=List[TreatmentTypeCountSchema])
@decorate_view(cache_page(CACHE_DURATION))
def discover_treatment_type_count(request):
"""
Return the count for every treatment type in the database.
"""
- treatment_types = list(Treatment.objects.values_list("treatment_type", flat=True))
- # Handle missing values as empty arrays
- for i in range(len(treatment_types)):
- if treatment_types[i] is None:
- treatment_types[i] = [None]
+ result = (
+ Treatment.objects.annotate(
+ treatment_type_name=Func(
+ Coalesce(F("treatment_type"), Value(["None"])), function="unnest"
+ )
+ )
+ .values("treatment_type_name")
+ .annotate(count=Count("uuid"))
+ .annotate(
+ treatment_type_count=Case(
+ When(
+ count__lt=SMALL_NUMBER_THRESHOLD,
+ then=Value(SMALL_NUMBER_DISPLAY),
+ ),
+ default=Cast(F("count"), output_field=CharField()),
+ )
+ )
+ .values("treatment_type_name", "treatment_type_count")
+ )
- return count_terms(treatment_types)
+ return result
-@overview_router.get("/diagnosis_age_count/", response=Dict[str, int])
+@overview_router.get("/diagnosis_age_count/", response=List[DiagnosisAgeCountSchema])
@decorate_view(cache_page(CACHE_DURATION))
def discover_diagnosis_age_count(request):
"""
Return the count for age of diagnosis by calculating the date of birth interval.
"""
- months_in_year = 12
-
- age_counts = {
- "null": 0,
- "0-19": 0,
- "20-29": 0,
- "30-39": 0,
- "40-49": 0,
- "50-59": 0,
- "60-69": 0,
- "70-79": 0,
- "80+": 0,
- }
+ result = (
+ Donor.objects.annotate(
+ abs_month_interval=Abs(
+ Cast("date_of_birth__month_interval", output_field=IntegerField())
+ ),
+ age_at_diagnosis=Case(
+ When(Q(date_of_birth__isnull=True), then=Value("null")),
+ When(abs_month_interval__lt=240, then=Value("0-19")),
+ When(abs_month_interval__lt=360, then=Value("20-29")),
+ When(abs_month_interval__lt=480, then=Value("30-39")),
+ When(abs_month_interval__lt=600, then=Value("40-49")),
+ When(abs_month_interval__lt=720, then=Value("50-59")),
+ When(abs_month_interval__lt=840, then=Value("60-69")),
+ When(abs_month_interval__lt=960, then=Value("70-79")),
+ default=Value("80+"),
+ output_field=CharField(),
+ ),
+ )
+ .values(
+ "age_at_diagnosis",
+ )
+ .annotate(count=Count("uuid"))
+ .annotate(
+ age_count=Case(
+ When(
+ count__lt=SMALL_NUMBER_THRESHOLD,
+ then=Value(SMALL_NUMBER_DISPLAY),
+ ),
+ default=Cast("count", output_field=CharField()),
+ )
+ )
+ .values("age_at_diagnosis", "age_count")
+ )
- donors = Donor.objects.values("date_of_birth")
-
- for donor in donors:
- age = -1
- if donor["date_of_birth"] and donor["date_of_birth"].get("month_interval"):
- age = abs(donor["date_of_birth"]["month_interval"]) // months_in_year
-
- if age < 0:
- age_counts["null"] += 1
- elif age <= 19:
- age_counts["0-19"] += 1
- elif age <= 29:
- age_counts["20-29"] += 1
- elif age <= 39:
- age_counts["30-39"] += 1
- elif age <= 49:
- age_counts["40-49"] += 1
- elif age <= 59:
- age_counts["50-59"] += 1
- elif age <= 69:
- age_counts["60-69"] += 1
- elif age <= 79:
- age_counts["70-79"] += 1
- else:
- age_counts["80+"] += 1
-
- return age_counts
+ return result
diff --git a/chord_metadata_service/mohpackets/docs/schema.json b/chord_metadata_service/mohpackets/docs/schema.json
index aefdb3bf4..69649d8ef 100644
--- a/chord_metadata_service/mohpackets/docs/schema.json
+++ b/chord_metadata_service/mohpackets/docs/schema.json
@@ -3,7 +3,7 @@
"info": {
"title": "MoH Service API",
"version": "4.2.1",
- "description": "This is the RESTful API for the MoH Service. Based on https://raw.githubusercontent.com/CanDIG/katsu/a5656164c539273637fe7ae7709f41d61bf86ced/chord_metadata_service/mohpackets/docs/schema.json"
+ "description": "This is the RESTful API for the MoH Service."
},
"paths": {
"/v2/service-info": {
@@ -3943,6 +3943,7 @@
}
}
},
+ "description": "Return all the programs in the database.",
"tags": [
"discovery"
]
@@ -3952,259 +3953,6 @@
"get": {
"operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_donors",
"summary": "Discover Donors",
- "parameters": [
- {
- "in": "query",
- "name": "submitter_donor_id",
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Submitter Donor Id"
- },
- "required": false
- },
- {
- "in": "query",
- "name": "program_id",
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Program Id"
- },
- "required": false
- },
- {
- "in": "query",
- "name": "gender",
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "q": "gender__icontains",
- "title": "Gender"
- },
- "required": false
- },
- {
- "in": "query",
- "name": "sex_at_birth",
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Sex At Birth"
- },
- "required": false
- },
- {
- "in": "query",
- "name": "is_deceased",
- "schema": {
- "anyOf": [
- {
- "type": "boolean"
- },
- {
- "type": "null"
- }
- ],
- "title": "Is Deceased"
- },
- "required": false
- },
- {
- "in": "query",
- "name": "lost_to_followup_after_clinical_event_identifier",
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Lost To Followup After Clinical Event Identifier"
- },
- "required": false
- },
- {
- "in": "query",
- "name": "lost_to_followup_reason",
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Lost To Followup Reason"
- },
- "required": false
- },
- {
- "in": "query",
- "name": "cause_of_death",
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Cause Of Death"
- },
- "required": false
- },
- {
- "in": "query",
- "name": "primary_site",
- "schema": {
- "items": {
- "type": "string"
- },
- "q": "primary_site__overlap",
- "title": "Primary Site",
- "type": "array"
- },
- "required": false
- }
- ],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/specimen/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_specimens",
- "summary": "Discover Specimens",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/sample_registrations/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_sample_registrations",
- "summary": "Discover Sample Registrations",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/primary_diagnoses/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_primary_diagnoses",
- "summary": "Discover Primary Diagnoses",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/treatments/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_treatments",
- "summary": "Discover Treatments",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/chemotherapies/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_chemotherapies",
- "summary": "Discover Chemotherapies",
"parameters": [],
"responses": {
"200": {
@@ -4212,188 +3960,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/hormone_therapies/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_hormone_therapies",
- "summary": "Discover Hormone Therapies",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/radiations/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_radiations",
- "summary": "Discover Radiations",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/immunotherapies/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_immunotherapies",
- "summary": "Discover Immunotherapies",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/surgeries/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_surgeries",
- "summary": "Discover Surgeries",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/follow_ups/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_follow_ups",
- "summary": "Discover Follow Ups",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/biomarkers/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_biomarkers",
- "summary": "Discover Biomarkers",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/comorbidities/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_comorbidities",
- "summary": "Discover Comorbidities",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
- }
- }
- }
- }
- },
- "tags": [
- "discovery"
- ]
- }
- },
- "/v2/discovery/exposures/": {
- "get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_exposures",
- "summary": "Discover Exposures",
- "parameters": [],
- "responses": {
- "200": {
- "description": "OK",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/DiscoverySchema"
+ "items": {
+ "$ref": "#/components/schemas/DiscoveryDonorSchema"
+ },
+ "title": "Response",
+ "type": "array"
}
}
}
}
},
+ "description": "Return the number of donors per cohort in the database.",
"tags": [
"discovery"
]
@@ -4461,11 +4038,11 @@
"content": {
"application/json": {
"schema": {
- "additionalProperties": {
- "type": "integer"
+ "items": {
+ "$ref": "#/components/schemas/PatientPerCohortSchema"
},
"title": "Response",
- "type": "object"
+ "type": "array"
}
}
}
@@ -4489,7 +4066,7 @@
"application/json": {
"schema": {
"additionalProperties": {
- "type": "integer"
+ "type": "string"
},
"title": "Response",
"type": "object"
@@ -4515,11 +4092,11 @@
"content": {
"application/json": {
"schema": {
- "additionalProperties": {
- "type": "integer"
+ "items": {
+ "$ref": "#/components/schemas/GenderCountSchema"
},
"title": "Response",
- "type": "object"
+ "type": "array"
}
}
}
@@ -4531,10 +4108,10 @@
]
}
},
- "/v2/discovery/overview/cancer_type_count/": {
+ "/v2/discovery/overview/primary_site_count/": {
"get": {
- "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_cancer_type_count",
- "summary": "Discover Cancer Type Count",
+ "operationId": "chord_metadata_service_mohpackets_apis_discovery_discover_primary_site_count",
+ "summary": "Discover Primary Site Count",
"parameters": [],
"responses": {
"200": {
@@ -4542,11 +4119,11 @@
"content": {
"application/json": {
"schema": {
- "additionalProperties": {
- "type": "integer"
+ "items": {
+ "$ref": "#/components/schemas/PrimarySiteCountSchema"
},
"title": "Response",
- "type": "object"
+ "type": "array"
}
}
}
@@ -4569,11 +4146,11 @@
"content": {
"application/json": {
"schema": {
- "additionalProperties": {
- "type": "integer"
+ "items": {
+ "$ref": "#/components/schemas/TreatmentTypeCountSchema"
},
"title": "Response",
- "type": "object"
+ "type": "array"
}
}
}
@@ -4596,11 +4173,11 @@
"content": {
"application/json": {
"schema": {
- "additionalProperties": {
- "type": "integer"
+ "items": {
+ "$ref": "#/components/schemas/DiagnosisAgeCountSchema"
},
"title": "Response",
- "type": "object"
+ "type": "array"
}
}
}
@@ -14219,20 +13796,119 @@
"title": "ProgramDiscoverySchema",
"type": "object"
},
- "DiscoverySchema": {
+ "DiscoveryDonorSchema": {
"properties": {
- "donors_by_cohort": {
- "additionalProperties": {
- "type": "integer"
- },
- "title": "Donors By Cohort",
- "type": "object"
+ "program_id": {
+ "title": "Program Id",
+ "type": "string"
+ },
+ "donors_count": {
+ "title": "Donors Count",
+ "type": "string"
+ }
+ },
+ "required": [
+ "program_id",
+ "donors_count"
+ ],
+ "title": "DiscoveryDonorSchema",
+ "type": "object"
+ },
+ "PatientPerCohortSchema": {
+ "properties": {
+ "program_id": {
+ "title": "Program Id",
+ "type": "string"
+ },
+ "patients_count": {
+ "title": "Patients Count",
+ "type": "string"
+ }
+ },
+ "required": [
+ "program_id",
+ "patients_count"
+ ],
+ "title": "PatientPerCohortSchema",
+ "type": "object"
+ },
+ "GenderCountSchema": {
+ "properties": {
+ "gender": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Gender"
+ },
+ "gender_count": {
+ "title": "Gender Count",
+ "type": "string"
+ }
+ },
+ "required": [
+ "gender",
+ "gender_count"
+ ],
+ "title": "GenderCountSchema",
+ "type": "object"
+ },
+ "PrimarySiteCountSchema": {
+ "properties": {
+ "primary_site_name": {
+ "title": "Primary Site Name",
+ "type": "string"
+ },
+ "primary_site_count": {
+ "title": "Primary Site Count",
+ "type": "string"
+ }
+ },
+ "required": [
+ "primary_site_name",
+ "primary_site_count"
+ ],
+ "title": "PrimarySiteCountSchema",
+ "type": "object"
+ },
+ "TreatmentTypeCountSchema": {
+ "properties": {
+ "treatment_type_name": {
+ "title": "Treatment Type Name",
+ "type": "string"
+ },
+ "treatment_type_count": {
+ "title": "Treatment Type Count",
+ "type": "string"
+ }
+ },
+ "required": [
+ "treatment_type_name",
+ "treatment_type_count"
+ ],
+ "title": "TreatmentTypeCountSchema",
+ "type": "object"
+ },
+ "DiagnosisAgeCountSchema": {
+ "properties": {
+ "age_at_diagnosis": {
+ "title": "Age At Diagnosis",
+ "type": "string"
+ },
+ "age_count": {
+ "title": "Age Count",
+ "type": "string"
}
},
"required": [
- "donors_by_cohort"
+ "age_at_diagnosis",
+ "age_count"
],
- "title": "DiscoverySchema",
+ "title": "DiagnosisAgeCountSchema",
"type": "object"
},
"DonorExplorerFilterSchema": {
diff --git a/chord_metadata_service/mohpackets/docs/schema.md b/chord_metadata_service/mohpackets/docs/schema.md
index 6750a3635..3bda3c72d 100644
--- a/chord_metadata_service/mohpackets/docs/schema.md
+++ b/chord_metadata_service/mohpackets/docs/schema.md
@@ -1,7 +1,7 @@
MoH Service API v4.2.1
-This is the RESTful API for the MoH Service. Based on https://raw.githubusercontent.com/CanDIG/katsu/a5656164c539273637fe7ae7709f41d61bf86ced/chord_metadata_service/mohpackets/docs/schema.json
+This is the RESTful API for the MoH Service.
Base URLs:
@@ -1440,6 +1440,8 @@ Base URLs:
*Discover Programs*
+Return all the programs in the database.
+
> Example responses
> 200 Response
@@ -1461,304 +1463,19 @@ Base URLs:
*Discover Donors*
-
-
-|Name|In|Type|Required|Description|
-|---|---|---|---|---|
-|submitter_donor_id|query|any|false|none|
-|program_id|query|any|false|none|
-|gender|query|any|false|none|
-|sex_at_birth|query|any|false|none|
-|is_deceased|query|any|false|none|
-|lost_to_followup_after_clinical_event_identifier|query|any|false|none|
-|lost_to_followup_reason|query|any|false|none|
-|cause_of_death|query|any|false|none|
-|primary_site|query|array[string]|false|none|
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_specimens
-
-
-
-`GET /v2/discovery/specimen/`
-
-*Discover Specimens*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_sample_registrations
-
-
-
-`GET /v2/discovery/sample_registrations/`
-
-*Discover Sample Registrations*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_primary_diagnoses
-
-
-
-`GET /v2/discovery/primary_diagnoses/`
-
-*Discover Primary Diagnoses*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_treatments
-
-
-
-`GET /v2/discovery/treatments/`
-
-*Discover Treatments*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_chemotherapies
-
-
-
-`GET /v2/discovery/chemotherapies/`
-
-*Discover Chemotherapies*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_hormone_therapies
-
-
-
-`GET /v2/discovery/hormone_therapies/`
-
-*Discover Hormone Therapies*
+Return the number of donors per cohort in the database.
> Example responses
> 200 Response
```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_radiations
-
-
-
-`GET /v2/discovery/radiations/`
-
-*Discover Radiations*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_immunotherapies
-
-
-
-`GET /v2/discovery/immunotherapies/`
-
-*Discover Immunotherapies*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_surgeries
-
-
-
-`GET /v2/discovery/surgeries/`
-
-*Discover Surgeries*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_follow_ups
-
-
-
-`GET /v2/discovery/follow_ups/`
-
-*Discover Follow Ups*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_biomarkers
-
-
-
-`GET /v2/discovery/biomarkers/`
-
-*Discover Biomarkers*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_comorbidities
-
-
-
-`GET /v2/discovery/comorbidities/`
-
-*Discover Comorbidities*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
-}
-```
-
-## chord_metadata_service_mohpackets_apis_discovery_discover_exposures
-
-
-
-`GET /v2/discovery/exposures/`
-
-*Discover Exposures*
-
-> Example responses
-
-> 200 Response
-
-```json
-{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
+[
+ {
+ "program_id": "string",
+ "donors_count": "string"
}
-}
+]
```
## chord_metadata_service_mohpackets_apis_discovery_discover_sidebar_list
@@ -1818,10 +1535,12 @@ Return the number of patients per cohort in the database.
> 200 Response
```json
-{
- "property1": 0,
- "property2": 0
-}
+[
+ {
+ "program_id": "string",
+ "patients_count": "string"
+ }
+]
```
## chord_metadata_service_mohpackets_apis_discovery_discover_individual_count
@@ -1840,8 +1559,8 @@ Return the number of individuals in the database.
```json
{
- "property1": 0,
- "property2": 0
+ "property1": "string",
+ "property2": "string"
}
```
@@ -1860,19 +1579,21 @@ Return the count for every gender in the database.
> 200 Response
```json
-{
- "property1": 0,
- "property2": 0
-}
+[
+ {
+ "gender": "string",
+ "gender_count": "string"
+ }
+]
```
-## chord_metadata_service_mohpackets_apis_discovery_discover_cancer_type_count
+## chord_metadata_service_mohpackets_apis_discovery_discover_primary_site_count
-
+
-`GET /v2/discovery/overview/cancer_type_count/`
+`GET /v2/discovery/overview/primary_site_count/`
-*Discover Cancer Type Count*
+*Discover Primary Site Count*
Return the count for every cancer type in the database.
@@ -1881,10 +1602,12 @@ Return the count for every cancer type in the database.
> 200 Response
```json
-{
- "property1": 0,
- "property2": 0
-}
+[
+ {
+ "primary_site_name": "string",
+ "primary_site_count": "string"
+ }
+]
```
## chord_metadata_service_mohpackets_apis_discovery_discover_treatment_type_count
@@ -1902,10 +1625,12 @@ Return the count for every treatment type in the database.
> 200 Response
```json
-{
- "property1": 0,
- "property2": 0
-}
+[
+ {
+ "treatment_type_name": "string",
+ "treatment_type_count": "string"
+ }
+]
```
## chord_metadata_service_mohpackets_apis_discovery_discover_diagnosis_age_count
@@ -1923,10 +1648,12 @@ Return the count for age of diagnosis by calculating the date of birth interval.
> 200 Response
```json
-{
- "property1": 0,
- "property2": 0
-}
+[
+ {
+ "age_at_diagnosis": "string",
+ "age_count": "string"
+ }
+]
```
explorer
@@ -18627,31 +18354,166 @@ ProgramDiscoverySchema
|program_id|string|true|none|none|
|metadata|any|true|none|none|
-DiscoverySchema
+DiscoveryDonorSchema
-
-
-
-
+
+
+
+
```json
{
- "donors_by_cohort": {
- "property1": 0,
- "property2": 0
- }
+ "program_id": "string",
+ "donors_count": "string"
+}
+
+```
+
+DiscoveryDonorSchema
+
+### Properties
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|program_id|string|true|none|none|
+|donors_count|string|true|none|none|
+
+PatientPerCohortSchema
+
+
+
+
+
+
+```json
+{
+ "program_id": "string",
+ "patients_count": "string"
+}
+
+```
+
+PatientPerCohortSchema
+
+### Properties
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|program_id|string|true|none|none|
+|patients_count|string|true|none|none|
+
+GenderCountSchema
+
+
+
+
+
+
+```json
+{
+ "gender": "string",
+ "gender_count": "string"
+}
+
+```
+
+GenderCountSchema
+
+### Properties
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|gender|any|true|none|none|
+
+anyOf
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|» *anonymous*|string|false|none|none|
+
+or
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|» *anonymous*|null|false|none|none|
+
+continued
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|gender_count|string|true|none|none|
+
+PrimarySiteCountSchema
+
+
+
+
+
+
+```json
+{
+ "primary_site_name": "string",
+ "primary_site_count": "string"
+}
+
+```
+
+PrimarySiteCountSchema
+
+### Properties
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|primary_site_name|string|true|none|none|
+|primary_site_count|string|true|none|none|
+
+TreatmentTypeCountSchema
+
+
+
+
+
+
+```json
+{
+ "treatment_type_name": "string",
+ "treatment_type_count": "string"
+}
+
+```
+
+TreatmentTypeCountSchema
+
+### Properties
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|treatment_type_name|string|true|none|none|
+|treatment_type_count|string|true|none|none|
+
+DiagnosisAgeCountSchema
+
+
+
+
+
+
+```json
+{
+ "age_at_diagnosis": "string",
+ "age_count": "string"
}
```
-DiscoverySchema
+DiagnosisAgeCountSchema
### Properties
|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
-|donors_by_cohort|object|true|none|none|
-|» **additionalProperties**|integer|false|none|none|
+|age_at_diagnosis|string|true|none|none|
+|age_count|string|true|none|none|
DonorExplorerFilterSchema
diff --git a/chord_metadata_service/mohpackets/docs/schema.yml b/chord_metadata_service/mohpackets/docs/schema.yml
index 8ca7c38c4..dd3d86032 100644
--- a/chord_metadata_service/mohpackets/docs/schema.yml
+++ b/chord_metadata_service/mohpackets/docs/schema.yml
@@ -628,16 +628,31 @@ components:
- month_interval
title: DateInterval
type: object
- DiscoverySchema:
+ DiagnosisAgeCountSchema:
properties:
- donors_by_cohort:
- additionalProperties:
- type: integer
- title: Donors By Cohort
- type: object
+ age_at_diagnosis:
+ title: Age At Diagnosis
+ type: string
+ age_count:
+ title: Age Count
+ type: string
required:
- - donors_by_cohort
- title: DiscoverySchema
+ - age_at_diagnosis
+ - age_count
+ title: DiagnosisAgeCountSchema
+ type: object
+ DiscoveryDonorSchema:
+ properties:
+ donors_count:
+ title: Donors Count
+ type: string
+ program_id:
+ title: Program Id
+ type: string
+ required:
+ - program_id
+ - donors_count
+ title: DiscoveryDonorSchema
type: object
DiseaseStatusFollowupEnum:
enum:
@@ -1372,6 +1387,21 @@ components:
- submitter_donor_id
title: FollowUpModelSchema
type: object
+ GenderCountSchema:
+ properties:
+ gender:
+ anyOf:
+ - type: string
+ - type: 'null'
+ title: Gender
+ gender_count:
+ title: Gender Count
+ type: string
+ required:
+ - gender
+ - gender_count
+ title: GenderCountSchema
+ type: object
GenderEnum:
enum:
- Man
@@ -2994,6 +3024,19 @@ components:
- previous_page
title: PagedTreatmentModelSchema
type: object
+ PatientPerCohortSchema:
+ properties:
+ patients_count:
+ title: Patients Count
+ type: string
+ program_id:
+ title: Program Id
+ type: string
+ required:
+ - program_id
+ - patients_count
+ title: PatientPerCohortSchema
+ type: object
PercentCellsRangeEnum:
enum:
- 0-19%
@@ -3240,6 +3283,19 @@ components:
- submitter_donor_id
title: PrimaryDiagnosisModelSchema
type: object
+ PrimarySiteCountSchema:
+ properties:
+ primary_site_count:
+ title: Primary Site Count
+ type: string
+ primary_site_name:
+ title: Primary Site Name
+ type: string
+ required:
+ - primary_site_name
+ - primary_site_count
+ title: PrimarySiteCountSchema
+ type: object
PrimarySiteEnum:
enum:
- Accessory sinuses
@@ -5228,6 +5284,19 @@ components:
- Unknown
title: TreatmentStatusEnum
type: string
+ TreatmentTypeCountSchema:
+ properties:
+ treatment_type_count:
+ title: Treatment Type Count
+ type: string
+ treatment_type_name:
+ title: Treatment Type Name
+ type: string
+ required:
+ - treatment_type_name
+ - treatment_type_count
+ title: TreatmentTypeCountSchema
+ type: object
TreatmentTypeEnum:
enum:
- Bone marrow transplant
@@ -5344,7 +5413,7 @@ components:
name: X-Service-Token
type: apiKey
info:
- description: This is the RESTful API for the MoH Service. Based on https://raw.githubusercontent.com/CanDIG/katsu/a5656164c539273637fe7ae7709f41d61bf86ced/chord_metadata_service/mohpackets/docs/schema.json
+ description: This is the RESTful API for the MoH Service.
title: MoH Service API
version: 4.2.1
openapi: 3.1.0
@@ -7201,210 +7270,24 @@ paths:
summary: List Treatments
tags:
- authorized
- /v2/discovery/biomarkers/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_biomarkers
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Biomarkers
- tags:
- - discovery
- /v2/discovery/chemotherapies/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_chemotherapies
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Chemotherapies
- tags:
- - discovery
- /v2/discovery/comorbidities/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_comorbidities
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Comorbidities
- tags:
- - discovery
/v2/discovery/donors/:
get:
+ description: Return the number of donors per cohort in the database.
operationId: chord_metadata_service_mohpackets_apis_discovery_discover_donors
- parameters:
- - in: query
- name: submitter_donor_id
- required: false
- schema:
- anyOf:
- - type: string
- - type: 'null'
- title: Submitter Donor Id
- - in: query
- name: program_id
- required: false
- schema:
- anyOf:
- - type: string
- - type: 'null'
- title: Program Id
- - in: query
- name: gender
- required: false
- schema:
- anyOf:
- - type: string
- - type: 'null'
- q: gender__icontains
- title: Gender
- - in: query
- name: sex_at_birth
- required: false
- schema:
- anyOf:
- - type: string
- - type: 'null'
- title: Sex At Birth
- - in: query
- name: is_deceased
- required: false
- schema:
- anyOf:
- - type: boolean
- - type: 'null'
- title: Is Deceased
- - in: query
- name: lost_to_followup_after_clinical_event_identifier
- required: false
- schema:
- anyOf:
- - type: string
- - type: 'null'
- title: Lost To Followup After Clinical Event Identifier
- - in: query
- name: lost_to_followup_reason
- required: false
- schema:
- anyOf:
- - type: string
- - type: 'null'
- title: Lost To Followup Reason
- - in: query
- name: cause_of_death
- required: false
- schema:
- anyOf:
- - type: string
- - type: 'null'
- title: Cause Of Death
- - in: query
- name: primary_site
- required: false
- schema:
- items:
- type: string
- q: primary_site__overlap
- title: Primary Site
- type: array
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Donors
- tags:
- - discovery
- /v2/discovery/exposures/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_exposures
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Exposures
- tags:
- - discovery
- /v2/discovery/follow_ups/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_follow_ups
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Follow Ups
- tags:
- - discovery
- /v2/discovery/hormone_therapies/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_hormone_therapies
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Hormone Therapies
- tags:
- - discovery
- /v2/discovery/immunotherapies/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_immunotherapies
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Immunotherapies
- tags:
- - discovery
- /v2/discovery/overview/cancer_type_count/:
- get:
- description: Return the count for every cancer type in the database.
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_cancer_type_count
parameters: []
responses:
'200':
content:
application/json:
schema:
- additionalProperties:
- type: integer
+ items:
+ $ref: '#/components/schemas/DiscoveryDonorSchema'
title: Response
- type: object
+ type: array
description: OK
- summary: Discover Cancer Type Count
+ summary: Discover Donors
tags:
- - overview
+ - discovery
/v2/discovery/overview/cohort_count/:
get:
description: Return the number of cohorts in the database.
@@ -7434,10 +7317,10 @@ paths:
content:
application/json:
schema:
- additionalProperties:
- type: integer
+ items:
+ $ref: '#/components/schemas/DiagnosisAgeCountSchema'
title: Response
- type: object
+ type: array
description: OK
summary: Discover Diagnosis Age Count
tags:
@@ -7452,10 +7335,10 @@ paths:
content:
application/json:
schema:
- additionalProperties:
- type: integer
+ items:
+ $ref: '#/components/schemas/GenderCountSchema'
title: Response
- type: object
+ type: array
description: OK
summary: Discover Gender Count
tags:
@@ -7471,7 +7354,7 @@ paths:
application/json:
schema:
additionalProperties:
- type: integer
+ type: string
title: Response
type: object
description: OK
@@ -7488,48 +7371,53 @@ paths:
content:
application/json:
schema:
- additionalProperties:
- type: integer
+ items:
+ $ref: '#/components/schemas/PatientPerCohortSchema'
title: Response
- type: object
+ type: array
description: OK
summary: Discover Patients Per Cohort
tags:
- overview
- /v2/discovery/overview/treatment_type_count/:
+ /v2/discovery/overview/primary_site_count/:
get:
- description: Return the count for every treatment type in the database.
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_treatment_type_count
+ description: Return the count for every cancer type in the database.
+ operationId: chord_metadata_service_mohpackets_apis_discovery_discover_primary_site_count
parameters: []
responses:
'200':
content:
application/json:
schema:
- additionalProperties:
- type: integer
+ items:
+ $ref: '#/components/schemas/PrimarySiteCountSchema'
title: Response
- type: object
+ type: array
description: OK
- summary: Discover Treatment Type Count
+ summary: Discover Primary Site Count
tags:
- overview
- /v2/discovery/primary_diagnoses/:
+ /v2/discovery/overview/treatment_type_count/:
get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_primary_diagnoses
+ description: Return the count for every treatment type in the database.
+ operationId: chord_metadata_service_mohpackets_apis_discovery_discover_treatment_type_count
parameters: []
responses:
'200':
content:
application/json:
schema:
- $ref: '#/components/schemas/DiscoverySchema'
+ items:
+ $ref: '#/components/schemas/TreatmentTypeCountSchema'
+ title: Response
+ type: array
description: OK
- summary: Discover Primary Diagnoses
+ summary: Discover Treatment Type Count
tags:
- - discovery
+ - overview
/v2/discovery/programs/:
get:
+ description: Return all the programs in the database.
operationId: chord_metadata_service_mohpackets_apis_discovery_discover_programs
parameters: []
responses:
@@ -7545,34 +7433,6 @@ paths:
summary: Discover Programs
tags:
- discovery
- /v2/discovery/radiations/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_radiations
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Radiations
- tags:
- - discovery
- /v2/discovery/sample_registrations/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_sample_registrations
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Sample Registrations
- tags:
- - discovery
/v2/discovery/sidebar_list/:
get:
description: 'Retrieve the list of available values for all fields (including
@@ -7592,48 +7452,6 @@ paths:
summary: Discover Sidebar List
tags:
- discovery
- /v2/discovery/specimen/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_specimens
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Specimens
- tags:
- - discovery
- /v2/discovery/surgeries/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_surgeries
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Surgeries
- tags:
- - discovery
- /v2/discovery/treatments/:
- get:
- operationId: chord_metadata_service_mohpackets_apis_discovery_discover_treatments
- parameters: []
- responses:
- '200':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DiscoverySchema'
- description: OK
- summary: Discover Treatments
- tags:
- - discovery
/v2/explorer/donors/:
get:
operationId: chord_metadata_service_mohpackets_apis_explorer_explorer_donor
diff --git a/chord_metadata_service/mohpackets/schemas/discovery.py b/chord_metadata_service/mohpackets/schemas/discovery.py
index 9076b8b43..0fa9424eb 100644
--- a/chord_metadata_service/mohpackets/schemas/discovery.py
+++ b/chord_metadata_service/mohpackets/schemas/discovery.py
@@ -1,6 +1,6 @@
-from typing import Dict
-from ninja import Schema
+from typing import Optional
+from ninja import Schema
"""
Module with schema used for discovery response
@@ -14,5 +14,35 @@ class ProgramDiscoverySchema(Schema):
metadata: object
-class DiscoverySchema(Schema):
- donors_by_cohort: Dict[str, int]
+# class DiscoverySchema(Schema):
+# donors_by_cohort: Dict[str, int]
+
+
+class DiscoveryDonorSchema(Schema):
+ program_id: str
+ donors_count: str
+
+
+class PatientPerCohortSchema(Schema):
+ program_id: str
+ patients_count: str
+
+
+class GenderCountSchema(Schema):
+ gender: Optional[str]
+ gender_count: str
+
+
+class PrimarySiteCountSchema(Schema):
+ primary_site_name: str
+ primary_site_count: str
+
+
+class TreatmentTypeCountSchema(Schema):
+ treatment_type_name: str
+ treatment_type_count: str
+
+
+class DiagnosisAgeCountSchema(Schema):
+ age_at_diagnosis: str
+ age_count: str
diff --git a/chord_metadata_service/mohpackets/tests/endpoints/factories.py b/chord_metadata_service/mohpackets/tests/endpoints/factories.py
index 96b9f359a..dd44d7234 100644
--- a/chord_metadata_service/mohpackets/tests/endpoints/factories.py
+++ b/chord_metadata_service/mohpackets/tests/endpoints/factories.py
@@ -39,6 +39,8 @@
Note:
These factories use the Factory Boy library (https://factoryboy.readthedocs.io/)
to generate test data.
+ Some business logic is not strictly enforced. For example,
+ date_of_birth could have mismatched month_interval and day_interval.
Author: Son Chau
"""
@@ -73,16 +75,20 @@ class Meta:
),
no_declaration=None,
)
- date_of_birth = {
- "month_interval": random.randint(0, 100),
- "day_interval": random.randint(0, 300),
- }
+ date_of_birth = factory.LazyFunction(
+ lambda: {
+ "month_interval": random.randint(0, 1000),
+ "day_interval": random.randint(0, 3000),
+ }
+ )
date_of_death = factory.Maybe(
"is_deceased",
- yes_declaration={
- "month_interval": random.randint(0, 100),
- "day_interval": random.randint(0, 300),
- },
+ yes_declaration=factory.LazyFunction(
+ lambda: {
+ "month_interval": random.randint(0, 1000),
+ "day_interval": random.randint(0, 3000),
+ }
+ ),
no_declaration=None,
)
primary_site = factory.Faker(
@@ -103,10 +109,12 @@ class Meta:
# Default values
submitter_primary_diagnosis_id = factory.Sequence(lambda n: "DIAG_%d" % n)
- date_of_diagnosis = {
- "month_interval": random.randint(0, 100),
- "day_interval": random.randint(0, 300),
- }
+ date_of_diagnosis = factory.LazyFunction(
+ lambda: {
+ "month_interval": random.randint(0, 1000),
+ "day_interval": random.randint(0, 3000),
+ }
+ )
cancer_type_code = factory.Faker("uuid4")
basis_of_diagnosis = factory.Faker(
"random_element", elements=PERM_VAL.BASIS_OF_DIAGNOSIS
@@ -147,8 +155,8 @@ def set_clinical_event_identifier(self, create, extracted, **kwargs):
PERM_VAL.LOST_TO_FOLLOWUP_REASON
)
donor.date_alive_after_lost_to_followup = {
- "month_interval": random.randint(0, 100),
- "day_interval": random.randint(0, 300),
+ "month_interval": random.randint(0, 1000),
+ "day_interval": random.randint(0, 3000),
}
donor.save()
diff --git a/chord_metadata_service/mohpackets/tests/endpoints/test_overview.py b/chord_metadata_service/mohpackets/tests/endpoints/test_overview.py
new file mode 100644
index 000000000..ed01ebc13
--- /dev/null
+++ b/chord_metadata_service/mohpackets/tests/endpoints/test_overview.py
@@ -0,0 +1,203 @@
+from http import HTTPStatus
+
+import chord_metadata_service.mohpackets.permissible_values as PERM_VAL
+from chord_metadata_service.mohpackets.models import Donor, Treatment
+from chord_metadata_service.mohpackets.tests.endpoints.base import BaseTestCase
+from chord_metadata_service.mohpackets.tests.endpoints.factories import (
+ DonorFactory,
+ TreatmentFactory,
+)
+
+"""
+ This module contains API tests related to overview endpoints.
+
+ It includes tests for:
+ - Displaying actual number if data >5
+ - Censoring if data <5
+
+ Author: Son Chau
+"""
+
+
+class OverviewTestCase(BaseTestCase):
+ def setUp(self):
+ super().setUp()
+ self.patients_per_cohort_url = "/v2/discovery/overview/patients_per_cohort/"
+ self.individual_count_url = "/v2/discovery/overview/individual_count/"
+ self.gender_count_url = "/v2/discovery/overview/gender_count/"
+ self.primary_site_count_url = "/v2/discovery/overview/primary_site_count/"
+ self.treatment_type_count_url = "/v2/discovery/overview/treatment_type_count/"
+ self.diagnosis_age_count_url = "/v2/discovery/overview/diagnosis_age_count/"
+ self.discover_donors_url = "/v2/discovery/donors/"
+ # The default dataset are <5 so we need to add 5 more donors
+ # and 5 more treatments to display data >5
+ self.gender_man = PERM_VAL.GENDER[0] # Man
+ self.primary_site_sinus = PERM_VAL.PRIMARY_SITE[0] # Accessory sinuses
+ self.treatment_type_bone = PERM_VAL.TREATMENT_TYPE[0] # Bone marrow transplant
+ self.donors.extend(
+ DonorFactory.create_batch(
+ 5,
+ program_id=self.programs[0],
+ gender=self.gender_man,
+ primary_site=[self.primary_site_sinus],
+ date_of_birth={"month_interval": 840}, # 70-79 age
+ )
+ )
+ self.treatments.extend(
+ TreatmentFactory.create_batch(
+ 5,
+ primary_diagnosis_uuid=self.primary_diagnoses[0],
+ treatment_type=[self.treatment_type_bone],
+ )
+ )
+
+ def test_individual_count_api_no_censoring(self):
+ """
+ Verify individual_count endpoint does not censor number >5.
+
+ Testing Strategy:
+ - Total number of donors is 9
+ - Send a request to individual_count endpoint
+ - Ensure that the response show the full number
+ """
+ response = self.client.get(self.individual_count_url)
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+ individual_count_value = response.json()["individual_count"]
+ self.assertGreaterEqual(len(self.donors), 5)
+ self.assertEqual(individual_count_value, str(len(self.donors)))
+
+ def test_individual_count_api_censoring(self):
+ """
+ Verify individual_count endpoint censoring for small datasets.
+
+ Testing Strategy:
+ - Remove some donors (total count is 2 now)
+ - Send a request to individual_count endpoint.
+ - Ensure that the response does not reveal the number when it is less than 5.
+ """
+ Donor.objects.filter(program_id=self.programs[0]).delete()
+ donors = Donor.objects.all()
+ response = self.client.get(self.individual_count_url)
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+ individual_count_value = response.json()["individual_count"]
+ self.assertLess(len(donors), 5)
+ self.assertEqual(individual_count_value, "<5")
+
+ def test_patients_per_cohort_api_censoring(self):
+ """
+ Verify the censoring of patient counts per cohort endpoint.
+
+ Testing Strategy:
+ - Program 0 has 7, Program 1 has 2 donors
+ - Send a request to patients_per_cohort endpoint.
+ - Ensure that the response does not reveal the number when it is less than 5 donors.
+ """
+ response = self.client.get(self.patients_per_cohort_url)
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+ for item in response.json():
+ patients_count_value = item["patients_count"]
+ patients_count = Donor.objects.filter(program_id=item["program_id"]).count()
+ if patients_count < 5:
+ self.assertEqual(patients_count_value, "<5")
+ else:
+ self.assertEqual(patients_count_value, str(patients_count))
+
+ def test_gender_count_api_censoring(self):
+ """
+ Test gender count API censoring for small datasets.
+
+ Testing Strategy:
+ - "Man" gender is greater or equal to 5
+ - Other genders is less than 5
+ - Send a request to gender_count endpoint.
+ - Ensure that the response does not reveal the count when it is less than 5.
+ """
+
+ response = self.client.get(self.gender_count_url)
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+ for item in response.json():
+ gender_count_value = item["gender_count"]
+ gender_count = Donor.objects.filter(gender=item["gender"]).count()
+ if gender_count < 5:
+ self.assertEqual(gender_count_value, "<5")
+ else:
+ self.assertEqual(gender_count_value, str(gender_count))
+
+ def test_primary_site_count_api_censoring(self):
+ """
+ Test primary site count API censoring for small datasets.
+
+ Testing Strategy:
+ - "Accessory sinuses" primary site is greater or equal to 5
+ - Other primary site is less than 5
+ - Send a request to primary_site_count endpoint.
+ - Ensure that the response does not reveal the count when it is less than 5.
+ """
+ response = self.client.get(self.primary_site_count_url)
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+ for item in response.json():
+ primary_site_count_value = item["primary_site_count"]
+ primary_site_count = Donor.objects.filter(
+ primary_site__contains=[item["primary_site_name"]]
+ ).count()
+ if primary_site_count < 5:
+ self.assertEqual(primary_site_count_value, "<5")
+ else:
+ self.assertEqual(primary_site_count_value, str(primary_site_count))
+
+ def test_treatment_type_count_api_censoring(self):
+ """
+ Test treatment type count count API censoring for small datasets.
+
+ Testing Strategy:
+ - "Bone marrow transplant" treatment type is greater or equal to 5
+ - Other treatment type should be less than 5 (but might not since randomized)
+ - Send a request to treatment_type_count endpoint.
+ - Ensure that the response does not reveal the count when it is less than 5.
+ """
+ response = self.client.get(self.treatment_type_count_url)
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+ for item in response.json():
+ treatment_type_count_value = item["treatment_type_count"]
+ treatment_type_count = Treatment.objects.filter(
+ treatment_type__contains=[item["treatment_type_name"]]
+ ).count()
+ if treatment_type_count < 5:
+ self.assertEqual(treatment_type_count_value, "<5")
+ else:
+ self.assertEqual(treatment_type_count_value, str(treatment_type_count))
+
+ def test_diagnosis_age_count_api_censoring(self):
+ """
+ Test diagnosis age count count API censoring for small datasets.
+
+ Testing Strategy:
+ - "70-79" age bracket is greater or equal to 5
+ - Other age bracket is less than 5
+ - Send a request to diagnosis_age_count endpoint.
+ - Ensure that the response does not reveal the count when it is less than 5.
+ """
+ response = self.client.get(self.diagnosis_age_count_url)
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+ for item in response.json():
+ if item["age_at_diagnosis"] != "70-79":
+ self.assertEqual(item["age_count"], "<5")
+
+ def test_discover_donors_api_censoring(self):
+ """
+ Test discovery donor API censoring for small datasets.
+
+ Testing Strategy:
+ - Program 0 has 7, Program 1 has 2 donors
+ - Send a request to discovery donors endpoint.
+ - Ensure that the response does not reveal the count when it is less than 5.
+ """
+ response = self.client.get(self.discover_donors_url)
+ self.assertEqual(response.status_code, HTTPStatus.OK)
+ for item in response.json():
+ donors_count_value = item["donors_count"]
+ donors_count = Donor.objects.filter(program_id=item["program_id"]).count()
+ if donors_count < 5:
+ self.assertEqual(donors_count_value, "<5")
+ else:
+ self.assertEqual(donors_count_value, str(donors_count))
diff --git a/config/settings/base.py b/config/settings/base.py
index fc0ffe931..6ab612b14 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -147,4 +147,4 @@
# CURRENT KATSU VERSION ACCORDING TO MODEL CHANGES
# ------------------------------------------------
-KATSU_VERSION = "4.2.1"
+KATSU_VERSION = "4.3.0"
diff --git a/config/settings/dev.py b/config/settings/dev.py
index 67b03c04c..474ab0276 100644
--- a/config/settings/dev.py
+++ b/config/settings/dev.py
@@ -14,15 +14,35 @@
from .base import *
+required_env_vars = [
+ "HOST_CONTAINER_NAME",
+ "EXTERNAL_URL",
+ "AGGREGATE_COUNT_THRESHOLD",
+ "OPA_URL",
+ "POSTGRES_DATABASE",
+ "POSTGRES_USER",
+ "POSTGRES_PASSWORD_FILE",
+ "POSTGRES_HOST",
+ "POSTGRES_PORT",
+ "REDIS_PASSWORD_FILE",
+]
+
+missing_vars = [var for var in required_env_vars if not os.getenv(var)]
+if missing_vars:
+ raise EnvironmentError(
+ f"Missing required environment variables: {', '.join(missing_vars)}"
+ )
+
DEBUG = True
ALLOWED_HOSTS = [
"localhost",
"127.0.0.1",
- os.environ.get("HOST_CONTAINER_NAME"),
- os.environ.get("EXTERNAL_URL"),
- "query"
+ os.environ["HOST_CONTAINER_NAME"],
+ os.environ["EXTERNAL_URL"],
+ "query",
]
+AGGREGATE_COUNT_THRESHOLD = os.environ["AGGREGATE_COUNT_THRESHOLD"]
# Debug toolbar settings
# ----------------------
@@ -30,8 +50,8 @@
MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")
INTERNAL_IPS = type("c", (), {"__contains__": lambda *a: True})()
DEBUG_TOOLBAR_CONFIG = {
- 'RENDER_PANELS': False,
- 'RESULTS_CACHE_SIZE': 100,
+ "RENDER_PANELS": False,
+ "RESULTS_CACHE_SIZE": 100,
}
# Whitenoise
@@ -41,7 +61,7 @@
# CANDIG SETTINGS
# ---------------
-CANDIG_OPA_URL = os.getenv("OPA_URL")
+CANDIG_OPA_URL = os.environ["OPA_URL"]
CACHE_DURATION = int(os.getenv("CACHE_DURATION", 86400)) # default to 1 day
CONN_MAX_AGE = int(os.getenv("CONN_MAX_AGE", 0))
if exists("/run/secrets/opa-service-token"):
@@ -65,11 +85,11 @@ def get_secret(path):
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
- "NAME": os.environ.get("POSTGRES_DATABASE"),
- "USER": os.environ.get("POSTGRES_USER"),
- "PASSWORD": get_secret(os.environ.get("POSTGRES_PASSWORD_FILE")),
- "HOST": os.environ.get("POSTGRES_HOST"),
- "PORT": os.environ.get("POSTGRES_PORT"),
+ "NAME": os.environ["POSTGRES_DATABASE"],
+ "USER": os.environ["POSTGRES_USER"],
+ "PASSWORD": get_secret(os.environ["POSTGRES_PASSWORD_FILE"]),
+ "HOST": os.environ["POSTGRES_HOST"],
+ "PORT": os.environ["POSTGRES_PORT"],
}
}
@@ -78,7 +98,7 @@ def get_secret(path):
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
- "LOCATION": f"redis://:{get_secret(os.environ.get('REDIS_PASSWORD_FILE'))}@{os.environ.get('EXTERNAL_URL')}:6379/1",
+ "LOCATION": f"redis://:{get_secret(os.environ['REDIS_PASSWORD_FILE'])}@{os.environ['EXTERNAL_URL']}:6379/1",
"TIMEOUT": CACHE_DURATION,
}
}
diff --git a/config/settings/local.py b/config/settings/local.py
index 0c32dc338..9aa8b2918 100644
--- a/config/settings/local.py
+++ b/config/settings/local.py
@@ -17,6 +17,7 @@
DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "0.0.0.0"]
+AGGREGATE_COUNT_THRESHOLD = 5
# Debug toolbar settings
# ----------------------
diff --git a/config/settings/prod.py b/config/settings/prod.py
index 6495b1bb9..a75da7c93 100644
--- a/config/settings/prod.py
+++ b/config/settings/prod.py
@@ -10,13 +10,34 @@
from .base import *
+required_env_vars = [
+ "HOST_CONTAINER_NAME",
+ "EXTERNAL_URL",
+ "CANDIG_INTERNAL_DOMAIN",
+ "AGGREGATE_COUNT_THRESHOLD",
+ "OPA_URL",
+ "POSTGRES_DATABASE",
+ "POSTGRES_USER",
+ "POSTGRES_PASSWORD_FILE",
+ "POSTGRES_HOST",
+ "POSTGRES_PORT",
+ "REDIS_PASSWORD_FILE",
+]
+
+missing_vars = [var for var in required_env_vars if not os.getenv(var)]
+if missing_vars:
+ raise EnvironmentError(
+ f"Missing required environment variables: {', '.join(missing_vars)}"
+ )
+
ALLOWED_HOSTS = [
- os.environ.get("HOST_CONTAINER_NAME"),
- os.environ.get("EXTERNAL_URL"),
- os.environ.get("CANDIG_INTERNAL_DOMAIN"),
+ os.environ["HOST_CONTAINER_NAME"],
+ os.environ["EXTERNAL_URL"],
+ os.environ["CANDIG_INTERNAL_DOMAIN"],
"127.0.0.1",
- "query"
+ "query",
]
+AGGREGATE_COUNT_THRESHOLD = os.environ["AGGREGATE_COUNT_THRESHOLD"]
# Whitenoise
# ----------
@@ -25,13 +46,14 @@
# CANDIG SETTINGS
# ---------------
-CANDIG_OPA_URL = os.getenv("OPA_URL")
+CANDIG_OPA_URL = os.environ["OPA_URL"]
CACHE_DURATION = int(os.getenv("CACHE_DURATION", 86400)) # default to 1 day
CONN_MAX_AGE = int(os.getenv("CONN_MAX_AGE", 0))
if exists("/run/secrets/opa-service-token"):
with open("/run/secrets/opa-service-token", "r") as f:
CANDIG_OPA_SECRET = f.read()
+
# function to read docker secret password file
def get_secret(path):
try:
@@ -49,11 +71,11 @@ def get_secret(path):
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
- "NAME": os.environ.get("POSTGRES_DATABASE"),
- "USER": os.environ.get("POSTGRES_USER"),
- "PASSWORD": get_secret(os.environ.get("POSTGRES_PASSWORD_FILE")),
- "HOST": os.environ.get("POSTGRES_HOST"),
- "PORT": os.environ.get("POSTGRES_PORT"),
+ "NAME": os.environ["POSTGRES_DATABASE"],
+ "USER": os.environ["POSTGRES_USER"],
+ "PASSWORD": get_secret(os.environ["POSTGRES_PASSWORD_FILE"]),
+ "HOST": os.environ["POSTGRES_HOST"],
+ "PORT": os.environ["POSTGRES_PORT"],
}
}
@@ -62,7 +84,7 @@ def get_secret(path):
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
- "LOCATION": f"redis://:{get_secret(os.environ.get('REDIS_PASSWORD_FILE'))}@{os.environ.get('EXTERNAL_URL')}:6379/1",
+ "LOCATION": f"redis://:{get_secret(os.environ['REDIS_PASSWORD_FILE'])}@{os.environ['EXTERNAL_URL']}:6379/1",
"TIMEOUT": CACHE_DURATION,
}
}