fasten-onprem/backend/pkg/web/handler/glossary.go

159 lines
4.9 KiB
Go

package handler
import (
"context"
"encoding/json"
"fmt"
"github.com/fastenhealth/fasten-onprem/backend/pkg"
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
"github.com/fastenhealth/fasten-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: %s", codeSystem)
}
}
// 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)
}
}