Skip to content

Commit

Permalink
Merge pull request #256 from fastenhealth/labwork_report_filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
AnalogJ authored Oct 3, 2023
2 parents cce4a25 + f26447c commit 0b99549
Show file tree
Hide file tree
Showing 69 changed files with 678 additions and 121 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
.idea/**/tasks.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/dataSources.xml

# Sensitive or high-churn files
.idea/**/dataSources/
Expand Down
2 changes: 1 addition & 1 deletion .idea/dataSources.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

178 changes: 121 additions & 57 deletions backend/pkg/database/sqlite_repository_query.go

Large diffs are not rendered by default.

135 changes: 126 additions & 9 deletions backend/pkg/database/sqlite_repository_query_sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL() {
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?)",
"GROUP BY `fhir`.`id`",
"ORDER BY fhir.sort_date ASC",
"ORDER BY fhir.sort_date DESC",
}, " "),
sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
Expand Down Expand Up @@ -136,7 +136,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithMultipleWhereCon
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson, json_each(fhir.category) as categoryJson",
"WHERE ((codeJson.value ->> '$.code' = ?)) AND ((categoryJson.value ->> '$.code' = ?)) AND (user_id = ?)",
"GROUP BY `fhir`.`id`",
"ORDER BY fhir.sort_date ASC",
"ORDER BY fhir.sort_date DESC",
}, " "),
sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
Expand All @@ -158,7 +158,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveOrderBy
"activityCode": "test_code",
},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{OrderBy: "instantiatesUri"},
Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "instantiatesUri"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
Expand Down Expand Up @@ -193,7 +193,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordOrderByAg
Select: []string{},
Where: map[string]interface{}{},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{OrderBy: "id"},
Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "id"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
Expand Down Expand Up @@ -230,7 +230,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexOrderByAg
"code": "test_code",
},
From: "Observation",
Aggregations: &models.QueryResourceAggregations{OrderBy: "valueString:value"},
Aggregations: &models.QueryResourceAggregations{OrderBy: &models.QueryResourceAggregation{Field: "valueString:value"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
Expand Down Expand Up @@ -267,7 +267,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveCountBy
"activityCode": "test_code",
},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{CountBy: "instantiatesUri"},
Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "instantiatesUri"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
Expand Down Expand Up @@ -304,7 +304,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithKeywordCountByAg
"activityCode": "test_code",
},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{CountBy: "source_resource_type"},
Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "source_resource_type"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
Expand Down Expand Up @@ -339,7 +339,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithWildcardCountByA
Select: []string{},
Where: map[string]interface{}{},
From: "CarePlan",
Aggregations: &models.QueryResourceAggregations{CountBy: "*"},
Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "*"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
Expand Down Expand Up @@ -376,7 +376,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexCountByAg
"code": "test_code",
},
From: "Observation",
Aggregations: &models.QueryResourceAggregations{CountBy: "code:code"},
Aggregations: &models.QueryResourceAggregations{CountBy: &models.QueryResourceAggregation{Field: "code:code"}},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
Expand All @@ -398,3 +398,120 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexCountByAg
"test_code", "00000000-0000-0000-0000-000000000000",
})
}

func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithComplexGroupByWithOrderByMaxFnAggregation() {
//setup
sqliteRepo := suite.TestRepository.(*SqliteRepository)
sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true})

//test
authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username")

sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{
Select: []string{},
Where: map[string]interface{}{
"code": "test_code",
},
From: "Observation",
Aggregations: &models.QueryResourceAggregations{
GroupBy: &models.QueryResourceAggregation{Field: "code:code"},
OrderBy: &models.QueryResourceAggregation{Field: "sort_date", Function: "max"},
},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
statement := sqlQuery.Find(&results).Statement
sqlString := statement.SQL.String()
sqlParams := statement.Vars

//assert
require.NoError(suite.T(), err)
require.Equal(suite.T(),
strings.Join([]string{
"SELECT (codeJson.value ->> '$.code') as label, max(fhir.sort_date) as value",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?)",
"GROUP BY (codeJson.value ->> '$.code')",
"ORDER BY max(fhir.sort_date) DESC",
}, " "), sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
"test_code", "00000000-0000-0000-0000-000000000000",
})
}

