adding a glossary endpoint which attempts to get patient-friendly descriptions from code. (#120)
This commit is contained in:
parent
fa75594a47
commit
390cea6108
|
@ -34,6 +34,9 @@ type DatabaseRepository interface {
|
||||||
GetSourceSummary(context.Context, string) (*models.SourceSummary, error)
|
GetSourceSummary(context.Context, string) (*models.SourceSummary, error)
|
||||||
GetSources(context.Context) ([]models.SourceCredential, error)
|
GetSources(context.Context) ([]models.SourceCredential, error)
|
||||||
|
|
||||||
//used by Client
|
CreateGlossaryEntry(ctx context.Context, glossaryEntry *models.Glossary) error
|
||||||
|
GetGlossaryEntry(ctx context.Context, code string, codeSystem string) (*models.Glossary, error)
|
||||||
|
|
||||||
|
//used by fasten-sources Clients
|
||||||
UpsertRawResource(ctx context.Context, sourceCredentials sourcePkg.SourceCredential, rawResource sourcePkg.RawResourceFhir) (bool, error)
|
UpsertRawResource(ctx context.Context, sourceCredentials sourcePkg.SourceCredential, rawResource sourcePkg.RawResourceFhir) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,14 +57,14 @@ func NewRepository(appConfig config.Interface, globalLogger logrus.FieldLogger)
|
||||||
}
|
}
|
||||||
globalLogger.Infof("Successfully connected to fasten sqlite db: %s\n", appConfig.GetString("database.location"))
|
globalLogger.Infof("Successfully connected to fasten sqlite db: %s\n", appConfig.GetString("database.location"))
|
||||||
|
|
||||||
deviceRepo := SqliteRepository{
|
fastenRepo := SqliteRepository{
|
||||||
AppConfig: appConfig,
|
AppConfig: appConfig,
|
||||||
Logger: globalLogger,
|
Logger: globalLogger,
|
||||||
GormClient: database,
|
GormClient: database,
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: automigrate for now
|
//TODO: automigrate for now
|
||||||
err = deviceRepo.Migrate()
|
err = fastenRepo.Migrate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ func NewRepository(appConfig config.Interface, globalLogger logrus.FieldLogger)
|
||||||
return nil, fmt.Errorf("Failed to create admin user! - %v", err)
|
return nil, fmt.Errorf("Failed to create admin user! - %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &deviceRepo, nil
|
return &fastenRepo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SqliteRepository struct {
|
type SqliteRepository struct {
|
||||||
|
@ -91,6 +91,7 @@ func (sr *SqliteRepository) Migrate() error {
|
||||||
&models.User{},
|
&models.User{},
|
||||||
&models.SourceCredential{},
|
&models.SourceCredential{},
|
||||||
&models.ResourceFhir{},
|
&models.ResourceFhir{},
|
||||||
|
&models.Glossary{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to automigrate! - %v", err)
|
return fmt.Errorf("Failed to automigrate! - %v", err)
|
||||||
|
@ -140,6 +141,24 @@ func (sr *SqliteRepository) GetCurrentUser(ctx context.Context) (*models.User, e
|
||||||
return ¤tUser, nil
|
return ¤tUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Glossary
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func (sr *SqliteRepository) CreateGlossaryEntry(ctx context.Context, glossaryEntry *models.Glossary) error {
|
||||||
|
record := sr.GormClient.Create(glossaryEntry)
|
||||||
|
if record.Error != nil {
|
||||||
|
return record.Error
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SqliteRepository) GetGlossaryEntry(ctx context.Context, code string, codeSystem string) (*models.Glossary, error) {
|
||||||
|
var foundGlossaryEntry models.Glossary
|
||||||
|
result := sr.GormClient.Where(models.Glossary{Code: code, CodeSystem: codeSystem}).First(&foundGlossaryEntry)
|
||||||
|
return &foundGlossaryEntry, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Summary
|
// Summary
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
// Glossary contains patient friendly terms for medical concepts
|
||||||
|
// Can be retrieved by Code and CodeSystem
|
||||||
|
// Structured similar to ValueSet https://hl7.org/fhir/valueset.html
|
||||||
|
type Glossary struct {
|
||||||
|
ModelBase
|
||||||
|
Code string `json:"code" gorm:"uniqueIndex:idx_glossary_term"`
|
||||||
|
CodeSystem string `json:"code_system" gorm:"uniqueIndex:idx_glossary_term"`
|
||||||
|
Publisher string `json:"publisher"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type MedlinePlusConnectResults struct {
|
||||||
|
Feed struct {
|
||||||
|
Base string `json:"base"`
|
||||||
|
Lang string `json:"lang"`
|
||||||
|
Xsi string `json:"xsi"`
|
||||||
|
Title struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"_value"`
|
||||||
|
} `json:"title"`
|
||||||
|
Updated struct {
|
||||||
|
Value time.Time `json:"_value"`
|
||||||
|
} `json:"updated"`
|
||||||
|
ID struct {
|
||||||
|
Value string `json:"_value"`
|
||||||
|
} `json:"id"`
|
||||||
|
Author struct {
|
||||||
|
Name struct {
|
||||||
|
Value string `json:"_value"`
|
||||||
|
} `json:"name"`
|
||||||
|
URI struct {
|
||||||
|
Value string `json:"_value"`
|
||||||
|
} `json:"uri"`
|
||||||
|
} `json:"author"`
|
||||||
|
Subtitle struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"_value"`
|
||||||
|
} `json:"subtitle"`
|
||||||
|
Category []struct {
|
||||||
|
Scheme string `json:"scheme"`
|
||||||
|
Term string `json:"term"`
|
||||||
|
} `json:"category"`
|
||||||
|
Entry []MedlinePlusConnectResultEntry `json:"entry"`
|
||||||
|
} `json:"feed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MedlinePlusConnectResultEntry struct {
|
||||||
|
Title struct {
|
||||||
|
Value string `json:"_value"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"title"`
|
||||||
|
Link []struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
} `json:"link"`
|
||||||
|
ID struct {
|
||||||
|
Value string `json:"_value"`
|
||||||
|
} `json:"id"`
|
||||||
|
Summary struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"_value"`
|
||||||
|
} `json:"summary"`
|
||||||
|
Updated struct {
|
||||||
|
Value time.Time `json:"_value"`
|
||||||
|
} `json:"updated"`
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
|
||||||
|
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
|
||||||
|
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
|
||||||
|
"github.com/fastenhealth/gofhir-models/fhir401"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FindCodeSystem(codeSystem string) (string, error) {
|
||||||
|
log.Printf("codeSystem: %s", codeSystem)
|
||||||
|
if strings.HasPrefix(codeSystem, "2.16.840.1.113883.6.") {
|
||||||
|
return codeSystem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://terminology.hl7.org/external_terminologies.html
|
||||||
|
codeSystemIds := map[string]string{
|
||||||
|
"http://hl7.org/fhir/sid/icd-10-cm": "2.16.840.1.113883.6.90",
|
||||||
|
"http://hl7.org/fhir/sid/icd-10": "2.16.840.1.113883.6.90",
|
||||||
|
"http://terminology.hl7.org/CodeSystem/icd9cm": "2.16.840.1.113883.6.103",
|
||||||
|
"http://snomed.info/sct": "2.16.840.1.113883.6.96",
|
||||||
|
"http://www.nlm.nih.gov/research/umls/rxnorm": "2.16.840.1.113883.6.88",
|
||||||
|
"http://hl7.org/fhir/sid/ndc": "2.16.840.1.113883.6.69",
|
||||||
|
"http://loinc.org": "2.16.840.1.113883.6.1",
|
||||||
|
"http://www.ama-assn.org/go/cpt": "2.16.840.1.113883.6.12",
|
||||||
|
}
|
||||||
|
|
||||||
|
if codeSystemId, ok := codeSystemIds[codeSystem]; ok {
|
||||||
|
return codeSystemId, nil
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("Code System not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://medlineplus.gov/medlineplus-connect/web-service/
|
||||||
|
// NOTE: max requests is 100/min
|
||||||
|
func GlossarySearchByCode(c *gin.Context) {
|
||||||
|
logger := c.MustGet(pkg.ContextKeyTypeLogger).(*logrus.Entry)
|
||||||
|
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
|
||||||
|
|
||||||
|
codeSystemId, err := FindCodeSystem(c.Query("code_system"))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.Query("code") == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"error": "code is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the code is in the DB cache
|
||||||
|
foundGlossaryEntry, err := databaseRepo.GetGlossaryEntry(c, c.Query("code"), codeSystemId)
|
||||||
|
if err == nil {
|
||||||
|
//found in DB cache
|
||||||
|
logger.Debugf("Found code (%s %s) in DB cache", c.Query("code"), codeSystemId)
|
||||||
|
dateStr := foundGlossaryEntry.UpdatedAt.Format(time.RFC3339)
|
||||||
|
valueSet := fhir401.ValueSet{
|
||||||
|
Title: &foundGlossaryEntry.Title,
|
||||||
|
Url: &foundGlossaryEntry.Url,
|
||||||
|
Description: &foundGlossaryEntry.Description,
|
||||||
|
Date: &dateStr,
|
||||||
|
Publisher: &foundGlossaryEntry.Publisher,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, valueSet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the URL for the MedlinePlus Connect Web Service API
|
||||||
|
medlinePlusConnectEndpoint := "https://connect.medlineplus.gov/service"
|
||||||
|
|
||||||
|
// Define the query parameters
|
||||||
|
params := url.Values{
|
||||||
|
"informationRecipient.languageCode.c": []string{"en"},
|
||||||
|
"knowledgeResponseType": []string{"application/json"},
|
||||||
|
"mainSearchCriteria.v.c": []string{c.Query("code")},
|
||||||
|
"mainSearchCriteria.v.cs": []string{codeSystemId},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the HTTP GET request to the API and retrieve the response
|
||||||
|
//TODO: when using IPV6 to communicate with MedlinePlus, we're getting timeouts. Force IPV4
|
||||||
|
var (
|
||||||
|
zeroDialer net.Dialer
|
||||||
|
httpClient = &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return zeroDialer.DialContext(ctx, "tcp4", addr)
|
||||||
|
}
|
||||||
|
httpClient.Transport = transport
|
||||||
|
resp, err := httpClient.Get(medlinePlusConnectEndpoint + "?" + params.Encode())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error sending request:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Parse the JSON response into a struct
|
||||||
|
var response models.MedlinePlusConnectResults
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&response)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error parsing response:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Feed.Entry) == 0 {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": false, "error": "No results found"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
foundEntry := response.Feed.Entry[0]
|
||||||
|
|
||||||
|
dateStr := foundEntry.Updated.Value.Format(time.RFC3339)
|
||||||
|
valueSet := fhir401.ValueSet{
|
||||||
|
Title: &foundEntry.Title.Value,
|
||||||
|
Url: &foundEntry.Link[0].Href,
|
||||||
|
Description: &foundEntry.Summary.Value,
|
||||||
|
Date: &dateStr,
|
||||||
|
Publisher: &response.Feed.Author.Name.Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
//store in DB cache (ignore errors)
|
||||||
|
databaseRepo.CreateGlossaryEntry(c, &models.Glossary{
|
||||||
|
ModelBase: models.ModelBase{
|
||||||
|
CreatedAt: foundEntry.Updated.Value,
|
||||||
|
UpdatedAt: foundEntry.Updated.Value,
|
||||||
|
},
|
||||||
|
Code: c.Query("code"),
|
||||||
|
CodeSystem: codeSystemId,
|
||||||
|
Publisher: response.Feed.Author.Name.Value,
|
||||||
|
Title: foundEntry.Title.Value,
|
||||||
|
Url: foundEntry.Link[0].Href,
|
||||||
|
Description: foundEntry.Summary.Value,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, valueSet)
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
||||||
//r.Any("/database/*proxyPath", handler.CouchDBProxy)
|
//r.Any("/database/*proxyPath", handler.CouchDBProxy)
|
||||||
//r.GET("/cors/*proxyPath", handler.CORSProxy)
|
//r.GET("/cors/*proxyPath", handler.CORSProxy)
|
||||||
//r.OPTIONS("/cors/*proxyPath", handler.CORSProxy)
|
//r.OPTIONS("/cors/*proxyPath", handler.CORSProxy)
|
||||||
|
api.GET("/glossary/code", handler.GlossarySearchByCode)
|
||||||
|
|
||||||
secure := api.Group("/secure").Use(middleware.RequireAuth())
|
secure := api.Group("/secure").Use(middleware.RequireAuth())
|
||||||
{
|
{
|
||||||
|
@ -61,6 +62,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
||||||
secure.GET("/resource/graph", handler.GetResourceFhirGraph)
|
secure.GET("/resource/graph", handler.GetResourceFhirGraph)
|
||||||
secure.GET("/resource/fhir/:sourceId/:resourceId", handler.GetResourceFhir)
|
secure.GET("/resource/fhir/:sourceId/:resourceId", handler.GetResourceFhir)
|
||||||
secure.POST("/resource/composition", handler.CreateResourceComposition)
|
secure.POST("/resource/composition", handler.CreateResourceComposition)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ae.Config.GetBool("web.allow_unsafe_endpoints") {
|
if ae.Config.GetBool("web.allow_unsafe_endpoints") {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div *ngIf="!loading else isLoadingTemplate">
|
||||||
|
<div [innerHTML]="description"></div>
|
||||||
|
<p>Source: <a [href]="url">{{source}}</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #isLoadingTemplate>
|
||||||
|
<app-loading-spinner [loadingTitle]="'Please wait, loading glossary entry...'"></app-loading-spinner>
|
||||||
|
</ng-template>
|
|
@ -0,0 +1,5 @@
|
||||||
|
:host {
|
||||||
|
max-height:300px;
|
||||||
|
overflow-y:scroll;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GlossaryLookupComponent } from './glossary-lookup.component';
|
||||||
|
|
||||||
|
describe('GlossaryLookupComponent', () => {
|
||||||
|
let component: GlossaryLookupComponent;
|
||||||
|
let fixture: ComponentFixture<GlossaryLookupComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ GlossaryLookupComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(GlossaryLookupComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
|
import {FastenApiService} from '../../services/fasten-api.service';
|
||||||
|
import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-glossary-lookup',
|
||||||
|
templateUrl: './glossary-lookup.component.html',
|
||||||
|
styleUrls: ['./glossary-lookup.component.scss']
|
||||||
|
})
|
||||||
|
export class GlossaryLookupComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() code: string
|
||||||
|
@Input() codeSystem: string
|
||||||
|
@Input() snippetLength: number = -1
|
||||||
|
|
||||||
|
description: SafeHtml = ""
|
||||||
|
url: string = ""
|
||||||
|
source: string = ""
|
||||||
|
loading: boolean = true
|
||||||
|
|
||||||
|
constructor(private fastenApi: FastenApiService, private sanitized: DomSanitizer) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.fastenApi.getGlossarySearchByCode(this.code, this.codeSystem).subscribe(result => {
|
||||||
|
this.loading = false
|
||||||
|
console.log(result)
|
||||||
|
this.url = result?.url
|
||||||
|
this.source = result?.publisher
|
||||||
|
this.description = this.sanitized.bypassSecurityTrustHtml(result?.description)
|
||||||
|
// this.description = result.description
|
||||||
|
}, error => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -48,12 +48,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 bg-gray-100">
|
<div class="col-6 bg-gray-100">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 mt-3">
|
<div *ngIf="observationCode" class="col-12 mt-3">
|
||||||
<p>
|
<app-glossary-lookup [code]="observationCode" [codeSystem]="'http://loinc.org'"></app-glossary-lookup>
|
||||||
<small>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
|
@ -32,11 +32,9 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
<div *ngIf="conditionDisplayModel" class="col-12 mt-3 mb-2">
|
<div *ngIf="conditionDisplayModel.code_id && conditionDisplayModel.code_system" class="col-12 mt-3 mb-2">
|
||||||
<p class="tx-indigo">Initial Presentation</p>
|
<p class="tx-indigo">Definition</p>
|
||||||
<p>
|
<app-glossary-lookup [code]="conditionDisplayModel.code_id" [codeSystem]="conditionDisplayModel.code_system"></app-glossary-lookup>
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -65,6 +65,7 @@ import { PractitionerComponent } from './fhir/resources/practitioner/practitione
|
||||||
import {PipesModule} from '../pipes/pipes.module';
|
import {PipesModule} from '../pipes/pipes.module';
|
||||||
import { NlmTypeaheadComponent } from './nlm-typeahead/nlm-typeahead.component';
|
import { NlmTypeaheadComponent } from './nlm-typeahead/nlm-typeahead.component';
|
||||||
import { DocumentReferenceComponent } from './fhir/resources/document-reference/document-reference.component';
|
import { DocumentReferenceComponent } from './fhir/resources/document-reference/document-reference.component';
|
||||||
|
import { GlossaryLookupComponent } from './glossary-lookup/glossary-lookup.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -138,6 +139,7 @@ import { DocumentReferenceComponent } from './fhir/resources/document-reference/
|
||||||
PractitionerComponent,
|
PractitionerComponent,
|
||||||
NlmTypeaheadComponent,
|
NlmTypeaheadComponent,
|
||||||
DocumentReferenceComponent,
|
DocumentReferenceComponent,
|
||||||
|
GlossaryLookupComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ComponentsSidebarComponent,
|
ComponentsSidebarComponent,
|
||||||
|
@ -190,6 +192,7 @@ import { DocumentReferenceComponent } from './fhir/resources/document-reference/
|
||||||
PractitionerComponent,
|
PractitionerComponent,
|
||||||
NlmTypeaheadComponent,
|
NlmTypeaheadComponent,
|
||||||
DocumentReferenceComponent,
|
DocumentReferenceComponent,
|
||||||
|
GlossaryLookupComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,22 @@
|
||||||
<span>{{resource?.source_resource_id}}</span>
|
<span>{{resource?.source_resource_id}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="displayModel">
|
<ng-container *ngIf="!loading else isLoadingTemplate">
|
||||||
<fhir-resource [displayModel]="displayModel" [showDetails]="false"></fhir-resource>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="resource else isLoadingTemplate">
|
|
||||||
<pre><code [highlight]="resource.resource_raw | json"></code></pre>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
|
<fhir-resource *ngIf="displayModel else noDisplayModel" [displayModel]="displayModel" [showDetails]="false"></fhir-resource>
|
||||||
|
|
||||||
|
<ng-template #noDisplayModel>
|
||||||
|
<p> An error occurred while parsing FHIR resource </p>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
Enable Debug mode: <input type="checkbox" [(ngModel)]="debugMode"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="resource && debugMode">
|
||||||
|
<pre><code [highlight]="resource.resource_raw | json"></code></pre>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
<ng-template #isLoadingTemplate>
|
<ng-template #isLoadingTemplate>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
|
@ -13,6 +13,8 @@ import {FastenDisplayModel} from '../../../lib/models/fasten/fasten-display-mode
|
||||||
})
|
})
|
||||||
export class ResourceDetailComponent implements OnInit {
|
export class ResourceDetailComponent implements OnInit {
|
||||||
loading: boolean = false
|
loading: boolean = false
|
||||||
|
debugMode = false;
|
||||||
|
|
||||||
|
|
||||||
sourceId: string = ""
|
sourceId: string = ""
|
||||||
sourceName: string = ""
|
sourceName: string = ""
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {AuthService} from './auth.service';
|
||||||
import {GetEndpointAbsolutePath} from '../../lib/utils/endpoint_absolute_path';
|
import {GetEndpointAbsolutePath} from '../../lib/utils/endpoint_absolute_path';
|
||||||
import {environment} from '../../environments/environment';
|
import {environment} from '../../environments/environment';
|
||||||
import {ResourceAssociation} from '../models/fasten/resource_association';
|
import {ResourceAssociation} from '../models/fasten/resource_association';
|
||||||
|
import {ValueSet} from 'fhir/r4';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -23,6 +24,24 @@ export class FastenApiService {
|
||||||
constructor(private _httpClient: HttpClient, private router: Router, private authService: AuthService) {
|
constructor(private _httpClient: HttpClient, private router: Router, private authService: AuthService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TERMINOLOGY SERVER/GLOSSARY ENDPOINTS
|
||||||
|
*/
|
||||||
|
getGlossarySearchByCode(code: string, codeSystem: string): Observable<ValueSet> {
|
||||||
|
|
||||||
|
const endpointUrl = new URL(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/glossary/code`);
|
||||||
|
endpointUrl.searchParams.set('code', code);
|
||||||
|
endpointUrl.searchParams.set('code_system', codeSystem);
|
||||||
|
|
||||||
|
return this._httpClient.get<any>(endpointUrl.toString())
|
||||||
|
.pipe(
|
||||||
|
map((response: ValueSet) => {
|
||||||
|
console.log("Glossary RESPONSE", response)
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
SECURE ENDPOINTS
|
SECURE ENDPOINTS
|
||||||
|
|
|
@ -8,6 +8,9 @@ import {FastenOptions} from '../fasten/fasten-options';
|
||||||
export class ConditionModel extends FastenDisplayModel {
|
export class ConditionModel extends FastenDisplayModel {
|
||||||
|
|
||||||
code_text: string | undefined
|
code_text: string | undefined
|
||||||
|
code_id: string | undefined
|
||||||
|
code_system: string | undefined
|
||||||
|
|
||||||
severity_text: string | undefined
|
severity_text: string | undefined
|
||||||
has_asserter: boolean | undefined
|
has_asserter: boolean | undefined
|
||||||
asserter: ReferenceModel | undefined
|
asserter: ReferenceModel | undefined
|
||||||
|
@ -31,6 +34,8 @@ export class ConditionModel extends FastenDisplayModel {
|
||||||
_.get(fhirResource, 'code.coding.0.display') ||
|
_.get(fhirResource, 'code.coding.0.display') ||
|
||||||
_.get(fhirResource, 'code.text') ||
|
_.get(fhirResource, 'code.text') ||
|
||||||
_.get(fhirResource, 'code.coding.0.code');
|
_.get(fhirResource, 'code.coding.0.code');
|
||||||
|
this.code_id = _.get(fhirResource, 'code.coding.0.code')
|
||||||
|
this.code_system = _.get(fhirResource, 'code.coding.0.system')
|
||||||
this.severity_text =
|
this.severity_text =
|
||||||
_.get(fhirResource, 'severity.coding.0.display') ||
|
_.get(fhirResource, 'severity.coding.0.display') ||
|
||||||
_.get(fhirResource, 'severity.text');
|
_.get(fhirResource, 'severity.text');
|
||||||
|
|
Loading…
Reference in New Issue