Skip to content

Commit 82d6ff4

Browse files
committed
feat: refactor RetrieveCohortOverlapStats and fix breakdown stats method
...and fix model tests
1 parent 8b91efe commit 82d6ff4

File tree

6 files changed

+68
-54
lines changed

6 files changed

+68
-54
lines changed

controllers/cohortdata.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -282,13 +282,12 @@ func populateConceptValue(row []string, cohortItem models.PersonConceptAndValue,
282282
func (u CohortDataController) RetrieveCohortOverlapStats(c *gin.Context) {
283283
errors := make([]error, 4)
284284
var sourceId, caseCohortId, controlCohortId int
285-
var conceptIdsAndValues []utils.CustomConceptVariableDef
286-
var cohortPairs []utils.CustomDichotomousVariableDef
285+
var conceptDefsAndCohortPairs []interface{}
287286
sourceId, errors[0] = utils.ParseNumericArg(c, "sourceid")
288287
caseCohortId, errors[1] = utils.ParseNumericArg(c, "casecohortid")
289288
controlCohortId, errors[2] = utils.ParseNumericArg(c, "controlcohortid")
290-
conceptIdsAndValues, cohortPairs, errors[3] = utils.ParseConceptDefsAndDichotomousDefs(c)
291-
conceptIds := utils.ExtractConceptIdsFromCustomConceptVariablesDef(conceptIdsAndValues)
289+
conceptDefsAndCohortPairs, errors[3] = utils.ParseConceptDefsAndDichotomousDefsAsSingleList(c)
290+
_, cohortPairs := utils.GetConceptDefsAndValuesAndCohortPairsAsSeparateLists(conceptDefsAndCohortPairs)
292291

293292
validAccessRequest := u.teamProjectAuthz.TeamProjectValidation(c, []int{caseCohortId, controlCohortId}, cohortPairs)
294293
if !validAccessRequest {
@@ -304,7 +303,7 @@ func (u CohortDataController) RetrieveCohortOverlapStats(c *gin.Context) {
304303
return
305304
}
306305
overlapStats, err := u.cohortDataModel.RetrieveCohortOverlapStats(sourceId, caseCohortId,
307-
controlCohortId, conceptIds, cohortPairs)
306+
controlCohortId, conceptDefsAndCohortPairs)
308307
if err != nil {
309308
c.JSON(http.StatusInternalServerError, gin.H{"message": "Error retrieving stats", "error": err.Error()})
310309
c.Abort()

models/cohortdata.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
type CohortDataI interface {
1212
RetrieveDataBySourceIdAndCohortIdAndConceptIdsOrderedByPersonId(sourceId int, cohortDefinitionId int, conceptIds []int64) ([]*PersonConceptAndValue, error)
13-
RetrieveCohortOverlapStats(sourceId int, caseCohortId int, controlCohortId int, otherFilterConceptIds []int64, filterCohortPairs []utils.CustomDichotomousVariableDef) (CohortOverlapStats, error)
13+
RetrieveCohortOverlapStats(sourceId int, caseCohortId int, controlCohortId int, filterConceptDefsAndCohortPairs []interface{}) (CohortOverlapStats, error)
1414
RetrieveDataByOriginalCohortAndNewCohort(sourceId int, originalCohortDefinitionId int, cohortDefinitionId int) ([]*PersonIdAndCohort, error)
1515
RetrieveHistogramDataBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs(sourceId int, cohortDefinitionId int, filterConceptDefsAndCohortPairs []interface{}) ([]*PersonConceptAndValue, error)
1616
RetrieveBarGraphDataBySourceIdAndCohortIdAndConceptIds(sourceId int, conceptId int64) ([]*NominalGroupData, error)
@@ -170,27 +170,28 @@ func (h CohortData) RetrieveBarGraphDataBySourceIdAndCohortIdAndConceptIds(sourc
170170
return cohortData, meta_result.Error
171171
}
172172

173-
// Basically the same as the method above, but without the extra filtering on filterConceptId and filterConceptValue:
174173
func (h CohortData) RetrieveCohortOverlapStats(sourceId int, caseCohortId int, controlCohortId int,
175-
filterConceptIds []int64, filterCohortPairs []utils.CustomDichotomousVariableDef) (CohortOverlapStats, error) {
174+
filterConceptDefsAndCohortPairs []interface{}) (CohortOverlapStats, error) {
176175

177176
var dataSourceModel = new(Source)
178177
omopDataSource := dataSourceModel.GetDataSource(sourceId, Omop)
179178
resultsDataSource := dataSourceModel.GetDataSource(sourceId, Results)
180179

181180
var cohortOverlapStats CohortOverlapStats
182-
query := QueryFilterByCohortPairsHelper(filterCohortPairs, resultsDataSource, caseCohortId, "case_cohort_unionedAndIntersectedWithFilters").
183-
Select("count(distinct(case_cohort_unionedAndIntersectedWithFilters.subject_id)) as case_control_overlap").
184-
Joins("INNER JOIN " + resultsDataSource.Schema + ".cohort as control_cohort ON control_cohort.subject_id = case_cohort_unionedAndIntersectedWithFilters.subject_id") // this one allows for the intersection between case and control and the assessment of the overlap
181+
session := resultsDataSource.Db.Session(&gorm.Session{})
182+
err := session.Transaction(func(query *gorm.DB) error {
183+
finalSetAlias := "case_cohort_unionedAndIntersectedWithFilters"
184+
query, _ = QueryFilterByConceptDefsPlusCohortPairsHelper(query, sourceId, caseCohortId, filterConceptDefsAndCohortPairs, omopDataSource, resultsDataSource, finalSetAlias)
185185

186-
if len(filterConceptIds) > 0 {
187-
query = QueryFilterByConceptIdsHelper(query, sourceId, filterConceptIds, omopDataSource, resultsDataSource.Schema, "control_cohort.subject_id")
188-
}
189-
query = query.Where("control_cohort.cohort_definition_id = ?", controlCohortId)
190-
query, cancel := utils.AddTimeoutToQuery(query)
191-
defer cancel()
192-
meta_result := query.Scan(&cohortOverlapStats)
193-
return cohortOverlapStats, meta_result.Error
186+
query = query.Select("count(distinct(case_cohort_unionedAndIntersectedWithFilters.subject_id)) as case_control_overlap").
187+
Joins("INNER JOIN "+resultsDataSource.Schema+".cohort as control_cohort ON control_cohort.subject_id = case_cohort_unionedAndIntersectedWithFilters.subject_id"). // this one allows for the intersection between case and control and the assessment of the overlap
188+
Where("control_cohort.cohort_definition_id = ?", controlCohortId)
189+
query, cancel := utils.AddTimeoutToQuery(query)
190+
defer cancel()
191+
meta_result := query.Scan(&cohortOverlapStats)
192+
return meta_result.Error
193+
})
194+
return cohortOverlapStats, err
194195
}
195196

196197
func (p *PersonConceptAndCount) String() string {

models/concept.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ func (h Concept) RetrieveBreakdownStatsBySourceIdAndCohortIdAndConceptDefsPlusCo
185185
var conceptBreakdownList []*ConceptBreakdown
186186
session := resultsDataSource.Db.Session(&gorm.Session{})
187187
err := session.Transaction(func(query *gorm.DB) error {
188-
query, tmpTableName := QueryFilterByConceptDefsPlusCohortPairsHelper(query, sourceId, cohortDefinitionId, filterConceptDefsAndCohortPairs, omopDataSource, resultsDataSource, finalSetAlias)
188+
query, finalObservationTableAlias := QueryFilterByConceptDefsPlusCohortPairsHelper(query, sourceId, cohortDefinitionId, filterConceptDefsAndCohortPairs, omopDataSource, resultsDataSource, finalSetAlias)
189189
// count persons, grouping by concept value:
190-
if tmpTableName != "" {
191-
query = query.Select(tmpTableName + ".value_as_concept_id), count(distinct(" + tmpTableName + ".person_id)) as npersons_in_cohort_with_value").
192-
Group(tmpTableName + ".value_as_concept_id")
190+
if finalObservationTableAlias != "" {
191+
query = query.Select(finalObservationTableAlias + ".value_as_concept_id, count(distinct(" + finalObservationTableAlias + ".person_id)) as npersons_in_cohort_with_value").
192+
Group(finalObservationTableAlias + ".value_as_concept_id")
193193
} else {
194194
query = query.Select("observation.value_as_concept_id, count(distinct(observation.person_id)) as npersons_in_cohort_with_value").
195195
Joins("INNER JOIN "+omopDataSource.Schema+".observation_continuous as observation"+omopDataSource.GetViewDirective()+" ON "+finalSetAlias+".subject_id = observation.person_id").

models/helper.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,31 @@ func QueryFilterByConceptDefsPlusCohortPairsHelper(query *gorm.DB, sourceId int,
2121
// Caching of temporary tables: for optimal performance, a temporary table dictionary / cache is updated, keeping a mapping
2222
// of existing temporary table names vs underlying subsets of items in filterConceptDefsAndCohortPairs that gave rise to these
2323
// tables.
24+
// Returns: the final query,
25+
// and, if the last variable in filterConceptDefsAndCohortPairs is a concept/numeric one,
26+
// this method also returns the final observation table alias representing the filter
27+
// added for this variable to the final query.
2428
query = query.Table(resultsDataSource.Schema+".cohort as "+finalCohortAlias).
2529
Select("*").
26-
Where("cohort_definition_id=?", mainCohortDefinitionId)
30+
Where(finalCohortAlias+".cohort_definition_id=?", mainCohortDefinitionId)
2731

28-
tmpTableName := ""
32+
finalObservationTableAlias := ""
2933
var err error
3034
for i, item := range filterConceptDefsAndCohortPairs {
3135
observationTableAlias := fmt.Sprintf("filter_%d", i)
3236
switch convertedItem := item.(type) {
3337
case utils.CustomConceptVariableDef:
34-
query, tmpTableName, err = QueryFilterByConceptDefHelper(query, sourceId, convertedItem, omopDataSource, finalCohortAlias, observationTableAlias)
38+
query, finalObservationTableAlias, err = QueryFilterByConceptDefHelper(query, sourceId, convertedItem, omopDataSource, finalCohortAlias, observationTableAlias)
3539
case utils.CustomDichotomousVariableDef:
3640
query = QueryFilterByCohortPairHelper(query, convertedItem, resultsDataSource, mainCohortDefinitionId, finalCohortAlias+".subject_id", observationTableAlias)
37-
tmpTableName = ""
41+
finalObservationTableAlias = ""
3842
}
3943
}
4044
if err != nil {
4145
log.Fatalf("Error: %s", err.Error())
4246
panic("error")
4347
}
44-
return query, tmpTableName
48+
return query, finalObservationTableAlias
4549
}
4650

4751
// DEPRECATED - USE QueryFilterByConceptDefsHelper
@@ -91,7 +95,7 @@ func QueryFilterByConceptDefHelper(query *gorm.DB, sourceId int, filterConceptDe
9195
// simple filterConceptDef with no transformation
9296
query := queryJoinAndFilterByConceptDef(query, sourceId, filterConceptDef,
9397
omopDataSource, personIdFieldForObservationJoin, "observation_continuous", observationTableAlias)
94-
return query, "", nil
98+
return query, observationTableAlias, nil
9599
}
96100
}
97101

tests/controllers_tests/controllers_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (h dummyCohortDataModel) RetrieveBarGraphDataBySourceIdAndCohortIdAndConcep
9292
}
9393

9494
func (h dummyCohortDataModel) RetrieveCohortOverlapStats(sourceId int, caseCohortId int, controlCohortId int,
95-
otherFilterConceptIds []int64, filterCohortPairs []utils.CustomDichotomousVariableDef) (models.CohortOverlapStats, error) {
95+
filterConceptDefsAndCohortPairs []interface{}) (models.CohortOverlapStats, error) {
9696
var zeroOverlap models.CohortOverlapStats
9797
return zeroOverlap, nil
9898
}

tests/models_tests/models_test.go

+34-24
Original file line numberDiff line numberDiff line change
@@ -475,11 +475,11 @@ func TestRetrieveBreakdownStatsBySourceIdAndCohortIdAndConceptDefsPlusCohortPair
475475
ProvidedName: "test"},
476476
}
477477
breakdownConceptId := hareConceptId // not normally the case...but we'll use the same here just for the test...
478-
stats, _ := conceptModel.RetrieveBreakdownStatsBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs(testSourceId,
478+
stats, err := conceptModel.RetrieveBreakdownStatsBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs(testSourceId,
479479
extendedCopyOfSecondLargestCohort.Id, filterConceptDefsAndCohortPairs, breakdownConceptId)
480480
// we expect values since secondLargestCohort has multiple subjects with hare info:
481481
if len(stats) < 4 {
482-
t.Errorf("Expected at least 4 results, found %d", len(stats))
482+
t.Errorf("Expected at least 4 results, found %d. Error: %v", len(stats), err)
483483
}
484484
prevName := ""
485485
for _, stat := range stats {
@@ -499,11 +499,11 @@ func TestRetrieveBreakdownStatsBySourceIdAndCohortIdAndConceptDefsPlusCohortPair
499499
ConceptId: hareConceptId,
500500
},
501501
}
502-
stats2, _ := conceptModel.RetrieveBreakdownStatsBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs(testSourceId,
502+
stats2, err := conceptModel.RetrieveBreakdownStatsBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs(testSourceId,
503503
extendedCopyOfSecondLargestCohort.Id, filterConceptDefsAndCohortPairs, breakdownConceptId)
504504
// very rough check (ideally we would check the individual stats as well...TODO?):
505505
if len(stats) > len(stats2) {
506-
t.Errorf("First query is more restrictive, so its stats should not be larger than stats2 of second query. Got %d and %d", len(stats), len(stats2))
506+
t.Errorf("First query is more restrictive, so its stats should not be larger than stats2 of second query. Got %d and %d. Error: %v", len(stats), len(stats2), err)
507507
}
508508

509509
// test filtering with secondLargestCohort, smallest and largestCohort.
@@ -1342,53 +1342,63 @@ func TestRetrieveCohortOverlapStats(t *testing.T) {
13421342
setUp(t)
13431343
caseCohortId := secondLargestCohort.Id
13441344
controlCohortId := secondLargestCohort.Id // to ensure we get some overlap, just repeat the same here...
1345-
otherFilterConceptIds := []int64{}
1346-
filterCohortPairs := []utils.CustomDichotomousVariableDef{}
1347-
stats, _ := cohortDataModel.RetrieveCohortOverlapStats(testSourceId, caseCohortId, controlCohortId,
1348-
otherFilterConceptIds, filterCohortPairs)
1345+
filterConceptDefsAndCohortPairs := []interface{}{}
1346+
stats, err := cohortDataModel.RetrieveCohortOverlapStats(testSourceId, caseCohortId, controlCohortId,
1347+
filterConceptDefsAndCohortPairs)
13491348
// basic test:
13501349
if stats.CaseControlOverlap != int64(secondLargestCohort.CohortSize) {
1351-
t.Errorf("Expected nr persons to be %d, found %d", secondLargestCohort.CohortSize, stats.CaseControlOverlap)
1350+
t.Errorf("Expected nr persons to be %d, found %d. Error: %v", secondLargestCohort.CohortSize, stats.CaseControlOverlap, err)
13521351
}
13531352

13541353
// now use largestCohort as background and filter on the extendedCopyOfSecondLargestCohort
13551354
caseCohortId = largestCohort.Id
13561355
controlCohortId = largestCohort.Id // to ensure we get largestCohort as initial overlap, just repeat the same here...
1357-
filterCohortPairs = []utils.CustomDichotomousVariableDef{
1358-
{
1356+
filterConceptDefsAndCohortPairs = []interface{}{
1357+
utils.CustomDichotomousVariableDef{
13591358
CohortDefinitionId1: smallestCohort.Id,
13601359
CohortDefinitionId2: extendedCopyOfSecondLargestCohort.Id,
1361-
ProvidedName: "test"},
1360+
ProvidedName: "test",
1361+
},
13621362
}
13631363
// then we expect overlap of 6 for extendedCopyOfSecondLargestCohort and largestCohort:
1364-
stats, _ = cohortDataModel.RetrieveCohortOverlapStats(testSourceId, caseCohortId, controlCohortId,
1365-
otherFilterConceptIds, filterCohortPairs)
1364+
stats, err = cohortDataModel.RetrieveCohortOverlapStats(testSourceId, caseCohortId, controlCohortId,
1365+
filterConceptDefsAndCohortPairs)
13661366
if stats.CaseControlOverlap != 6 {
1367-
t.Errorf("Expected nr persons to be %d, found %d", 6, stats.CaseControlOverlap)
1367+
t.Errorf("Expected nr persons to be %d, found %d. Error: %v", 6, stats.CaseControlOverlap, err)
13681368
}
13691369

13701370
// extra test: different parameters that should return the same as above ^:
13711371
caseCohortId = largestCohort.Id
13721372
controlCohortId = extendedCopyOfSecondLargestCohort.Id
1373-
filterCohortPairs = []utils.CustomDichotomousVariableDef{}
1374-
otherFilterConceptIds = []int64{histogramConceptId} // extra filter, to cover this part of the code...
1373+
filterConceptDefsAndCohortPairs = []interface{}{
1374+
utils.CustomConceptVariableDef{
1375+
ConceptId: histogramConceptId,
1376+
},
1377+
} // extra filter, to cover this part of the code...
13751378
// then we expect overlap of 5 for extendedCopyOfSecondLargestCohort and largestCohort (the filter on histogramConceptId should not matter
13761379
// since all in largestCohort have an observation for this concept id except one person who has it but has value_as_number as NULL):
1377-
stats2, _ := cohortDataModel.RetrieveCohortOverlapStats(testSourceId, caseCohortId, controlCohortId,
1378-
otherFilterConceptIds, filterCohortPairs)
1380+
stats2, err := cohortDataModel.RetrieveCohortOverlapStats(testSourceId, caseCohortId, controlCohortId,
1381+
filterConceptDefsAndCohortPairs)
13791382
if stats2.CaseControlOverlap != stats.CaseControlOverlap-1 {
1380-
t.Errorf("Expected nr persons to be %d, found %d", stats.CaseControlOverlap, stats2.CaseControlOverlap)
1383+
t.Errorf("Expected nr persons to be %d, found. %d Error: %v", stats.CaseControlOverlap, stats2.CaseControlOverlap, err)
13811384
}
13821385

13831386
// test for otherFilterConceptIds by filtering above on dummyContinuousConceptId, which is NOT
13841387
// found in any observations of the largestCohort:
1385-
otherFilterConceptIds = []int64{histogramConceptId, dummyContinuousConceptId}
1388+
filterConceptDefsAndCohortPairs = []interface{}{
1389+
utils.CustomConceptVariableDef{
1390+
ConceptId: histogramConceptId,
1391+
},
1392+
utils.CustomConceptVariableDef{
1393+
ConceptId: dummyContinuousConceptId,
1394+
},
1395+
}
13861396
// all other arguments are the same as test above, and we expect overlap of 0, showing the otherFilterConceptIds
13871397
// had the expected effect:
1388-
stats3, _ := cohortDataModel.RetrieveCohortOverlapStats(testSourceId, caseCohortId, controlCohortId,
1389-
otherFilterConceptIds, filterCohortPairs)
1398+
stats3, err := cohortDataModel.RetrieveCohortOverlapStats(testSourceId, caseCohortId, controlCohortId,
1399+
filterConceptDefsAndCohortPairs)
13901400
if stats3.CaseControlOverlap != 0 {
1391-
t.Errorf("Expected nr persons to be 0, found %d", stats3.CaseControlOverlap)
1401+
t.Errorf("Expected nr persons to be 0, found %d. Error: %v", stats3.CaseControlOverlap, err)
13921402
}
13931403
}
13941404

0 commit comments

Comments
 (0)