Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: support filters and transformation #125

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cb8d570
fix: fix the naming for concept lists
pieterlukasse Dec 20, 2024
e2fbc49
feat: working histogram endpoint POC for using tmp table for transfor…
pieterlukasse Jan 9, 2025
1090e84
feat: added first transformation
pieterlukasse Jan 10, 2025
d5935dd
feat: added z-score transformation
pieterlukasse Jan 10, 2025
6e15ec8
feat: update tempttablecache to include cleanup of oldest / least acc…
pieterlukasse Jan 10, 2025
151b59f
fix: fix code error
pieterlukasse Jan 10, 2025
02f6f00
fix: fix tests and compilation errors
pieterlukasse Jan 10, 2025
e074037
fix: fix to avoid double parsing of request body, which fails
pieterlukasse Jan 14, 2025
3cb31dc
fix: fix parsing and failing tests
pieterlukasse Jan 14, 2025
903ec0b
tmp: temporarily disable strict validation until frontend is fixed
pieterlukasse Jan 14, 2025
680dad9
fix: remove code duplication and fix filter_0 issue
pieterlukasse Jan 15, 2025
d2ce4cf
fix: improve test code
pieterlukasse Jan 16, 2025
835006e
fix: using ORM methods instead of raw query
pieterlukasse Jan 16, 2025
32dd028
feat: add some debug log statements
pieterlukasse Jan 16, 2025
7c4d45a
tmp: try different tosql method
pieterlukasse Jan 16, 2025
f3951c2
tmp: remove interpolate
pieterlukasse Jan 16, 2025
f790702
feat: simplify toSQL
pieterlukasse Jan 16, 2025
802e335
feat: add support for sqlserver
pieterlukasse Jan 20, 2025
b8c2fea
fix: fix tests and fix temp table name
pieterlukasse Jan 20, 2025
e0fd0d4
fix: avoid negative or 0's for log transformation; fix sqlserver syntax
pieterlukasse Jan 20, 2025
bc5394e
fix: fix log and stdev queries
pieterlukasse Jan 21, 2025
3d11a93
fix: fix temp table alias
pieterlukasse Jan 21, 2025
fc14ccd
test: using global temp table
pieterlukasse Jan 21, 2025
efd202e
test: try cloning the session
pieterlukasse Jan 22, 2025
6b02d5c
feat: remove confusing code; test without dryrun
pieterlukasse Jan 22, 2025
674a0fc
feat: wrap all in single transaction
pieterlukasse Jan 22, 2025
0763d91
feat: adjust temp table usage to check for table existence first
pieterlukasse Jan 23, 2025
96d3cd8
fix: fix scenario with multiple filters
pieterlukasse Jan 23, 2025
98baf7b
fix: reset the error state for the table exists check
pieterlukasse Jan 23, 2025
8a5dbe8
feat: remove histogramConceptId from histogram endpoint
pieterlukasse Jan 24, 2025
1473df6
feat: remove histogram concept id from histogram endpoint
pieterlukasse Jan 24, 2025
bd144e7
feat: cleanup / review todos
pieterlukasse Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ curl -d '{"variables":[{"variable_type": "concept", "concept_id": 2000000324},{"

Histogram endpoint:
```bash
curl -d '{"variables":[{"variable_type": "custom_dichotomous", "cohort_ids": [1, 4]}]}' -H "Content-Type: application/json" -X POST http://localhost:8080/histogram/by-source-id/1/by-cohort-definition-id/4/by-histogram-concept-id/2000006885
curl -d '{"variables":[{"variable_type": "custom_dichotomous", "cohort_ids": [1, 4]}, {"variable_type": "concept", "concept_id": 2000006885}]}' -H "Content-Type: application/json" -X POST http://localhost:8080/histogram/by-source-id/1/by-cohort-definition-id/4
```

# Deployment steps
Expand Down
24 changes: 8 additions & 16 deletions controllers/cohortdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,20 @@ func NewCohortDataController(cohortDataModel models.CohortDataI, dataDictionaryM
}

func (u CohortDataController) RetrieveHistogramForCohortIdAndConceptId(c *gin.Context) {
sourceIdStr := c.Param("sourceid")
log.Printf("Querying source: %s", sourceIdStr)
cohortIdStr := c.Param("cohortid")
log.Printf("Querying cohort for cohort definition id: %s", cohortIdStr)
histogramIdStr := c.Param("histogramid")
if sourceIdStr == "" || cohortIdStr == "" || histogramIdStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"message": "bad request"})
sourceId, cohortId, conceptIdsAndCohortPairs, err := utils.ParseSourceIdAndCohortIdAndVariablesAsSingleList(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "bad request", "error": err.Error()})
c.Abort()
return
}

filterConceptIdsAndValues, cohortPairs, err := utils.ParseConceptDefsAndDichotomousDefs(c)
// parse cohortPairs separately as well, so we can validate permissions
_, cohortPairs := utils.GetConceptDefsAndValuesAndCohortPairsAsSeparateLists(conceptIdsAndCohortPairs)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Error parsing request body for prefixed concept ids", "error": err.Error()})
c.Abort()
return
}

