159 lines
4.9 KiB
Go
159 lines
4.9 KiB
Go
|
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)
|
||
|
}
|
||
|
}
|