wstl1/mapping_engine/harmonization/harmonizecode/harmonize_code.go (207 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 handles harmonization of codes. package harmonizecode import ( "context" "fmt" "io/ioutil" "strings" "github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/projector" /* copybara-comment: projector */ "github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/types" /* copybara-comment: types */ "github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/util/gcsutil" /* copybara-comment: gcsutil */ "github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/util/jsonutil" /* copybara-comment: jsonutil */ hpb "github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/proto" /* copybara-comment: harmonization_go_proto */ httppb "github.com/GoogleCloudPlatform/healthcare-data-harmonization/mapping_engine/proto" /* copybara-comment: http_go_proto */ ) const ( projectorName = "$HarmonizeCode" withTargetProjector = "$HarmonizeCodeWithTarget" searchProjector = "$HarmonizeCodeBySearch" localHarmonizerName = "$Local" ) // CodeHarmonizer is the interface for harmonizing codes. type CodeHarmonizer interface { // sourceCode is the code being translated. // sourceSystem is the system that this code belongs to. // sourceName is the name of the concept map to use. Harmonize(sourceCode, sourceSystem, sourceName string) ([]HarmonizedCode, error) // sourceCode is the code being translated. // sourceSystem is the system that this code belongs to. // targetSystem is the system in which a translated code is sought. // sourceName is the name of the concept map to use. HarmonizeWithTarget(sourceCode, sourceSystem, targetSystem, sourceName string) ([]HarmonizedCode, error) // sourceCode is the code being translated. // sourceSystem is the system that this code belongs to. // sourceValueset is the name of the source value set to search by. // targetValueset is the name of the target value set to search by. // version is the version of the concept map to use. HarmonizeBySearch(sourceCode, sourceSystem, sourceValueset, targetValueset, version string) ([]HarmonizedCode, error) } // HarmonizedCode is the result of harmonization. // TODO(): Add original code and equivalence here. type HarmonizedCode struct { Code string System string Display string Version string } // ToJSONContainer converts the HarmonizedCode to a JSONContainer. func (h HarmonizedCode) ToJSONContainer() jsonutil.JSONContainer { jc := make(jsonutil.JSONContainer) c := jsonutil.JSONToken(jsonutil.JSONStr(h.Code)) jc["code"] = &c v := jsonutil.JSONToken(jsonutil.JSONStr(h.Version)) jc["version"] = &v d := jsonutil.JSONToken(jsonutil.JSONStr(h.Display)) jc["display"] = &d s := jsonutil.JSONToken(jsonutil.JSONStr(h.System)) jc["system"] = &s return jc } // FromJSONContainer converts a JSONContainer to a HarmonizedCode. func FromJSONContainer(jc jsonutil.JSONContainer) (HarmonizedCode, error) { result := HarmonizedCode{} if c, ok := jc["code"]; ok { if s, ok := (*c).(jsonutil.JSONStr); ok { result.Code = string(s) } else { return result, fmt.Errorf("code field is invalid") } } if v, ok := jc["version"]; ok { if s, ok := (*v).(jsonutil.JSONStr); ok { result.Version = string(s) } else { return result, fmt.Errorf("version field is invalid") } } if s, ok := jc["system"]; ok { if system, ok := (*s).(jsonutil.JSONStr); ok { result.System = string(system) } else { return result, fmt.Errorf("system field is invalid") } } if d, ok := jc["display"]; ok { if s, ok := (*d).(jsonutil.JSONStr); ok { result.Display = string(s) } else { return result, fmt.Errorf("display field is invalid") } } return result, nil } // LoadCodeHarmonizationProjectors loads all harmonization projectors. func LoadCodeHarmonizationProjectors(r *types.Registry, hc *hpb.CodeHarmonizationConfig) error { if hc == nil { return nil } harmonizers, err := makeCodeHarmonizers(hc) if err != nil { return err } proj, err := buildHarmonizeCodeProjector(harmonizers, projectorName) if err != nil { return err } if err = r.RegisterProjector(projectorName, proj); err != nil { return fmt.Errorf("error registering projector %q: %v", projectorName, err) } sproj, err := buildHarmonizeBySearchProjector(harmonizers, searchProjector) if err != nil { return err } if err = r.RegisterProjector(searchProjector, sproj); err != nil { return fmt.Errorf("error registering projector %q: %v", searchProjector, err) } tproj, err := buildHarmonizeWithTargetProjector(harmonizers, withTargetProjector) if err != nil { return err } if err = r.RegisterProjector(withTargetProjector, tproj); err != nil { return fmt.Errorf("error registering projector %q: %v", withTargetProjector, err) } return nil } func makeCodeHarmonizers(lookups *hpb.CodeHarmonizationConfig) (map[string]CodeHarmonizer, error) { harmonizers := make(map[string]CodeHarmonizer) local := NewLocalCodeHarmonizer() for _, l := range lookups.CodeLookup { var raw []byte var err error switch t := l.Location.(type) { case *httppb.Location_LocalPath: if !strings.HasSuffix(t.LocalPath, ".json") { continue } raw, err = ioutil.ReadFile(t.LocalPath) if err != nil { return nil, fmt.Errorf("failed to read concept map file with error %v", err) } case *httppb.Location_GcsLocation: raw, err = gcsutil.ReadFromGcs(context.Background(), t.GcsLocation) if err != nil { return nil, fmt.Errorf("failed to read from GCS, %v", err) } case *httppb.Location_UrlPath: h, err := makeRemoteCodeHarmonizer(t.UrlPath, int(lookups.CacheTtlSeconds), int(lookups.CleanupIntervalSeconds)) if err != nil { return nil, fmt.Errorf("unable to create remote code harmonizer from url %s: %v", t.UrlPath, err) } harmonizers[l.GetName()] = h continue default: return nil, fmt.Errorf("location type %T is not supported", t) } // TODO(): Add support for multiple FHIR versions. cm, err := unmarshalR3ConceptMap(raw) if err != nil { return nil, fmt.Errorf("unmarshal failed with error %v", err) } if err := local.Cache(cm); err != nil { return nil, err } } harmonizers[localHarmonizerName] = local return harmonizers, nil } func codesToJSONArray(hcs []HarmonizedCode) jsonutil.JSONArr { results := make(jsonutil.JSONArr, 0, len(hcs)) for _, v := range hcs { results = append(results, v.ToJSONContainer()) } return results } func buildHarmonizeWithTargetProjector(harmonizers map[string]CodeHarmonizer, name string) (types.Projector, error) { f := func(sourceType, sourceCode, sourceSystem, targetSystem, sourceName jsonutil.JSONStr) (jsonutil.JSONToken, error) { st := string(sourceType) if st == "" { return nil, fmt.Errorf("the harmonization source type cannot be empty") } harmonizer, ok := harmonizers[st] if !ok { return nil, fmt.Errorf("the harmonization source %s does not exist", st) } harmonizedCodes, err := harmonizer.HarmonizeWithTarget(string(sourceCode), string(sourceSystem), string(targetSystem), string(sourceName)) if err != nil { return nil, err } return codesToJSONArray(harmonizedCodes), nil } return projector.FromFunction(f, name) } func buildHarmonizeBySearchProjector(harmonizers map[string]CodeHarmonizer, name string) (types.Projector, error) { f := func(sourceType, sourceCode, sourceSystem, sourceValueset, targetValueset, version jsonutil.JSONStr) (jsonutil.JSONToken, error) { st := string(sourceType) if st == "" { return nil, fmt.Errorf("the harmonization source type cannot be empty") } harmonizer, ok := harmonizers[st] if !ok { return nil, fmt.Errorf("the harmonization source %s does not exist", st) } harmonizedCodes, err := harmonizer.HarmonizeBySearch(string(sourceCode), string(sourceSystem), string(sourceValueset), string(targetValueset), string(version)) if err != nil { return nil, err } return codesToJSONArray(harmonizedCodes), nil } return projector.FromFunction(f, name) } func buildHarmonizeCodeProjector(harmonizers map[string]CodeHarmonizer, name string) (types.Projector, error) { f := func(sourceType, sourceCode, sourceSystem, sourceName jsonutil.JSONStr) (jsonutil.JSONToken, error) { st := string(sourceType) if st == "" { return nil, fmt.Errorf("the harmonization source type cannot be empty") } harmonizer, ok := harmonizers[st] if !ok { return nil, fmt.Errorf("the harmonization source %s does not exist", st) } harmonizedCodes, err := harmonizer.Harmonize(string(sourceCode), string(sourceSystem), string(sourceName)) if err != nil { return nil, err } return codesToJSONArray(harmonizedCodes), nil } return projector.FromFunction(f, name) }