sourceId, _ := strconv.Atoi(sourceIdStr)
cohortId, _ := strconv.Atoi(cohortIdStr)
histogramConceptId, _ := strconv.ParseInt(histogramIdStr, 10, 64)

validAccessRequest := u.teamProjectAuthz.TeamProjectValidation(c, []int{cohortId}, cohortPairs)
if !validAccessRequest {
log.Printf("Error: invalid request")
Expand All @@ -59,9 +51,9 @@ func (u CohortDataController) RetrieveHistogramForCohortIdAndConceptId(c *gin.Co
return
}

cohortData, err := u.cohortDataModel.RetrieveHistogramDataBySourceIdAndCohortIdAndConceptIdsAndCohortPairs(sourceId, cohortId, histogramConceptId, filterConceptIdsAndValues, cohortPairs)
cohortData, err := u.cohortDataModel.RetrieveHistogramDataBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs(sourceId, cohortId, conceptIdsAndCohortPairs)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Error retrieving concept details", "error": err.Error()})
c.JSON(http.StatusInternalServerError, gin.H{"message": "Error retrieving histogram data", "error": err.Error()})
c.Abort()
return
}
Expand Down Expand Up @@ -102,7 +94,7 @@ func (u CohortDataController) RetrieveStatsForCohortIdAndConceptId(c *gin.Contex
return
}

