working Patient record merge. Fixing IPS Export Data model for more flexibility.

This commit is contained in:
Jason Kulatunga 2024-03-08 15:09:47 -08:00
parent 182d56fb88
commit af1c033a5c
No known key found for this signature in database
12 changed files with 556 additions and 42 deletions

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/TwiN/deepmerge"
"github.com/fastenhealth/fasten-onprem/backend/pkg"
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
"github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
@ -15,13 +16,33 @@ import (
"time"
)
func (gr *GormRepository) GetInternationalPatientSummaryBundle(ctx context.Context) (interface{}, interface{}, error) {
// returns IPSBundle and IPSComposition
func (gr *GormRepository) GetInternationalPatientSummaryExport(ctx context.Context) (*ips.InternationalPatientSummaryExportData, error) {
exportData := &ips.InternationalPatientSummaryExportData{}
summaryTime := time.Now()
timestamp := summaryTime.Format(time.RFC3339)
exportData.GenerationDate = summaryTime
//get a list of all Patients associated with this user (we'll be creating a pseudo Patient for referencing in this Bundle
patient, err := gr.GetPatientMerged(ctx)
if err != nil {
//TODO: determine if we should error out here. If only manually entered records were entered, we wont have a Patient record,
return exportData, err
}
exportData.Patient = patient
//get a list of all Sources associated with this user
sources, err := gr.GetSources(ctx)
if err != nil {
return exportData, err
}
exportData.Sources = sources
narrativeEngine, err := ips.NewNarrative()
if err != nil {
return nil, nil, fmt.Errorf("error creating narrative engine: %w", err)
return exportData, fmt.Errorf("error creating narrative engine: %w", err)
}
//Algorithm to create the IPS bundle
@ -42,7 +63,7 @@ func (gr *GormRepository) GetInternationalPatientSummaryBundle(ctx context.Conte
//Step 1. Generate the IPS Section Lists
summarySectionQueryResults, err := gr.getInternationalPatientSummarySectionResources(ctx)
if err != nil {
return nil, nil, err
return exportData, err
}
//Step 2. Create the Composition Section
@ -50,7 +71,7 @@ func (gr *GormRepository) GetInternationalPatientSummaryBundle(ctx context.Conte
for sectionType, sectionQueryResultsList := range summarySectionQueryResults {
compositionSection, err := generateIPSCompositionSection(narrativeEngine, sectionType, sectionQueryResultsList)
if err != nil {
return nil, nil, err
return exportData, err
}
compositionSections = append(compositionSections, *compositionSection)
}
@ -60,6 +81,7 @@ func (gr *GormRepository) GetInternationalPatientSummaryBundle(ctx context.Conte
//TODO: Step 4. Create a Fasten Health Organization resource.
compositionUUID := uuid.New().String()
patientReference := fmt.Sprintf("%s/%s", exportData.Patient.GetSourceResourceType(), exportData.Patient.GetSourceResourceID())
//Step 5. Create the IPS Composition
ipsComposition := &fhir401.Composition{
@ -83,7 +105,7 @@ func (gr *GormRepository) GetInternationalPatientSummaryBundle(ctx context.Conte
},
},
Subject: &fhir401.Reference{
Reference: stringPtr("Patient/123"), //TODO
Reference: stringPtr(patientReference), //TODO
},
Date: timestamp,
Author: []fhir401.Reference{
@ -97,7 +119,7 @@ func (gr *GormRepository) GetInternationalPatientSummaryBundle(ctx context.Conte
Mode: fhir401.CompositionAttestationModePersonal,
Time: &timestamp,
Party: &fhir401.Reference{
Reference: stringPtr("Patient/123"),
Reference: stringPtr(patientReference),
},
},
},
@ -138,7 +160,7 @@ func (gr *GormRepository) GetInternationalPatientSummaryBundle(ctx context.Conte
// Add the Composition to the bundle
ipsCompositionJson, err := json.Marshal(ipsComposition)
if err != nil {
return nil, nil, err
return exportData, err
}
ipsBundle.Entry = append(ipsBundle.Entry, fhir401.BundleEntry{
Resource: json.RawMessage(ipsCompositionJson),
@ -156,7 +178,10 @@ func (gr *GormRepository) GetInternationalPatientSummaryBundle(ctx context.Conte
// }
//}
return ipsBundle, ipsComposition, nil
exportData.Bundle = ipsBundle
exportData.Composition = ipsComposition
return exportData, nil
}
// GetInternationalPatientSummary will generate an IPS bundle, which can then be used to generate a IPS QR code, PDF or JSON bundle
@ -697,11 +722,64 @@ func flattenQueryResultsToResourcesList(queryResultsList []any) []database.IFhir
return resources
}
func generateIPSSectionNarrative(sectionType pkg.IPSSections, resources []models.ResourceBase) string {
return ""
// When given a list of Patient database records, we need to merge them together to a Patient record that's usable by the
func (gr *GormRepository) GetPatientMerged(ctx context.Context) (*database.FhirPatient, error) {
currentUser, currentUserErr := gr.GetCurrentUser(ctx)
if currentUserErr != nil {
return nil, currentUserErr
}
tableName, err := database.GetTableNameByResourceType("Patient")
if err != nil {
return nil, err
}
var wrappedFhirPatients []database.FhirPatient
results := gr.GormClient.WithContext(ctx).
//Group("source_id"). //broken in Postgres.
Where(models.OriginBase{
UserID: currentUser.ID,
SourceResourceType: "Patient",
}).
Order("sort_date DESC").
Table(tableName).
Find(&wrappedFhirPatients)
if results.Error != nil {
return nil, results.Error
}
return mergePatients(wrappedFhirPatients)
}
// helper utility
func stringPtr(s string) *string {
return &s
}
func mergePatients(patients []database.FhirPatient) (*database.FhirPatient, error) {
if len(patients) == 0 {
log.Printf("no patients to merge, ignoring")
return nil, fmt.Errorf("no patients to merge, ignoring")
}
mergedPatientResource := `{}`
for ndx, _ := range patients {
patient := patients[ndx]
mergedPatientResourceBytes, err := deepmerge.JSON([]byte(mergedPatientResource), []byte(patient.ResourceRaw))
if err != nil {
return nil, err
}
mergedPatientResource = string(mergedPatientResourceBytes)
}
mergedPatient := &database.FhirPatient{
ResourceBase: models.ResourceBase{
OriginBase: patients[len(patients)-1].OriginBase,
},
}
err := mergedPatient.PopulateAndExtractSearchParameters([]byte(mergedPatientResource))
if err != nil {
return nil, fmt.Errorf("error occurred while extracting fields from merged Patient")
}
return mergedPatient, nil
}

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/fastenhealth/fasten-onprem/backend/pkg"
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
"github.com/fastenhealth/fasten-onprem/backend/pkg/utils/ips"
sourcePkg "github.com/fastenhealth/fasten-sources/clients/models"
"github.com/google/uuid"
)
@ -21,7 +22,7 @@ type DatabaseRepository interface {
//get a count of every resource type
GetSummary(ctx context.Context) (*models.Summary, error)
GetInternationalPatientSummaryBundle(ctx context.Context) (interface{}, interface{}, error)
GetInternationalPatientSummaryExport(ctx context.Context) (*ips.InternationalPatientSummaryExportData, error)
GetResourceByResourceTypeAndId(context.Context, string, string) (*models.ResourceBase, error)
GetResourceBySourceId(context.Context, string, string) (*models.ResourceBase, error)

View File

@ -0,0 +1,17 @@
package ips
import (
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
"github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
"github.com/fastenhealth/gofhir-models/fhir401"
"time"
)
type InternationalPatientSummaryExportData struct {
GenerationDate time.Time
Bundle *fhir401.Bundle
Composition *fhir401.Composition
Patient *database.FhirPatient
Sources []models.SourceCredential
}

View File

@ -39,7 +39,7 @@ type NarrativeTemplateData struct {
CarePlan []database.FhirCarePlan
Observation []database.FhirObservation
Encounter []database.FhirEncounter
Patient []database.FhirPatient
Patient database.FhirPatient
}
func NewNarrative() (*Narrative, error) {
@ -49,12 +49,15 @@ func NewNarrative() (*Narrative, error) {
// https://stackoverflow.com/questions/50283370/include-template-from-another-directory
// https://www.digitalocean.com/community/tutorials/how-to-use-templates-in-go
tmpl, err := template.New("ips").Funcs(template.FuncMap{
"safeHTML": func(s *string) template.HTML {
"safeHTMLPtr": func(s *string) template.HTML {
if s == nil {
return template.HTML("")
}
return template.HTML(*s)
},
"safeHTML": func(s string) template.HTML {
return template.HTML(s)
},
"parseStringList": func(data datatypes.JSON) []string {
var parsed []string
_ = json.Unmarshal(data, &parsed)
@ -124,8 +127,9 @@ func (r *Narrative) RenderSection(sectionType pkg.IPSSections, resources []datab
MedicationRequest: []database.FhirMedicationRequest{},
MedicationStatement: []database.FhirMedicationStatement{},
Observation: []database.FhirObservation{},
Patient: []database.FhirPatient{},
Procedure: []database.FhirProcedure{},
Patient: database.FhirPatient{},
}
//loop though the resources, cast them to the correct type, and then store them in the correct array
@ -156,8 +160,6 @@ func (r *Narrative) RenderSection(sectionType pkg.IPSSections, resources []datab
templateData.MedicationStatement = append(templateData.MedicationStatement, *resource.(*database.FhirMedicationStatement))
case "Observation":
templateData.Observation = append(templateData.Observation, *resource.(*database.FhirObservation))
case "Patient":
templateData.Patient = append(templateData.Patient, *resource.(*database.FhirPatient))
case "Procedure":
templateData.Procedure = append(templateData.Procedure, *resource.(*database.FhirProcedure))
}

View File

@ -11,7 +11,7 @@ all of the section narratives into a single narrative.
{{if eq $section.Text nil }}
{{$section.EmptyReason.Text}}
{{else }}
{{$section.Text.Div | safeHTML}}
{{$section.Text.Div | safeHTMLPtr}}
{{end}}
</div>
</section>

View File

@ -1 +1,101 @@
<div> header hello world</div>
{{- /*gotype: github.com/fastenhealth/fasten-onprem/backend/pkg/utils/ips.NarrativeTemplateData*/ -}}
<div class="container">
<div class="row">
<div class="col-8">
<h1>{{.Patient.Name | parseStringList | uniq | first}}</h1>
<div>Patient Health Summary; Generated: {{.Composition.Date | date "2006-01-02"}}</div>
<div>
<strong>ID:</strong> {{.Composition.Identifier.Value}} <br/>
</div>
</div>
<div class="col-4">
<div style="width:100%; display: flex; justify-content: center;" id="outer">
<div id="inner">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
height="100pt" viewBox="0 0 1640.000000 664.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,664.000000) scale(0.100000,-0.100000)"
fill="#5b47fb" stroke="none">
<path d="M3235 4879 c-114 -17 -220 -49 -314 -95 -76 -38 -110 -62 -176 -128
-138 -136 -201 -298 -212 -538 l-6 -128 -123 0 -124 0 0 -280 0 -280 125 0
125 0 0 -835 0 -835 340 0 340 0 0 835 0 835 185 0 185 0 0 280 0 280 -186 0
-187 0 5 79 c7 129 58 203 157 231 20 6 71 13 114 17 l77 6 0 283 0 284 -132
-1 c-73 -1 -160 -5 -193 -10z"/>
<path d="M8330 4260 l0 -270 -135 0 -135 0 0 -280 0 -280 134 0 134 0 5 -532
c3 -460 6 -543 21 -607 56 -241 182 -394 387 -471 116 -43 184 -51 457 -57
l262 -6 0 292 0 291 -159 0 c-170 0 -207 8 -248 52 -40 43 -43 83 -43 571 l0
467 225 0 225 0 0 280 0 280 -225 0 -225 0 0 270 0 270 -340 0 -340 0 0 -270z"/>
<path d="M4378 4010 c-134 -24 -254 -72 -365 -147 -200 -134 -345 -350 -415
-621 -27 -103 -31 -138 -35 -297 -4 -137 -1 -207 11 -294 47 -319 194 -582
419 -747 166 -122 380 -184 596 -170 253 15 458 113 591 282 l45 57 3 -157 3
-156 339 0 340 0 0 1115 0 1115 -340 0 -339 0 -3 -156 -3 -155 -50 62 c-94
119 -226 204 -385 249 -84 24 -326 36 -412 20z m453 -590 c179 -34 324 -176
374 -367 58 -220 -2 -475 -140 -601 -284 -259 -719 -110 -800 273 -19 90 -19
233 0 314 64 271 302 431 566 381z"/>
<path d="M6890 4014 c-201 -37 -265 -56 -385 -117 -113 -57 -238 -182 -288
-287 -84 -177 -86 -375 -4 -550 39 -85 146 -191 242 -242 111 -58 193 -86 455
-153 135 -35 270 -72 301 -84 205 -78 209 -271 7 -332 -124 -38 -274 -13 -366
60 -40 33 -84 103 -96 154 l-6 27 -336 0 -335 0 6 -47 c10 -70 58 -205 96
-271 19 -31 77 -100 129 -152 87 -88 106 -101 220 -158 514 -256 1200 -127
1407 263 119 226 76 534 -99 701 -128 122 -255 177 -632 275 -125 32 -249 68
-274 79 -105 47 -147 101 -140 180 10 103 91 154 243 154 123 0 183 -21 251
-89 38 -37 57 -66 67 -101 l14 -49 313 -3 313 -2 -7 52 c-11 85 -63 227 -114
312 -105 176 -303 308 -542 361 -73 16 -382 29 -440 19z"/>
<path d="M10435 4009 c-291 -42 -536 -179 -700 -390 -170 -218 -250 -515 -226
-835 6 -71 15 -154 21 -184 113 -560 563 -896 1160 -866 129 6 273 35 380 76
291 113 517 354 605 647 l13 43 -362 0 -361 0 -20 -36 c-29 -55 -118 -130
-183 -155 -74 -29 -211 -32 -293 -7 -147 46 -248 170 -280 346 -6 29 -7 57 -4
62 4 6 279 10 775 10 l768 0 8 88 c4 49 2 141 -4 212 -48 540 -374 902 -886
985 -98 16 -313 18 -411 4z m265 -540 c81 -12 175 -55 229 -104 52 -47 100
-145 108 -218 l6 -57 -431 0 -430 0 9 43 c29 127 119 247 224 297 94 45 178
56 285 39z"/>
<path d="M13070 3995 c-166 -34 -295 -101 -407 -210 l-83 -80 0 142 0 143
-340 0 -340 0 0 -1115 0 -1115 340 0 340 0 0 655 c0 446 4 671 11 707 26 120
111 233 213 283 28 14 85 30 127 36 194 28 367 -51 445 -202 58 -113 57 -108
61 -821 l4 -658 335 0 335 0 -4 743 c-3 707 -4 746 -24 832 -86 373 -308 599
-653 661 -94 17 -275 16 -360 -1z"/>
</g>
</svg>
</div>
</div>
<strong>Author:</strong> {{.Composition.Identifier.System}} <br/>
</div>
</div>
</div>
<div class="markdown-body">
<table class="hapiPropertyTable">
<thead>
<tr>
<th>Born</th>
<th>Language</th>
<th>Race/Ethnicity</th>
<th>Address</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<tr>
<td>
{{default "Unknown" (.Patient.Birthdate | date "2006-01-02")}}
</td>
<td>
{{default "Unknown" (.Patient.Language | parseMapList | pluckList "text" | join ",")}}
</td>
<td>
Unknown / Unknown
</td>
<td>
{{default "Unknown" (.Patient.Address | parseStringList | uniq | join "<br/>" | safeHTML)}}
</td>
<td>
{{default "Unknown" (.Patient.Phone | parseStringList | uniq | join ", ")}}
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,275 @@
<style>
/**
*** SIMPLE GRID
*** (C) ZACH COLE 2016
*** https://github.com/zachacole/Simple-Grid/blob/master/simple-grid.css
**/
@import url(https://fonts.googleapis.com/css?family=Lato:400,300,300italic,400italic,700,700italic);
/* UNIVERSAL */
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
left: 0;
top: 0;
font-size: 100%;
}
/* ROOT FONT STYLES */
* {
font-family: 'Lato', Helvetica, sans-serif;
color: #333447;
line-height: 1.5;
}
/* TYPOGRAPHY */
h1 {
font-size: 2.5rem;
}
h2 {
font-size: 2rem;
}
h3 {
font-size: 1.375rem;
}
h4 {
font-size: 1.125rem;
}
h5 {
font-size: 1rem;
}
h6 {
font-size: 0.875rem;
}
p {
font-size: 1.125rem;
font-weight: 200;
line-height: 1.8;
}
.font-light {
font-weight: 300;
}
.font-regular {
font-weight: 400;
}
.font-heavy {
font-weight: 700;
}
/* POSITIONING */
.left {
text-align: left;
}
.right {
text-align: right;
}
.center {
text-align: center;
margin-left: auto;
margin-right: auto;
}
.justify {
text-align: justify;
}
/* ==== GRID SYSTEM ==== */
.container {
width: 90%;
margin-left: auto;
margin-right: auto;
}
.row {
position: relative;
width: 100%;
}
.row [class^="col"] {
float: left;
margin: 0.5rem 2%;
min-height: 0.125rem;
}
.col-1,
.col-2,
.col-3,
.col-4,
.col-5,
.col-6,
.col-7,
.col-8,
.col-9,
.col-10,
.col-11,
.col-12 {
width: 96%;
}
.col-1-sm {
width: 4.33%;
}
.col-2-sm {
width: 12.66%;
}
.col-3-sm {
width: 21%;
}
.col-4-sm {
width: 29.33%;
}
.col-5-sm {
width: 37.66%;
}
.col-6-sm {
width: 46%;
}
.col-7-sm {
width: 54.33%;
}
.col-8-sm {
width: 62.66%;
}
.col-9-sm {
width: 71%;
}
.col-10-sm {
width: 79.33%;
}
.col-11-sm {
width: 87.66%;
}
.col-12-sm {
width: 96%;
}
.row::after {
content: "";
display: table;
clear: both;
}
.hidden-sm {
display: none;
}
@media only screen and (min-width: 33.75em) { /* 540px */
.container {
width: 80%;
}
}
@media only screen and (min-width: 45em) { /* 720px */
.col-1 {
width: 4.33%;
}
.col-2 {
width: 12.66%;
}
.col-3 {
width: 21%;
}
.col-4 {
width: 29.33%;
}
.col-5 {
width: 37.66%;
}
.col-6 {
width: 46%;
}
.col-7 {
width: 54.33%;
}
.col-8 {
width: 62.66%;
}
.col-9 {
width: 71%;
}
.col-10 {
width: 79.33%;
}
.col-11 {
width: 87.66%;
}
.col-12 {
width: 96%;
}
.hidden-sm {
display: block;
}
}
@media only screen and (min-width: 60em) { /* 960px */
.container {
width: 75%;
max-width: 60rem;
}
}
</style>
<!-- Github Markdown CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-light.min.css" integrity="sha512-Pmhg2i/F7+5+7SsdoUqKeH7UAZoVMYb1sxGOoJ0jWXAEHP0XV2H4CITyK267eHWp2jpj7rtqWNkmEOw1tNyYpg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- Custom CSS -->
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>

View File

@ -1,28 +1,12 @@
{{- /*gotype: github.com/fastenhealth/fasten-onprem/backend/pkg/utils/ips.NarrativeTemplateData*/ -}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>title</title>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-light.min.css" integrity="sha512-Pmhg2i/F7+5+7SsdoUqKeH7UAZoVMYb1sxGOoJ0jWXAEHP0XV2H4CITyK267eHWp2jpj7rtqWNkmEOw1tNyYpg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
{{template "styles.gohtml" .}}
</head>
<body>

View File

@ -5,7 +5,6 @@ import (
"github.com/fastenhealth/fasten-onprem/backend/pkg"
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
"github.com/fastenhealth/fasten-onprem/backend/pkg/utils/ips"
"github.com/fastenhealth/gofhir-models/fhir401"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
@ -28,7 +27,7 @@ func GetIPSSummary(c *gin.Context) {
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
ipsBundle, ipsComposititon, err := databaseRepo.GetInternationalPatientSummaryBundle(c)
ipsExport, err := databaseRepo.GetInternationalPatientSummaryExport(c)
if err != nil {
logger.Errorln("An error occurred while retrieving IPS summary", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
@ -36,7 +35,7 @@ func GetIPSSummary(c *gin.Context) {
}
if c.Query("format") == "" {
c.JSON(http.StatusOK, gin.H{"success": true, "data": ipsBundle})
c.JSON(http.StatusOK, gin.H{"success": true, "data": ipsExport.Bundle})
return
}
@ -47,13 +46,13 @@ func GetIPSSummary(c *gin.Context) {
return
}
composititon := ipsComposititon.(*fhir401.Composition)
composititon := ipsExport.Composition
if c.Query("format") == "html" {
logger.Debugln("Rendering HTML")
//create string writer
content, err := narrative.RenderTemplate("index.gohtml", ips.NarrativeTemplateData{Composition: composititon})
content, err := narrative.RenderTemplate("index.gohtml", ips.NarrativeTemplateData{Composition: composititon, Patient: *ipsExport.Patient})
if err != nil {
logger.Errorln("An error occurred while executing template", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})

View File

@ -0,0 +1,49 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1640.000000pt" height="664.000000pt" viewBox="0 0 1640.000000 664.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,664.000000) scale(0.100000,-0.100000)"
fill="#5b47fb" stroke="none">
<path d="M3235 4879 c-114 -17 -220 -49 -314 -95 -76 -38 -110 -62 -176 -128
-138 -136 -201 -298 -212 -538 l-6 -128 -123 0 -124 0 0 -280 0 -280 125 0
125 0 0 -835 0 -835 340 0 340 0 0 835 0 835 185 0 185 0 0 280 0 280 -186 0
-187 0 5 79 c7 129 58 203 157 231 20 6 71 13 114 17 l77 6 0 283 0 284 -132
-1 c-73 -1 -160 -5 -193 -10z"/>
<path d="M8330 4260 l0 -270 -135 0 -135 0 0 -280 0 -280 134 0 134 0 5 -532
c3 -460 6 -543 21 -607 56 -241 182 -394 387 -471 116 -43 184 -51 457 -57
l262 -6 0 292 0 291 -159 0 c-170 0 -207 8 -248 52 -40 43 -43 83 -43 571 l0
467 225 0 225 0 0 280 0 280 -225 0 -225 0 0 270 0 270 -340 0 -340 0 0 -270z"/>
<path d="M4378 4010 c-134 -24 -254 -72 -365 -147 -200 -134 -345 -350 -415
-621 -27 -103 -31 -138 -35 -297 -4 -137 -1 -207 11 -294 47 -319 194 -582
419 -747 166 -122 380 -184 596 -170 253 15 458 113 591 282 l45 57 3 -157 3
-156 339 0 340 0 0 1115 0 1115 -340 0 -339 0 -3 -156 -3 -155 -50 62 c-94
119 -226 204 -385 249 -84 24 -326 36 -412 20z m453 -590 c179 -34 324 -176
374 -367 58 -220 -2 -475 -140 -601 -284 -259 -719 -110 -800 273 -19 90 -19
233 0 314 64 271 302 431 566 381z"/>
<path d="M6890 4014 c-201 -37 -265 -56 -385 -117 -113 -57 -238 -182 -288
-287 -84 -177 -86 -375 -4 -550 39 -85 146 -191 242 -242 111 -58 193 -86 455
-153 135 -35 270 -72 301 -84 205 -78 209 -271 7 -332 -124 -38 -274 -13 -366
60 -40 33 -84 103 -96 154 l-6 27 -336 0 -335 0 6 -47 c10 -70 58 -205 96
-271 19 -31 77 -100 129 -152 87 -88 106 -101 220 -158 514 -256 1200 -127
1407 263 119 226 76 534 -99 701 -128 122 -255 177 -632 275 -125 32 -249 68
-274 79 -105 47 -147 101 -140 180 10 103 91 154 243 154 123 0 183 -21 251
-89 38 -37 57 -66 67 -101 l14 -49 313 -3 313 -2 -7 52 c-11 85 -63 227 -114
312 -105 176 -303 308 -542 361 -73 16 -382 29 -440 19z"/>
<path d="M10435 4009 c-291 -42 -536 -179 -700 -390 -170 -218 -250 -515 -226
-835 6 -71 15 -154 21 -184 113 -560 563 -896 1160 -866 129 6 273 35 380 76
291 113 517 354 605 647 l13 43 -362 0 -361 0 -20 -36 c-29 -55 -118 -130
-183 -155 -74 -29 -211 -32 -293 -7 -147 46 -248 170 -280 346 -6 29 -7 57 -4
62 4 6 279 10 775 10 l768 0 8 88 c4 49 2 141 -4 212 -48 540 -374 902 -886
985 -98 16 -313 18 -411 4z m265 -540 c81 -12 175 -55 229 -104 52 -47 100
-145 108 -218 l6 -57 -431 0 -430 0 9 43 c29 127 119 247 224 297 94 45 178
56 285 39z"/>
<path d="M13070 3995 c-166 -34 -295 -101 -407 -210 l-83 -80 0 142 0 143
-340 0 -340 0 0 -1115 0 -1115 340 0 340 0 0 655 c0 446 4 671 11 707 26 120
111 233 213 283 28 14 85 30 127 36 194 28 367 -51 445 -202 58 -113 57 -108
61 -821 l4 -658 335 0 335 0 -4 743 c-3 707 -4 746 -24 832 -86 373 -308 599
-653 661 -94 17 -275 16 -360 -1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

5
go.mod
View File

@ -1,6 +1,8 @@
module github.com/fastenhealth/fasten-onprem
go 1.18
go 1.21
toolchain go1.21.1
//replace github.com/fastenhealth/fasten-sources => ../fasten-sources
@ -12,6 +14,7 @@ replace github.com/mattn/go-sqlite3 v1.14.17 => github.com/jgiannuzzi/go-sqlite3
require (
github.com/Masterminds/sprig/v3 v3.2.3
github.com/TwiN/deepmerge v0.2.1
github.com/analogj/go-util v0.0.0-20210417161720-39b497cca03b
github.com/dave/jennifer v1.6.1
github.com/dominikbraun/graph v0.15.0

6
go.sum
View File

@ -50,6 +50,8 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/TwiN/deepmerge v0.2.1 h1:GowJr9O4THTVW4awX63x1BVg1hgr4q+35XKKCYbwsSs=
github.com/TwiN/deepmerge v0.2.1/go.mod h1:LVBmCEBQvibYSF8Gyl/NqhHXH7yIiT7Ozqf9dHxGPW0=
github.com/analogj/go-util v0.0.0-20210417161720-39b497cca03b h1:Y/+MfmdKPPpVY7C6ggt/FpltFSitlpUtyJEdcQyFXQg=
github.com/analogj/go-util v0.0.0-20210417161720-39b497cca03b/go.mod h1:bRSzJXgXnT5+Ihah7RSC7Cvp16UmoLn3wq6ROciS1Ow=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@ -115,6 +117,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@ -131,6 +134,7 @@ github.com/go-gormigrate/gormigrate/v2 v2.1.1/go.mod h1:L7nJ620PFDKei9QOhJzqA8kR
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@ -195,6 +199,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v54 v54.0.0 h1:OZdXwow4EAD5jEo5qg+dGFH2DpkyZvVsAehjvJuUL/c=
github.com/google/go-github/v54 v54.0.0/go.mod h1:Sw1LXWHhXRZtzJ9LI5fyJg9wbQzYvFhW8W5P2yaAQ7s=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@ -422,6 +427,7 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=