150 lines
3.7 KiB
Go
150 lines
3.7 KiB
Go
package lib
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
)
|
|
|
|
// Normalize resolves hospital test names to official LOINC codes and updates entries.
|
|
// Flow: hospital name + specimen + unit → LOINC (via cache or Gemini lookup) → official name from loinc_lab.
|
|
// No Fireworks LLM. Original Type field is never modified.
|
|
func Normalize(dossierID string, category int, progress ...func(processed, total int)) error {
|
|
reportProgress := func(p, t int) {
|
|
if len(progress) > 0 && progress[0] != nil {
|
|
progress[0](p, t)
|
|
}
|
|
}
|
|
|
|
// 1. Load all entries, build parent map for specimen lookup
|
|
entries, err := EntryQueryOld(dossierID, category, "")
|
|
if err != nil {
|
|
return fmt.Errorf("load entries: %w", err)
|
|
}
|
|
|
|
parentMap := make(map[string]*Entry)
|
|
for _, e := range entries {
|
|
if e.ParentID == "" || e.Type == "lab_order" {
|
|
parentMap[e.EntryID] = e
|
|
}
|
|
}
|
|
|
|
// 2. Collect unique type|specimen|unit combos, resolve each to LOINC
|
|
type testKey struct {
|
|
name, specimen, unit string
|
|
}
|
|
type resolved struct {
|
|
loinc string
|
|
info *LoincInfo
|
|
abbr string
|
|
}
|
|
cache := make(map[testKey]*resolved)
|
|
var lookupCount, cacheHits, misses int
|
|
|
|
for _, e := range entries {
|
|
if e.ParentID == "" || e.Type == "lab_order" || e.Type == "" {
|
|
continue
|
|
}
|
|
var data map[string]interface{}
|
|
json.Unmarshal([]byte(e.Data), &data)
|
|
unit, _ := data["unit"].(string)
|
|
|
|
specimen := ""
|
|
if parent, ok := parentMap[e.ParentID]; ok {
|
|
var pdata map[string]interface{}
|
|
if json.Unmarshal([]byte(parent.Data), &pdata) == nil {
|
|
specimen, _ = pdata["specimen"].(string)
|
|
}
|
|
}
|
|
|
|
tk := testKey{e.Type, specimen, unit}
|
|
if _, ok := cache[tk]; ok {
|
|
continue
|
|
}
|
|
lookupCount++
|
|
|
|
loinc := LoincLookup(e.Type, specimen, unit)
|
|
if loinc == "" {
|
|
cache[tk] = &resolved{}
|
|
misses++
|
|
continue
|
|
}
|
|
|
|
info := LoincGet(loinc)
|
|
abbr := LoincAbbr(info)
|
|
cache[tk] = &resolved{loinc: loinc, info: info, abbr: abbr}
|
|
if info != nil {
|
|
cacheHits++
|
|
}
|
|
}
|
|
|
|
reportProgress(lookupCount, lookupCount)
|
|
log.Printf("normalize: %d unique combos, %d resolved, %d unresolved", lookupCount, cacheHits, misses)
|
|
|
|
// 3. Apply to entries
|
|
var toSave []Entry
|
|
for _, e := range entries {
|
|
if e.ParentID == "" || e.Type == "lab_order" || e.Type == "" {
|
|
continue
|
|
}
|
|
var data map[string]interface{}
|
|
if json.Unmarshal([]byte(e.Data), &data) != nil {
|
|
data = make(map[string]interface{})
|
|
}
|
|
unit, _ := data["unit"].(string)
|
|
|
|
specimen := ""
|
|
if parent, ok := parentMap[e.ParentID]; ok {
|
|
var pdata map[string]interface{}
|
|
if json.Unmarshal([]byte(parent.Data), &pdata) == nil {
|
|
specimen, _ = pdata["specimen"].(string)
|
|
}
|
|
}
|
|
|
|
r := cache[testKey{e.Type, specimen, unit}]
|
|
if r == nil || r.loinc == "" || r.info == nil {
|
|
continue
|
|
}
|
|
|
|
// Check if already up to date
|
|
existingLoinc, _ := data["loinc"].(string)
|
|
existingName, _ := data["normalized_name"].(string)
|
|
if existingLoinc == r.loinc && existingName == r.info.LongName && e.SearchKey == r.loinc && e.SearchKey2 != "" {
|
|
continue
|
|
}
|
|
|
|
// Update Data JSON
|
|
data["loinc"] = r.loinc
|
|
data["normalized_name"] = r.info.LongName
|
|
data["abbreviation"] = r.abbr
|
|
b, _ := json.Marshal(data)
|
|
e.Data = string(b)
|
|
|
|
// Update search keys
|
|
e.SearchKey = r.loinc
|
|
e.SearchKey2 = strings.ToLower(r.info.LongName)
|
|
|
|
// Rebuild Summary: "Abbr: value unit"
|
|
summary := r.abbr + ": " + e.Value
|
|
if unit != "" {
|
|
summary += " " + unit
|
|
}
|
|
e.Summary = summary
|
|
|
|
toSave = append(toSave, *e)
|
|
}
|
|
|
|
if len(toSave) == 0 {
|
|
log.Printf("normalize: no changes needed")
|
|
return nil
|
|
}
|
|
|
|
log.Printf("normalize: updating %d entries", len(toSave))
|
|
ptrs := make([]*Entry, len(toSave))
|
|
for i := range toSave {
|
|
ptrs[i] = &toSave[i]
|
|
}
|
|
return EntryWrite("", ptrs...)
|
|
}
|