cohortData, err := u.cohortDataModel.RetrieveHistogramDataBySourceIdAndCohortIdAndConceptIdsAndCohortPairs(sourceId, cohortId, conceptId, filterConceptIdsAndValues, cohortPairs)
cohortData, err := u.cohortDataModel.RetrieveHistogramDataBySourceIdAndCohortIdAndConceptDefsAndCohortPairs(sourceId, cohortId, conceptId, filterConceptIdsAndValues, cohortPairs)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Error retrieving concept details", "error": err.Error()})
c.Abort()
Expand Down
6 changes: 3 additions & 3 deletions controllers/concept.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func (u ConceptController) RetrieveAttritionTable(c *gin.Context) {
c.Abort()
return
}
_, cohortPairs := utils.GetConceptIdsAndValuesAndCohortPairsAsSeparateLists(conceptIdsAndCohortPairs)
_, cohortPairs := utils.GetConceptDefsAndValuesAndCohortPairsAsSeparateLists(conceptIdsAndCohortPairs)
validAccessRequest := u.teamProjectAuthz.TeamProjectValidation(c, []int{cohortId}, cohortPairs)
if !validAccessRequest {
log.Printf("Error: invalid request")
Expand Down Expand Up @@ -266,8 +266,8 @@ func (u ConceptController) GetAttritionRowForConceptIdsAndCohortPairs(sourceId i
}

func (u ConceptController) GetAttritionRowForConceptIdOrCohortPair(sourceId int, cohortId int, conceptIdOrCohortPair interface{}, filterConceptIdsAndCohortPairs []interface{}, breakdownConceptId int64, sortedConceptValues []string) ([]string, error) {
filterConceptIdsAndValues, filterCohortPairs := utils.GetConceptIdsAndValuesAndCohortPairsAsSeparateLists(filterConceptIdsAndCohortPairs)
filterConceptIds := utils.ExtractConceptIdsFromCustomConceptVariablesDef(filterConceptIdsAndValues)
filterConceptDefsAndValues, filterCohortPairs := utils.GetConceptDefsAndValuesAndCohortPairsAsSeparateLists(filterConceptIdsAndCohortPairs)
filterConceptIds := utils.ExtractConceptIdsFromCustomConceptVariablesDef(filterConceptDefsAndValues)
breakdownStats, err := u.conceptModel.RetrieveBreakdownStatsBySourceIdAndCohortIdAndConceptIdsAndCohortPairs(sourceId, cohortId, filterConceptIds, filterCohortPairs, breakdownConceptId)
if err != nil {
return nil, fmt.Errorf("could not retrieve concept Breakdown for concepts %v dichotomous variables %v due to error: %s", filterConceptIds, filterCohortPairs, err.Error())
Expand Down
45 changes: 42 additions & 3 deletions models/cohortdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import (
"log"

"github.com/uc-cdis/cohort-middleware/utils"
"gorm.io/gorm"
)

type CohortDataI interface {
RetrieveDataBySourceIdAndCohortIdAndConceptIdsOrderedByPersonId(sourceId int, cohortDefinitionId int, conceptIds []int64) ([]*PersonConceptAndValue, error)
RetrieveCohortOverlapStats(sourceId int, caseCohortId int, controlCohortId int, otherFilterConceptIds []int64, filterCohortPairs []utils.CustomDichotomousVariableDef) (CohortOverlapStats, error)
RetrieveDataByOriginalCohortAndNewCohort(sourceId int, originalCohortDefinitionId int, cohortDefinitionId int) ([]*PersonIdAndCohort, error)
RetrieveHistogramDataBySourceIdAndCohortIdAndConceptIdsAndCohortPairs(sourceId int, cohortDefinitionId int, histogramConceptId int64, filterConceptIdsAndValues []utils.CustomConceptVariableDef, filterCohortPairs []utils.CustomDichotomousVariableDef) ([]*PersonConceptAndValue, error)
RetrieveHistogramDataBySourceIdAndCohortIdAndConceptDefsAndCohortPairs(sourceId int, cohortDefinitionId int, histogramConceptId int64, filterConceptIdsAndValues []utils.CustomConceptVariableDef, filterCohortPairs []utils.CustomDichotomousVariableDef) ([]*PersonConceptAndValue, error)
RetrieveHistogramDataBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs(sourceId int, cohortDefinitionId int, filterConceptDefsAndCohortPairs []interface{}) ([]*PersonConceptAndValue, error)
RetrieveBarGraphDataBySourceIdAndCohortIdAndConceptIds(sourceId int, conceptId int64) ([]*NominalGroupData, error)
RetrieveHistogramDataBySourceIdAndConceptId(sourceId int, histogramConceptId int64) ([]*PersonConceptAndValue, error)
}
Expand Down Expand Up @@ -97,7 +99,44 @@ func (h CohortData) RetrieveDataBySourceIdAndCohortIdAndConceptIdsOrderedByPerso
return cohortData, meta_result.Error
}

func (h CohortData) RetrieveHistogramDataBySourceIdAndCohortIdAndConceptIdsAndCohortPairs(sourceId int, cohortDefinitionId int, histogramConceptId int64, filterConceptIdsAndValues []utils.CustomConceptVariableDef, filterCohortPairs []utils.CustomDichotomousVariableDef) ([]*PersonConceptAndValue, error) {
func (h CohortData) RetrieveHistogramDataBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs(sourceId int, cohortDefinitionId int, filterConceptDefsAndCohortPairs []interface{}) ([]*PersonConceptAndValue, error) {
var dataSourceModel = new(Source)
omopDataSource := dataSourceModel.GetDataSource(sourceId, Omop)
resultsDataSource := dataSourceModel.GetDataSource(sourceId, Results)
finalSetAlias := "final_set_alias"
histogramConcept, err := utils.GetLastCustomConceptVariableDef(filterConceptDefsAndCohortPairs)
if err != nil {
log.Fatalf("failed: %v", err)
return nil, err
}
// get the observations for the subjects and the concepts, to build up the data rows to return:
var cohortData []*PersonConceptAndValue
session := resultsDataSource.Db.Session(&gorm.Session{})
err = session.Transaction(func(query *gorm.DB) error { // TODO - rename query?
query, tmpTableName := QueryFilterByConceptDefsPlusCohortPairsHelper(query, sourceId, cohortDefinitionId, filterConceptDefsAndCohortPairs, omopDataSource, resultsDataSource, finalSetAlias)
if tmpTableName != "" {
query = query.Select("distinct(" + tmpTableName + ".person_id), " + tmpTableName + ".observation_concept_id as concept_id, " + tmpTableName + ".value_as_number as concept_value_as_number")
} else {
query = query.Select("distinct(observation.person_id), observation.observation_concept_id as concept_id, observation.value_as_number as concept_value_as_number").
Joins("INNER JOIN "+omopDataSource.Schema+".observation_continuous as observation"+omopDataSource.GetViewDirective()+" ON "+finalSetAlias+".subject_id = observation.person_id").
Where("observation.observation_concept_id = ?", histogramConcept.ConceptId).
Where("observation.value_as_number is not null")
}

query, cancel := utils.AddTimeoutToQuery(query)
defer cancel()
meta_result := query.Scan(&cohortData)
return meta_result.Error
})
if err != nil {
log.Fatalf("Transaction failed: %v", err)
return nil, err
}
return cohortData, err
}

// DEPRECATED - USE RetrieveHistogramDataBySourceIdAndCohortIdAndConceptDefsPlusCohortPairs instead.
func (h CohortData) RetrieveHistogramDataBySourceIdAndCohortIdAndConceptDefsAndCohortPairs(sourceId int, cohortDefinitionId int, histogramConceptId int64, filterConceptDefs []utils.CustomConceptVariableDef, filterCohortPairs []utils.CustomDichotomousVariableDef) ([]*PersonConceptAndValue, error) {
var dataSourceModel = new(Source)
omopDataSource := dataSourceModel.GetDataSource(sourceId, Omop)
resultsDataSource := dataSourceModel.GetDataSource(sourceId, Results)
Expand All @@ -110,7 +149,7 @@ func (h CohortData) RetrieveHistogramDataBySourceIdAndCohortIdAndConceptIdsAndCo
Where("observation.observation_concept_id = ?", histogramConceptId).
Where("observation.value_as_number is not null")

query = QueryFilterByConceptIdsAndValuesHelper(query, sourceId, filterConceptIdsAndValues, omopDataSource, resultsDataSource.Schema, "unionAndIntersect.subject_id")
query = QueryFilterByConceptDefsHelper(query, sourceId, filterConceptDefs, omopDataSource, resultsDataSource.Schema, "unionAndIntersect.subject_id")
query, cancel := utils.AddTimeoutToQuery(query)
defer cancel()
meta_result := query.Scan(&cohortData)
Expand Down
Loading
Loading