func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoModifier() {
//setup
sqliteRepo := suite.TestRepository.(*SqliteRepository)
sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true})

//test
authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username")

sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{
Select: []string{},
Where: map[string]interface{}{},
From: "Observation",
Aggregations: &models.QueryResourceAggregations{
GroupBy: &models.QueryResourceAggregation{Field: "code"},
},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
statement := sqlQuery.Find(&results).Statement
sqlString := statement.SQL.String()
sqlParams := statement.Vars

//assert
require.NoError(suite.T(), err)
require.Equal(suite.T(),
strings.Join([]string{
"SELECT ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code')) as label, count(*) as value",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE (user_id = ?)",
"GROUP BY ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code'))",
"ORDER BY count(*) DESC",
}, " "), sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
"00000000-0000-0000-0000-000000000000",
})
}

func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenGroupByNoModifierWithLimit() {
//setup
sqliteRepo := suite.TestRepository.(*SqliteRepository)
sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true})

//test
authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username")

limit := 10
sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{
Select: []string{},
Where: map[string]interface{}{},
From: "Observation",
Limit: &limit,
Aggregations: &models.QueryResourceAggregations{
GroupBy: &models.QueryResourceAggregation{Field: "code"},
},
})
require.NoError(suite.T(), err)
var results []map[string]interface{}
statement := sqlQuery.Find(&results).Statement
sqlString := statement.SQL.String()
sqlParams := statement.Vars

