adding tests to ensure that the same field with multiple modifiers is correctly handled.

adding IPS query logic.
Adding IPS sections.

TODO: tests broken.
This commit is contained in:
Jason Kulatunga 2024-02-20 10:22:19 -08:00
parent a5eba794b5
commit 68859546c0
No known key found for this signature in database
4 changed files with 345 additions and 2 deletions

View File

@ -10,6 +10,8 @@ type DatabaseRepositoryType string
type InstallationVerificationStatus string
type InstallationQuotaStatus string
type IPSSections string
const (
ResourceListPageSize int = 20
@ -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"
)

View File

@ -3,6 +3,7 @@ package database
import (
"context"
"fmt"
"log"
"sort"
"strconv"
"strings"
@ -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)

View File

@ -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})
@ -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",
})
@ -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)

View File

@ -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
}