wstl1/mapping_engine/harmonization/harmonizecode/remote_code_harmonizer.go (147 lines of code) (raw):
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package harmonizecode
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"
"strings"
"github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/auth" /* copybara-comment: auth */
)
// RemoteCodeHarmonizer will harmonize codes using a remote lookup service.
type RemoteCodeHarmonizer struct {
client auth.Client
address string
cache *ExpiringCache
}
func makeRemoteCodeHarmonizer(address string, ttl int, cleanup int) (*RemoteCodeHarmonizer, error) {
c := auth.NewClient(context.Background())
return &RemoteCodeHarmonizer{
address: address,
client: c,
cache: NewCache(ttl, cleanup),
}, nil
}
// HarmonizeBySearch implements CodeHarmonizer's HarmonizeBySearch function.
func (h *RemoteCodeHarmonizer) HarmonizeBySearch(sourceCode, sourceSystem, sourceValueset, targetValueset, version string) ([]HarmonizedCode, error) {
key := CodeLookupKey{
Code: sourceCode,
System: sourceSystem,
Version: version,
}
if sourceValueset == "" && targetValueset == "" {
return nil, fmt.Errorf("source and target value sets cannot both be empty")
}
// Check cache before making http request.
if v, ok := h.cache.Get(key); ok {
return v.value, nil
}
u, err := url.Parse(h.address)
if err != nil {
return nil, fmt.Errorf("url is invalid %v", err)
}
u.Path = path.Join(u.Path, "fhir/ConceptMap/$translate")
addr := u.String()
req, err := http.NewRequest(http.MethodGet, addr, nil)
if err != nil {
return nil, fmt.Errorf("error building new request %v", err)
}
q := req.URL.Query()
q.Add("code", sourceCode)
q.Add("system", sourceSystem)
if sourceValueset != "" {
q.Add("source", sourceValueset)
}
if targetValueset != "" {
q.Add("target", targetValueset)
}
if version != "" {
q.Add("version", version)
}
req.URL.RawQuery = q.Encode()
raw, err := h.client.ExecuteRequest(context.Background(), req, "translate code", true)
if err != nil {
return nil, fmt.Errorf("error calling remote endpoint to harmonize code, %v", err)
}
res, err := rawToCodes(raw)
if err != nil {
return nil, fmt.Errorf("error unmarshalling translate result %v", err)
}
if len(res) == 0 {
res = append(res, HarmonizedCode{
Code: sourceCode,
System: "unharmonized",
Version: version,
})
}
// Add result to cache.
h.cache.Put(key, res)
return res, nil
}
// HarmonizeWithTarget implements CodeHarmonizer's HarmonizeWithTarget function.
func (h *RemoteCodeHarmonizer) HarmonizeWithTarget(sourceCode, sourceSystem, targetSystem, sourceName string) ([]HarmonizedCode, error) {
return nil, fmt.Errorf("HarmonizeWithTarget is not implemented in remote code harmonizer yet")
}
// Harmonize implements CodeHarmonizer's Harmonize function.
func (h *RemoteCodeHarmonizer) Harmonize(sourceCode, sourceSystem, sourceName string) ([]HarmonizedCode, error) {
key := CodeLookupKey{
Code: sourceCode,
System: sourceSystem,
}
// Check cache before making http request.
if v, ok := h.cache.Get(key); ok {
return v.value, nil
}
u, err := url.Parse(h.address)
if err != nil {
return nil, fmt.Errorf("url is invalid %v", err)
}
u.Path = path.Join(u.Path, fmt.Sprintf("fhir/ConceptMap/%s/$translate", sourceName))
addr := u.String()
req, err := http.NewRequest(http.MethodGet, addr, nil)
if err != nil {
return nil, fmt.Errorf("error building new request %v", err)
}
q := req.URL.Query()
q.Add("code", sourceCode)
q.Add("system", sourceSystem)
req.URL.RawQuery = q.Encode()
raw, err := h.client.ExecuteRequest(context.Background(), req, "translate code", true)
if err != nil {
return nil, fmt.Errorf("error calling remote endpoint to harmonize code, %v", err)
}
res, err := rawToCodes(raw)
if err != nil {
return nil, fmt.Errorf("error unmarshalling translate result %v", err)
}
if len(res) == 0 {
res = append(res, HarmonizedCode{
Code: sourceCode,
System: fmt.Sprintf("%s-%s", sourceName, "unharmonized"),
})
}
// Add result to cache.
h.cache.Put(key, res)
return res, nil
}
func rawToCodes(raw *json.RawMessage) ([]HarmonizedCode, error) {
// TODO(): Add support for multiple FHIR versions.
parameters, err := unmarshalR3Parameters(*raw)
if err != nil {
return nil, fmt.Errorf("unmarshalling concept map %v failed with error: %v", string(*raw), err)
}
var res []HarmonizedCode
for _, p := range parameters.Parameter {
if p.Name != "match" {
continue
}
for _, part := range p.Part {
if part.Name != "concept" {
continue
}
coding := part.ValueCoding
// We do not support non equivalent codes yet.
if part.Name == "equivalence" && strings.ToLower(coding.Code) != "equivalent" {
continue
}
res = append(res, HarmonizedCode{
Code: coding.Code,
System: coding.System,
Display: coding.Display,
Version: coding.Version,
})
}
}
return res, nil
}