//assert
require.NoError(suite.T(), err)
require.Equal(suite.T(),
strings.Join([]string{
"SELECT ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code')) as label, count(*) as value",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE (user_id = ?)",
"GROUP BY ((codeJson.value ->> '$.system') || '|' || (codeJson.value ->> '$.code'))",
"ORDER BY count(*) DESC",
"LIMIT 10",
}, " "), sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
"00000000-0000-0000-0000-000000000000",
})
}
53 changes: 52 additions & 1 deletion backend/pkg/database/sqlite_repository_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ func TestProcessSearchParameterValue(t *testing.T) {
{SearchParameter{Type: "token", Name: "identifier", Modifier: "otype"}, "http://terminology.hl7.org/CodeSystem/v2-0203|MR|446053", SearchParameterValue{Value: "MR|446053", Prefix: "", SecondaryValues: map[string]interface{}{"identifierSystem": "http://terminology.hl7.org/CodeSystem/v2-0203"}}, false},
{SearchParameter{Type: "token", Name: "code", Modifier: ""}, "|", SearchParameterValue{}, true}, //empty value should throw an error
{SearchParameter{Type: "token", Name: "code", Modifier: ""}, "", SearchParameterValue{}, true}, //empty value should throw an error
{SearchParameter{Type: "token", Name: "code", Modifier: ""}, "http://acme.org/conditions/codes|", SearchParameterValue{Value: "", Prefix: "", SecondaryValues: map[string]interface{}{"codeSystem": "http://acme.org/conditions/codes"}}, false},
{SearchParameter{Type: "token", Name: "code", Modifier: ""}, "|807-1", SearchParameterValue{Value: "807-1", Prefix: "", SecondaryValues: map[string]interface{}{"codeSystem": ""}}, false},

{SearchParameter{Type: "quantity", Name: "valueQuantity", Modifier: ""}, "5.4|http://unitsofmeasure.org|mg", SearchParameterValue{Value: float64(5.4), Prefix: "", SecondaryValues: map[string]interface{}{"valueQuantitySystem": "http://unitsofmeasure.org", "valueQuantityCode": "mg"}}, false},
{SearchParameter{Type: "quantity", Name: "valueQuantity", Modifier: ""}, "5.40e-3|http://unitsofmeasure.org|g", SearchParameterValue{Value: float64(0.0054), Prefix: "", SecondaryValues: map[string]interface{}{"valueQuantitySystem": "http://unitsofmeasure.org", "valueQuantityCode": "g"}}, false},
Expand Down Expand Up @@ -204,6 +206,55 @@ func TestSearchCodeToFromClause(t *testing.T) {

}

//Aggregation tests

// mimic tests from https://hl7.org/fhir/r4/search.html#token
func TestProcessAggregationParameter(t *testing.T) {
//setup
t.Parallel()
var processSearchParameterTests = []struct {
aggregationFieldWithFn models.QueryResourceAggregation // input
searchParameterLookup map[string]string // input (allowed search parameters)
expected AggregationParameter
expectedError bool // expected result
}{
//primitive types
{models.QueryResourceAggregation{Field: "test"}, map[string]string{"test": "keyword"}, AggregationParameter{SearchParameter: SearchParameter{Type: "keyword", Name: "test", Modifier: ""}}, false},
{models.QueryResourceAggregation{Field: "test"}, map[string]string{"test": "number"}, AggregationParameter{SearchParameter: SearchParameter{Type: "number", Name: "test", Modifier: ""}}, false},
{models.QueryResourceAggregation{Field: "test"}, map[string]string{"test": "uri"}, AggregationParameter{SearchParameter: SearchParameter{Type: "uri", Name: "test", Modifier: ""}}, false},
{models.QueryResourceAggregation{Field: "test"}, map[string]string{"test": "date"}, AggregationParameter{SearchParameter: SearchParameter{Type: "date", Name: "test", Modifier: ""}}, false},

{models.QueryResourceAggregation{Field: "test:hello"}, map[string]string{"test": "keyword"}, AggregationParameter{SearchParameter: SearchParameter{Type: "date", Name: "test", Modifier: ""}}, true}, //cannot have a modifier
{models.QueryResourceAggregation{Field: "test:hello"}, map[string]string{"test": "number"}, AggregationParameter{SearchParameter: SearchParameter{Type: "date", Name: "test", Modifier: ""}}, true}, //cannot have a modifier
{models.QueryResourceAggregation{Field: "test:hello"}, map[string]string{"test": "uri"}, AggregationParameter{SearchParameter: SearchParameter{Type: "date", Name: "test", Modifier: ""}}, true}, //cannot have a modifier
{models.QueryResourceAggregation{Field: "test:hello"}, map[string]string{"test": "date"}, AggregationParameter{SearchParameter: SearchParameter{Type: "date", Name: "test", Modifier: ""}}, true}, //cannot have a modifier

//complex types
{models.QueryResourceAggregation{Field: "test"}, map[string]string{"test": "reference"}, AggregationParameter{SearchParameter: SearchParameter{Type: "reference", Name: "test", Modifier: ""}}, true}, //complex types should throw an error when missing modifier
{models.QueryResourceAggregation{Field: "test"}, map[string]string{"test": "string"}, AggregationParameter{SearchParameter: SearchParameter{Type: "string", Name: "test", Modifier: ""}}, true}, //complex types should throw an error when missing modifier
{models.QueryResourceAggregation{Field: "test"}, map[string]string{"test": "quantity"}, AggregationParameter{SearchParameter: SearchParameter{Type: "quantity", Name: "test", Modifier: ""}}, true}, //complex types should throw an error when missing modifier

{models.QueryResourceAggregation{Field: "test:hello"}, map[string]string{"test": "reference"}, AggregationParameter{SearchParameter: SearchParameter{Type: "reference", Name: "test", Modifier: "hello"}}, false},
{models.QueryResourceAggregation{Field: "test:hello"}, map[string]string{"test": "string"}, AggregationParameter{SearchParameter: SearchParameter{Type: "string", Name: "test", Modifier: "hello"}}, false},
{models.QueryResourceAggregation{Field: "test:hello"}, map[string]string{"test": "quantity"}, AggregationParameter{SearchParameter: SearchParameter{Type: "quantity", Name: "test", Modifier: "hello"}}, false},

//token type
{models.QueryResourceAggregation{Field: "code"}, map[string]string{"code": "token"}, AggregationParameter{SearchParameter: SearchParameter{Type: "token", Name: "code", Modifier: ""}}, false},
{models.QueryResourceAggregation{Field: "code:code"}, map[string]string{"code": "token"}, AggregationParameter{SearchParameter: SearchParameter{Type: "token", Name: "code", Modifier: "code"}}, false},
}

//test && assert
for ndx, tt := range processSearchParameterTests {
actual, actualErr := ProcessAggregationParameter(tt.aggregationFieldWithFn, tt.searchParameterLookup)
if tt.expectedError {
require.Error(t, actualErr, "Expected error but got none for processAggregationParameterTests[%d] %s", ndx, tt.aggregationFieldWithFn)
} else {
require.NoError(t, actualErr, "Expected no error but got one for processAggregationParameterTests[%d] %s", ndx, tt.aggregationFieldWithFn)
require.Equal(t, tt.expected, actual)
}
}
}

func (suite *RepositoryTestSuite) TestQueryResources_SQL() {
//setup
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
Expand Down Expand Up @@ -245,7 +296,7 @@ func (suite *RepositoryTestSuite) TestQueryResources_SQL() {
"SELECT fhir.*",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?) GROUP BY `fhir`.`id`",
"ORDER BY fhir.sort_date ASC"}, " "))
"ORDER BY fhir.sort_date DESC"}, " "))
require.Equal(suite.T(), sqlParams, []interface{}{
"test_code", "00000000-0000-0000-0000-000000000000",
})
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (s *FhirAccount) GetSearchParameters() map[string]string {
"owner": "reference",
"period": "date",
"profile": "reference",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_adverse_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func (s *FhirAdverseEvent) GetSearchParameters() map[string]string {
"resultingcondition": "reference",
"seriousness": "token",
"severity": "token",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_allergy_intolerance.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func (s *FhirAllergyIntolerance) GetSearchParameters() map[string]string {
"recorder": "reference",
"route": "token",
"severity": "token",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_appointment.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func (s *FhirAppointment) GetSearchParameters() map[string]string {
"serviceCategory": "token",
"serviceType": "token",
"slot": "reference",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (s *FhirBinary) GetSearchParameters() map[string]string {
"language": "token",
"lastUpdated": "date",
"profile": "reference",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_care_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func (s *FhirCarePlan) GetSearchParameters() map[string]string {
"performer": "reference",
"profile": "reference",
"replaces": "reference",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_care_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func (s *FhirCareTeam) GetSearchParameters() map[string]string {
"lastUpdated": "date",
"participant": "reference",
"profile": "reference",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func (s *FhirClaim) GetSearchParameters() map[string]string {
"procedureUdi": "reference",
"profile": "reference",
"provider": "reference",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_claim_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func (s *FhirClaimResponse) GetSearchParameters() map[string]string {
"profile": "reference",
"request": "reference",
"requestor": "reference",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_composition.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func (s *FhirComposition) GetSearchParameters() map[string]string {
"relatedId": "token",
"relatedRef": "reference",
"section": "token",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func (s *FhirCondition) GetSearchParameters() map[string]string {
"profile": "reference",
"recordedDate": "date",
"severity": "token",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_consent.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func (s *FhirConsent) GetSearchParameters() map[string]string {
"purpose": "token",
"scope": "token",
"securityLabel": "token",
"sort_date": "date",
"sourceReference": "reference",
"source_id": "keyword",
"source_resource_id": "keyword",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/models/database/fhir_coverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (s *FhirCoverage) GetSearchParameters() map[string]string {
"payor": "reference",
"policyHolder": "reference",
"profile": "reference",
"sort_date": "date",
"source_id": "keyword",
"source_resource_id": "keyword",
"source_resource_type": "keyword",
Expand Down
Loading

0 comments on commit 0b99549

Please sign in to comment.