Skip to content

Commit

Permalink
Rfc80/treatments patients endpoint (#10903)
Browse files Browse the repository at this point in the history
* Create new endpoint treatment/patient-counts/fetch

* Create StudyViewFilterHelper class

* ⬆️ Upgrade CH client Version

* Fix Merge Conflict issue

* Update Treatment event name

* Update to handle patient treatment filtering

* Fix sonar issues

* Fix sonar comments 2
  • Loading branch information
haynescd authored and haynescd committed Nov 24, 2024
1 parent 8ab5f25 commit 94b431c
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 17 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.5.0</version>
<version>0.6.2</version>
<!-- use uber jar with all dependencies included, change classifier
to http for smaller jar -->
<classifier>all</classifier>
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/cbioportal/model/PatientTreatment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.cbioportal.model;

import java.io.Serializable;

public record PatientTreatment (String treatment, int count) implements Serializable {

}
11 changes: 11 additions & 0 deletions src/main/java/org/cbioportal/model/PatientTreatmentReport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.cbioportal.model;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

public record PatientTreatmentReport (int totalPatients, int totalSamples, List<PatientTreatment> patientTreatments) implements Serializable {
public PatientTreatmentReport(int totalPatients, int totalSamples) {
this(totalPatients, totalSamples, Collections.emptyList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import org.cbioportal.model.ClinicalEventTypeCount;
import org.cbioportal.model.CopyNumberCountByGene;
import org.cbioportal.model.GenomicDataCount;
import org.cbioportal.model.PatientTreatment;
import org.cbioportal.model.PatientTreatmentReport;
import org.cbioportal.model.Sample;
import org.cbioportal.web.parameter.ClinicalDataType;
import org.cbioportal.web.parameter.StudyViewFilter;
Expand Down Expand Up @@ -49,4 +51,8 @@ public interface StudyViewRepository {
int getSampleProfileCountWithoutPanelData(StudyViewFilter studyViewFilter, String alterationType);

List<ClinicalEventTypeCount> getClinicalEventTypeCounts(StudyViewFilter studyViewFilter);

List<PatientTreatment> getPatientTreatments(StudyViewFilter studyViewFilter);

PatientTreatmentReport getPatientTreatmentReport(StudyViewFilter studyViewFilter);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.cbioportal.persistence.helper;

import org.cbioportal.model.ClinicalAttribute;
import org.cbioportal.persistence.enums.ClinicalAttributeDataSource;
import org.cbioportal.web.parameter.CategorizedClinicalDataCountFilter;
import org.cbioportal.web.parameter.StudyViewFilter;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class StudyViewFilterHelper {
public static StudyViewFilterHelper build(@Nullable StudyViewFilter studyViewFilter, @Nullable EnumMap<ClinicalAttributeDataSource, List<ClinicalAttribute>> clinicalAttributesMap) {
if (Objects.isNull(studyViewFilter)) {
studyViewFilter = new StudyViewFilter();
}
if (Objects.isNull(clinicalAttributesMap)) {
clinicalAttributesMap = new EnumMap<>(ClinicalAttributeDataSource.class);
}
return new StudyViewFilterHelper(studyViewFilter, clinicalAttributesMap);
}

private final StudyViewFilter studyViewFilter;
private final CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter;


private StudyViewFilterHelper(@NonNull StudyViewFilter studyViewFilter, @NonNull Map<ClinicalAttributeDataSource, List<ClinicalAttribute>> clinicalAttributesMap ) {
this.studyViewFilter = studyViewFilter;
this.categorizedClinicalDataCountFilter = extractClinicalDataCountFilters(studyViewFilter, clinicalAttributesMap);
}

private CategorizedClinicalDataCountFilter extractClinicalDataCountFilters(final StudyViewFilter studyViewFilter, Map<ClinicalAttributeDataSource, List<ClinicalAttribute>> clinicalAttributesMap) {

if (studyViewFilter.getClinicalDataFilters() == null || clinicalAttributesMap.isEmpty()) {
return CategorizedClinicalDataCountFilter.getBuilder().build();
}

List<String> patientCategoricalAttributes = clinicalAttributesMap.get(ClinicalAttributeDataSource.PATIENT)
.stream().filter(ca -> ca.getDatatype().equals("STRING"))
.map(ClinicalAttribute::getAttrId)
.toList();

List<String> patientNumericalAttributes = clinicalAttributesMap.get(ClinicalAttributeDataSource.PATIENT)
.stream().filter(ca -> ca.getDatatype().equals("NUMBER"))
.map(ClinicalAttribute::getAttrId)
.toList();

List<String> sampleCategoricalAttributes = clinicalAttributesMap.get(ClinicalAttributeDataSource.SAMPLE)
.stream().filter(ca -> ca.getDatatype().equals("STRING"))
.map(ClinicalAttribute::getAttrId)
.toList();

List<String> sampleNumericalAttributes = clinicalAttributesMap.get(ClinicalAttributeDataSource.SAMPLE)
.stream().filter(ca -> ca.getDatatype().equals("NUMBER"))
.map(ClinicalAttribute::getAttrId)
.toList();

return CategorizedClinicalDataCountFilter.getBuilder()
.setPatientCategoricalClinicalDataFilters(studyViewFilter.getClinicalDataFilters()
.stream().filter(clinicalDataFilter -> patientCategoricalAttributes.contains(clinicalDataFilter.getAttributeId()))
.toList())
.setPatientNumericalClinicalDataFilters(studyViewFilter.getClinicalDataFilters().stream()
.filter(clinicalDataFilter -> patientNumericalAttributes.contains(clinicalDataFilter.getAttributeId()))
.toList())
.setSampleCategoricalClinicalDataFilters(studyViewFilter.getClinicalDataFilters().stream()
.filter(clinicalDataFilter -> sampleCategoricalAttributes.contains(clinicalDataFilter.getAttributeId()))
.toList())
.setSampleNumericalClinicalDataFilters(studyViewFilter.getClinicalDataFilters().stream()
.filter(clinicalDataFilter -> sampleNumericalAttributes.contains(clinicalDataFilter.getAttributeId()))
.toList())
.build();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.cbioportal.model.CopyNumberCountByGene;
import org.cbioportal.model.GenePanelToGene;
import org.cbioportal.model.GenomicDataCount;
import org.cbioportal.model.PatientTreatment;
import org.cbioportal.model.PatientTreatmentReport;
import org.cbioportal.model.Sample;
import org.cbioportal.persistence.helper.AlterationFilterHelper;
import org.cbioportal.web.parameter.CategorizedClinicalDataCountFilter;
Expand Down Expand Up @@ -55,4 +57,7 @@ List<ClinicalDataCount> getClinicalDataCounts(StudyViewFilter studyViewFilter, C
int getSampleProfileCountWithoutPanelData(StudyViewFilter studyViewFilter, CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter, boolean applyPatientIdFilters, String alterationType);

List<ClinicalEventTypeCount> getClinicalEventTypeCounts(StudyViewFilter studyViewFilter, CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter, boolean applyPatientIdFilters);

List<PatientTreatment> getPatientTreatments(StudyViewFilter studyViewFilter, CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter, boolean applyPatientIdFilters);
PatientTreatmentReport getPatientTreatmentCounts(StudyViewFilter studyViewFilter, CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter, boolean applyPatientIdFilters);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import org.cbioportal.model.GenePanelToGene;
import org.cbioportal.model.GenomicDataCount;
import org.cbioportal.model.CopyNumberCountByGene;
import org.cbioportal.model.PatientTreatment;
import org.cbioportal.model.PatientTreatmentReport;
import org.cbioportal.model.Sample;
import org.cbioportal.persistence.StudyViewRepository;
import org.cbioportal.persistence.enums.ClinicalAttributeDataSource;
Expand Down Expand Up @@ -114,8 +116,9 @@ public List<CaseListDataCount> getCaseListDataCounts(StudyViewFilter studyViewFi


private boolean shouldApplyPatientIdFilters(StudyViewFilter studyViewFilter, CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter) {
return studyViewFilter.getClinicalEventFilters() != null && !studyViewFilter.getClinicalEventFilters().isEmpty()
|| categorizedClinicalDataCountFilter.getPatientCategoricalClinicalDataFilters() != null && !categorizedClinicalDataCountFilter.getPatientCategoricalClinicalDataFilters().isEmpty()
return studyViewFilter.getClinicalEventFilters() != null && !studyViewFilter.getClinicalEventFilters().isEmpty()
|| studyViewFilter.getPatientTreatmentFilters() != null && studyViewFilter.getPatientTreatmentFilters().getFilters()!= null && !studyViewFilter.getPatientTreatmentFilters().getFilters().isEmpty()
|| categorizedClinicalDataCountFilter.getPatientCategoricalClinicalDataFilters() != null && !categorizedClinicalDataCountFilter.getPatientCategoricalClinicalDataFilters().isEmpty()
|| categorizedClinicalDataCountFilter.getPatientNumericalClinicalDataFilters() != null && !categorizedClinicalDataCountFilter.getPatientNumericalClinicalDataFilters().isEmpty();
}

Expand Down Expand Up @@ -180,12 +183,35 @@ public List<ClinicalEventTypeCount> getClinicalEventTypeCounts(StudyViewFilter s
shouldApplyPatientIdFilters(studyViewFilter,categorizedClinicalDataCountFilter));
}

@Override
public List<PatientTreatment> getPatientTreatments(StudyViewFilter studyViewFilter) {
CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter = extractClinicalDataCountFilters(studyViewFilter);
return mapper.getPatientTreatments(studyViewFilter, categorizedClinicalDataCountFilter, shouldApplyPatientIdFilters(studyViewFilter, categorizedClinicalDataCountFilter));
}

@Override
public PatientTreatmentReport getPatientTreatmentReport(StudyViewFilter studyViewFilter) {
CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter = extractClinicalDataCountFilters(studyViewFilter);
var patientTreatmentCounts = mapper.getPatientTreatmentCounts(studyViewFilter, categorizedClinicalDataCountFilter,
shouldApplyPatientIdFilters(studyViewFilter, categorizedClinicalDataCountFilter));
var patientTreatments = mapper.getPatientTreatments(studyViewFilter, categorizedClinicalDataCountFilter,
shouldApplyPatientIdFilters(studyViewFilter, categorizedClinicalDataCountFilter));
return new PatientTreatmentReport(patientTreatmentCounts.totalPatients(), patientTreatmentCounts.totalSamples(), patientTreatments);
}

private void buildClinicalAttributeNameMap() {
clinicalAttributesMap = this.getClinicalAttributes()
.stream()
.collect(Collectors.groupingBy(ca -> ca.getPatientAttribute() ? ClinicalAttributeDataSource.PATIENT : ClinicalAttributeDataSource.SAMPLE));
}

private Map<ClinicalAttributeDataSource, List<ClinicalAttribute>> getClinicalAttributeNameMap() {
if (clinicalAttributesMap.isEmpty()) {
buildClinicalAttributeNameMap();
}
return clinicalAttributesMap;
}

private CategorizedClinicalDataCountFilter extractClinicalDataCountFilters(final StudyViewFilter studyViewFilter) {
if (clinicalAttributesMap.isEmpty()) {
buildClinicalAttributeNameMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.cbioportal.model.ClinicalEventTypeCount;
import org.cbioportal.model.GenomicDataCount;
import org.cbioportal.model.CopyNumberCountByGene;
import org.cbioportal.model.PatientTreatmentReport;
import org.cbioportal.model.Sample;
import org.cbioportal.web.parameter.ClinicalDataType;
import org.cbioportal.web.parameter.StudyViewFilter;
Expand Down Expand Up @@ -35,5 +36,5 @@ public interface StudyViewColumnarService {
List<GenomicDataCount> getGenomicDataCounts(StudyViewFilter studyViewFilter);

List<ClinicalEventTypeCount> getClinicalEventTypeCounts(StudyViewFilter studyViewFilter);

PatientTreatmentReport getPatientTreatmentReport(StudyViewFilter studyViewFilter);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.cbioportal.model.ClinicalEventTypeCount;
import org.cbioportal.model.CopyNumberCountByGene;
import org.cbioportal.model.GenomicDataCount;
import org.cbioportal.model.PatientTreatmentReport;
import org.cbioportal.model.Sample;
import org.cbioportal.persistence.StudyViewRepository;
import org.cbioportal.service.AlterationCountService;
Expand Down Expand Up @@ -57,6 +58,11 @@ public List<ClinicalEventTypeCount> getClinicalEventTypeCounts(StudyViewFilter s
return studyViewRepository.getClinicalEventTypeCounts(studyViewFilter);
}

@Override
public PatientTreatmentReport getPatientTreatmentReport(StudyViewFilter studyViewFilter) {
return studyViewRepository.getPatientTreatmentReport(studyViewFilter);
}

public List<CopyNumberCountByGene> getCnaGenes(StudyViewFilter studyViewFilter) {
return alterationCountService.getCnaGenes(studyViewFilter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
import org.cbioportal.model.ClinicalData;
import org.cbioportal.model.ClinicalDataBin;
import org.cbioportal.model.ClinicalDataCountItem;
import org.cbioportal.model.ClinicalEventKeyCode;
import org.cbioportal.model.ClinicalEventTypeCount;
import org.cbioportal.model.ClinicalViolinPlotData;
import org.cbioportal.model.CopyNumberCountByGene;
import org.cbioportal.model.DensityPlotData;
import org.cbioportal.model.GenomicDataCount;
import org.cbioportal.model.PatientTreatmentReport;
import org.cbioportal.model.PatientTreatmentRow;
import org.cbioportal.model.Sample;
import org.cbioportal.service.ClinicalDataDensityPlotService;
import org.cbioportal.service.StudyViewColumnarService;
Expand Down Expand Up @@ -350,4 +353,33 @@ public ResponseEntity<List<ClinicalEventTypeCount>> getClinicalEventTypeCounts(
) {
return new ResponseEntity<>(studyViewColumnarService.getClinicalEventTypeCounts(interceptedStudyViewFilter), HttpStatus.OK);
}

@PreAuthorize("hasPermission(#involvedCancerStudies, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@PostMapping(value = "/column-store/treatments/patient-counts/fetch", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(description = "Get all patient level treatments")
@ApiResponse(responseCode = "200", description = "OK",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = PatientTreatmentRow.class))))
public ResponseEntity<PatientTreatmentReport> getPatientTreatmentCounts(
@Parameter(required = false )
@RequestParam(name = "tier", required = false, defaultValue = "Agent")
ClinicalEventKeyCode tier,

@Parameter(required = true, description = "Study view filter")
@Valid
@RequestBody(required = false)
StudyViewFilter studyViewFilter,

@Parameter(hidden = true) // prevent reference to this attribute in the swagger-ui interface
@RequestAttribute(required = false, value = "involvedCancerStudies")
Collection<String> involvedCancerStudies,

@Parameter(hidden = true) // prevent reference to this attribute in the swagger-ui interface. this attribute is needed for the @PreAuthorize tag above.
@Valid
@RequestAttribute(required = false, value = "interceptedStudyViewFilter")
StudyViewFilter interceptedStudyViewFilter
) {
return new ResponseEntity<>(studyViewColumnarService.getPatientTreatmentReport(interceptedStudyViewFilter),
HttpStatus.OK);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public class InvolvedCancerStudyExtractorInterceptor implements HandlerIntercept
public static final String CLINICAL_EVENT_TYPE_COUNT_FETCH_PATH = "/clinical-event-type-counts/fetch";
public static final String SURVIVAL_DATA_FETCH_PATH = "/survival-data/fetch";
public static final String CLINICAL_EVENT_META_FETCH_PATH = "/clinical-events-meta/fetch";
public static final String TREATMENTS_PATIENT_COUNT_FETCH_PATH = "/treatments/patient-counts/fetch";

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!request.getMethod().equals("POST")) {
Expand Down Expand Up @@ -178,7 +179,8 @@ public class InvolvedCancerStudyExtractorInterceptor implements HandlerIntercept
} else if (Arrays.asList(STUDY_VIEW_CLINICAL_DATA_DENSITY_PATH, STUDY_VIEW_CLINICAL_DATA_VIOLIN_PATH, STUDY_VIEW_CNA_GENES,
STUDY_VIEW_FILTERED_SAMPLES, STUDY_VIEW_MUTATED_GENES, STUDY_VIEW_STRUCTURAL_VARIANT_GENES,
STUDY_VIEW_STRUCTURAL_VARIANT_COUNTS, STUDY_VIEW_SAMPLE_COUNTS, STUDY_VIEW_SAMPLE_LIST_COUNTS_PATH, STUDY_VIEW_CLINICAL_TABLE_DATA_FETCH_PATH,
TREATMENTS_PATIENT_PATH, TREATMENTS_SAMPLE_PATH, STUDY_VIEW_PROFILE_SAMPLE_COUNTS_PATH, CLINICAL_EVENT_TYPE_COUNT_FETCH_PATH
TREATMENTS_PATIENT_PATH, TREATMENTS_SAMPLE_PATH, STUDY_VIEW_PROFILE_SAMPLE_COUNTS_PATH, CLINICAL_EVENT_TYPE_COUNT_FETCH_PATH,
TREATMENTS_PATIENT_COUNT_FETCH_PATH
).contains(requestPathInfo)) {
return extractAttributesFromStudyViewFilter(request);
} else if (requestPathInfo.equals(CLINICAL_DATA_ENRICHMENT_FETCH_PATH)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@
</where>
</foreach>
</sql>

<sql id="applyPatientTreatmentFilter">
<foreach item="andedPatientTreatmentFilters" collection="studyViewFilter.getPatientTreatmentFilters().getFilters()" open="INTERSECT"
separator="INTERSECT">
SELECT patient_unique_id
FROM clinical_event_derived
<where>
<foreach item="patientTreatmentFilter" collection="andedPatientTreatmentFilters.getFilters()" open="(" separator=") OR ("
close=")">
event_type = 'Treatment'
AND key = 'AGENT'
AND value = '${patientTreatmentFilter.treatment}'
</foreach>
</where>
</foreach>
</sql>

<sql id="patientUniqueIdsFromStudyViewFilter">
<trim prefixOverrides="INTERSECT">
Expand All @@ -144,9 +160,13 @@
</foreach>
</if>
<!-- Clinical Event Filter -->
<if test="studyViewFilter.clinicalEventFilters != null and !studyViewFilter.clinicalEventFilters.isEmpty()">
<if test="studyViewFilter.getClinicalEventFilters() != null and !studyViewFilter.getClinicalEventFilters().isEmpty()">
<include refid="applyClinicalEventTypeFilter"/>
</if>
<!-- Patient Treatment Filter -->
<if test="studyViewFilter.getPatientTreatmentFilters() != null and !studyViewFilter.getPatientTreatmentFilters().getFilters().isEmpty()">
<include refid="applyPatientTreatmentFilter"/>
</if>
</trim>
</sql>

Expand Down
Loading

0 comments on commit 94b431c

Please sign in to comment.