mirror of
https://github.com/AmanTahiliani/FHIR-Sandbox.git
synced 2026-08-08 04:06:16 -04:00
Patient Match
This commit is contained in:
152
app/handlers/match_proxy.go
Normal file
152
app/handlers/match_proxy.go
Normal file
@@ -0,0 +1,152 @@
|
||||
// Package handlers — match_proxy.go implements the browser-facing
|
||||
// "Find in Rimidi" proxy endpoint.
|
||||
//
|
||||
// POST /api/patient-match-proxy
|
||||
//
|
||||
// This endpoint is session-protected. It reads the current patient's
|
||||
// demographics from the local database, calls the remote Rimidi patient
|
||||
// match API, and returns the response to the browser.
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/AmanTahiliani/FHIR-Sandbox/app/middleware"
|
||||
)
|
||||
|
||||
// proxyTimeout is the HTTP client timeout for the outbound match call.
|
||||
const proxyTimeout = 10 * time.Second
|
||||
|
||||
// matchProxyResponse wraps the remote response and adds the local patient's
|
||||
// demographics so the UI can render a side-by-side diff.
|
||||
type matchProxyResponse struct {
|
||||
SourceSystem string `json:"source_system"`
|
||||
Matches []matchResult `json:"matches"`
|
||||
LocalPatient map[string]string `json:"local_patient"`
|
||||
Raw map[string]interface{} `json:"-"` // internal only
|
||||
}
|
||||
|
||||
// HandlePatientMatchProxy handles POST /api/patient-match-proxy.
|
||||
// It requires a valid session and reads the patient from session context.
|
||||
func (h *Handler) HandlePatientMatchProxy(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
sess := middleware.SessionFromContext(r.Context())
|
||||
if sess == nil {
|
||||
http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// ── Resolve patient from the session or query param ─────────────
|
||||
patientFHIRID := sess.PatientFHIRID
|
||||
if override := r.URL.Query().Get("patient_id"); override != "" {
|
||||
patientFHIRID = override
|
||||
}
|
||||
|
||||
if patientFHIRID == "" {
|
||||
http.Error(w, `{"error":"no patient in context"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch patient demographics from the local database.
|
||||
patient, err := h.store.GetUserByFHIRID(patientFHIRID, sess.EHRURL)
|
||||
if err != nil {
|
||||
log.Printf("handlers: match-proxy GetUserByFHIRID(%s): %v", patientFHIRID, err)
|
||||
http.Error(w, `{"error":"patient not found in local database"}`, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// ── Build outbound payload ──────────────────────────────────────
|
||||
payload := patientMatchRequest{
|
||||
FirstName: patient.FirstName,
|
||||
LastName: patient.LastName,
|
||||
Email: patient.Email,
|
||||
DOB: patient.DOB,
|
||||
Sex: patient.Gender,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
log.Printf("handlers: match-proxy marshal payload: %v", err)
|
||||
http.Error(w, `{"error":"internal error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// ── Call remote Rimidi match API ─────────────────────────────────
|
||||
remoteURL := h.cfg.PatientMatchRemoteURL
|
||||
remoteKey := h.cfg.PatientMatchRemoteAPIKey
|
||||
|
||||
if remoteURL == "" || remoteKey == "" {
|
||||
log.Printf("handlers: match-proxy remote URL/key not configured")
|
||||
http.Error(w, `{"error":"remote patient match not configured"}`, http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: proxyTimeout}
|
||||
req, err := http.NewRequest(http.MethodPost, remoteURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
log.Printf("handlers: match-proxy new request: %v", err)
|
||||
http.Error(w, `{"error":"internal error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Api-Key", remoteKey)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("handlers: match-proxy remote call failed: %v", err)
|
||||
http.Error(w, `{"error":"could not reach remote system"}`, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("handlers: match-proxy read response: %v", err)
|
||||
http.Error(w, `{"error":"failed to read remote response"}`, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("handlers: match-proxy remote returned %d: %s", resp.StatusCode, string(respBody[:min(len(respBody), 500)]))
|
||||
http.Error(w, fmt.Sprintf(`{"error":"remote system returned HTTP %d"}`, resp.StatusCode), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
// ── Parse remote response and augment with local demographics ───
|
||||
var remoteData map[string]interface{}
|
||||
if err := json.Unmarshal(respBody, &remoteData); err != nil {
|
||||
log.Printf("handlers: match-proxy unmarshal response: %v", err)
|
||||
http.Error(w, `{"error":"invalid response from remote system"}`, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
// Attach local patient demographics for side-by-side diff rendering.
|
||||
remoteData["local_patient"] = map[string]string{
|
||||
"first_name": patient.FirstName,
|
||||
"last_name": patient.LastName,
|
||||
"email": patient.Email,
|
||||
"dob": patient.DOB,
|
||||
"sex": patient.Gender,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(remoteData)
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user