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:
parent
a5eba794b5
commit
68859546c0
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue