Skip to content

Commit

Permalink
adding tests to ensure that the same field with multiple modifiers is…
Browse files Browse the repository at this point in the history
… correctly handled.

adding IPS query logic.
Adding IPS sections.

TODO: tests broken.
  • Loading branch information
AnalogJ committed Feb 20, 2024
1 parent 22c9aba commit f3de906
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 2 deletions.
17 changes: 17 additions & 0 deletions backend/pkg/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type DatabaseRepositoryType string
type InstallationVerificationStatus string
type InstallationQuotaStatus string

type IPSSections string

const (
ResourceListPageSize int = 20

Expand Down Expand Up @@ -50,4 +52,19 @@ const (
InstallationVerificationStatusVerified InstallationVerificationStatus = "VERIFIED" //email has been verified
InstallationQuotaStatusActive InstallationQuotaStatus = "ACTIVE"
InstallationQuotaStatusConsumed InstallationQuotaStatus = "CONSUMED"

IPSSectionsMedicationSummary IPSSections = "medication_summary"
IPSSectionsAllergiesIntolerances IPSSections = "allergies_intolerances"
IPSSectionsProblemList IPSSections = "problem_list"
IPSSectionsImmunizations IPSSections = "immunizations"
IPSSectionsHistoryOfProcedures IPSSections = "history_of_procedures"
IPSSectionsMedicalDevices IPSSections = "medical_devices"
IPSSectionsDiagnosticResults IPSSections = "diagnostic_results"
IPSSectionsVitalSigns IPSSections = "vital_signs"
IPSSectionsHistoryOfIllnesses IPSSections = "history_of_illnesses"
IPSSectionsPregnancy IPSSections = "pregnancy"
IPSSectionsSocialHistory IPSSections = "social_history"
IPSSectionsPlanOfCare IPSSections = "plan_of_care"
IPSSectionsFunctionalStatus IPSSections = "functional_status"
IPSSectionsAdvanceDirectives IPSSections = "advance_directives"
)
10 changes: 10 additions & 0 deletions backend/pkg/database/gorm_repository_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package database
import (
"context"
"fmt"
"log"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -230,6 +231,15 @@ func (gr *GormRepository) sqlQueryResources(ctx context.Context, query models.Qu
}
}

log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
log.Printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
log.Printf("whereClauses: %v", whereClauses)
log.Printf("whereNamedParameters: %v", whereNamedParameters)

//ensure Where and From clauses are unique
whereClauses = lo.Uniq(whereClauses)
whereClauses = lo.Compact(whereClauses)
Expand Down
79 changes: 77 additions & 2 deletions backend/pkg/database/gorm_repository_query_sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenWithNotModi
})
}

func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleValuesWithNotModifier() {
func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleANDValuesWithNotModifier() {
//setup
sqliteRepo := suite.TestRepository.(*GormRepository)
sqliteRepo.GormClient = sqliteRepo.GormClient.Session(&gorm.Session{DryRun: true})
Expand All @@ -197,7 +197,7 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleVal
sqlQuery, err := sqliteRepo.sqlQueryResources(authContext, models.QueryResource{
Select: []string{},
Where: map[string]interface{}{
"code:not": []string{"test_code", "test_code2"},
"code:not": []string{"test_code", "test_code2"}, //AND condition
},
From: "Observation",
})
Expand All @@ -223,6 +223,81 @@ func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleVal
})
}

func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMultipleORValues() {
//setup
sqliteRepo := suite.TestRepository.(*GormRepository)
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,test_code2", //OR condition
},
From: "Observation",
})
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 fhir.*",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE ((codeJson.value ->> '$.code' = ?)) OR ((codeJson.value ->> '$.code' = ?)) AND (user_id = ?)",
"GROUP BY `fhir`.`id`",
"ORDER BY fhir.sort_date DESC",
}, " "),
sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
"test_code", "test_code2", "00000000-0000-0000-0000-000000000000",
})
}

func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithTokenMixedMultipleANDORValues() {
//setup
sqliteRepo := suite.TestRepository.(*GormRepository)
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:not": []string{"test_code", "test_code2", "test_code3"}, //AND condition
"code": "test_code4,test_code5,test_code6", //OR condition
},
From: "Observation",
})
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 fhir.*",
"FROM fhir_observation as fhir, json_each(fhir.code) as codeJson",
"WHERE ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' <> ?)) AND ((codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?) OR (codeJson.value ->> '$.code' = ?)) AND (user_id = ?)",
"GROUP BY `fhir`.`id`",
"ORDER BY fhir.sort_date DESC",
}, " "),
sqlString)
require.Equal(suite.T(), sqlParams, []interface{}{
"test_code", "test_code2", "test_code3", "test_code4", "00000000-0000-0000-0000-000000000000",
})
}

func (suite *RepositorySqlTestSuite) TestQueryResources_SQL_WithPrimitiveOrderByAggregation() {
//setup
sqliteRepo := suite.TestRepository.(*GormRepository)
Expand Down
241 changes: 241 additions & 0 deletions backend/pkg/database/gorm_repository_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package database

import (
"context"
"github.com/fastenhealth/fasten-onprem/backend/pkg"
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
databaseModel "github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
)

// GetInternationalPatientSummary will generate an IPS bundle, which can then be used to generate a IPS QR code, PDF or JSON bundle
// The IPS bundle will contain a summary of all the data in the system, including a list of all sources, and the main Patient
// See: https://github.com/fastenhealth/fasten-onprem/issues/170
// See: https://github.com/jddamore/fhir-ips-server/blob/main/docs/Summary_Creation_Steps.md
func (gr *GormRepository) GetInternationalPatientSummary(ctx context.Context) (*models.Summary, error) {
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
if currentUserErr != nil {
return nil, currentUserErr
}

// we want a count of all resources for this user by type
var resourceCountResults []map[string]interface{}

resourceTypes := databaseModel.GetAllowedResourceTypes()
for _, resourceType := range resourceTypes {
tableName, err := databaseModel.GetTableNameByResourceType(resourceType)
if err != nil {
return nil, err
}
var count int64

gr.QueryResources(ctx, models.QueryResource{
Use: "",
Select: nil,
From: "",
Where: nil,
Limit: nil,
Offset: nil,
Aggregations: nil,
})

result := gr.GormClient.WithContext(ctx).
Table(tableName).
Where(models.OriginBase{
UserID: currentUser.ID,
}).
Count(&count)
if result.Error != nil {
return nil, result.Error
}
if count == 0 {
continue //don't add resource counts if the count is 0
}
resourceCountResults = append(resourceCountResults, map[string]interface{}{
"resource_type": resourceType,
"count": count,
})
}

// we want a list of all sources (when they were last updated)
sources, err := gr.GetSources(ctx)
if err != nil {
return nil, err
}

// we want the main Patient for each source
patients, err := gr.GetPatientForSources(ctx)
if err != nil {
return nil, err
}

if resourceCountResults == nil {
resourceCountResults = []map[string]interface{}{}
}
summary := &models.Summary{
Sources: sources,
ResourceTypeCounts: resourceCountResults,
Patients: patients,
}

return summary, nil
}

// https://github.com/jddamore/fhir-ips-server/blob/main/docs/Summary_Creation_Steps.md
func generateIPSSectionQueries(sectionType pkg.IPSSections) ([]models.QueryResource, error) {

queries := []models.QueryResource{}
switch sectionType {
case pkg.IPSSectionsAllergiesIntolerances:
queries = append(queries, models.QueryResource{
Select: nil,
From: "AllergyIntolerance",
Where: map[string]interface{}{
"clinicalStatus:not": []string{"inactive", "resolved"},
"verificationStatus:not": []string{"entered-in-error"},
},
})
break
case pkg.IPSSectionsProblemList:
queries = append(queries, models.QueryResource{
Select: nil,
From: "Condition",
Where: map[string]interface{}{
"clinicalStatus:not": []string{"inactive", "resolved"},
"verificationStatus:not": []string{"entered-in-error"},
},
})
break
case pkg.IPSSectionsMedicationSummary:
queries = append(queries, models.QueryResource{
Select: nil,
From: "MedicationStatement",
Where: map[string]interface{}{
"status": "active,intended,unknown,on-hold",
},
})
queries = append(queries, models.QueryResource{
Select: nil,
From: "MedicationRequest",
Where: map[string]interface{}{
"status": "active,unknown,on-hold",
},
})
break
case pkg.IPSSectionsDiagnosticResults:
queries = append(queries, models.QueryResource{
Select: nil,
From: "DiagnosticReport",
Where: map[string]interface{}{
"category": "LAB",
},
})

//TODO: group by code, sort by date, limit to the most recent 3
queries = append(queries, models.QueryResource{
Select: nil,
From: "Observation",
Where: map[string]interface{}{
"category": "laboratory",
"status:not": "preliminary",
},
})
break
case pkg.IPSSectionsVitalSigns:
//TODO: group by code, sort by date, limit to the most recent 3
queries = append(queries, models.QueryResource{
Select: nil,
From: "Observation",
Where: map[string]interface{}{
"category": "vital-signs",
},
})
break
case pkg.IPSSectionsSocialHistory:
queries = append(queries, models.QueryResource{
Select: nil,
From: "Observation",
Where: map[string]interface{}{
"category": "social-history",
"status:not": "preliminary",
},
})
break
case pkg.IPSSectionsPregnancy:
//TODO: determine the code for pregnancy from IPS specification
queries = append(queries, models.QueryResource{
Select: nil,
From: "Observation",
Where: map[string]interface{}{
"status:not": "preliminary",
},
})
break
case pkg.IPSSectionsImmunizations:
queries = append(queries, models.QueryResource{
Select: nil,
From: "Immunization",
Where: map[string]interface{}{
"status:not": "entered-in-error",
},
})
break
case pkg.IPSSectionsAdvanceDirectives:
queries = append(queries, models.QueryResource{
Select: nil,
From: "Consent",
Where: map[string]interface{}{
"status": "active",
},
})
break
case pkg.IPSSectionsFunctionalStatus:
queries = append(queries, models.QueryResource{
Select: nil,
From: "ClinicalImpression",
Where: map[string]interface{}{
"status": "in-progress,completed",
},
})
break
case pkg.IPSSectionsMedicalDevices:
queries = append(queries, models.QueryResource{
Select: nil,
From: "Device",
Where: map[string]interface{}{
"status": "entered-in-error",
},
})
break
case pkg.IPSSectionsHistoryOfIllnesses:
//TODO: last updated date should be older than 5 years (dateTime or period.high)
//TODO: check if where clause with multiple modifiers for the same field works as expected
queries = append(queries, models.QueryResource{
Select: nil,
From: "Condition",
Where: map[string]interface{}{
"clinicalStatus:not": []string{"entered-in-error"},
"clinicalStatus": "inactive,remission,resolved",
},
})
break
case pkg.IPSSectionsPlanOfCare:
queries = append(queries, models.QueryResource{
Select: nil,
From: "CarePlan",
Where: map[string]interface{}{
"status": "active,on-hold,unknown",
},
})
break
case pkg.IPSSectionsHistoryOfProcedures:
queries = append(queries, models.QueryResource{
Select: nil,
From: "Procedure",
Where: map[string]interface{}{
"status:not": []string{"entered-in-error", "not-done"},
},
})
}

return queries, nil
}

0 comments on commit f3de906

Please sign in to comment.