mirror of
https://github.com/AmanTahiliani/FHIR-Sandbox.git
synced 2026-08-07 11:53:56 -04:00
787 lines
25 KiB
Go
787 lines
25 KiB
Go
// clinical.go provides persistence operations for FHIR clinical resources:
|
|
// Observation, Condition, DocumentReference, and PatientSync.
|
|
//
|
|
// All upsert methods use (fhir_id, ehr_url) as the natural deduplication key,
|
|
// matching the same pattern used for users. On conflict the stored row is
|
|
// overwritten with the latest data from the EHR and synced_at is updated.
|
|
package db
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/AmanTahiliani/FHIR-Sandbox/app/models"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Observation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// UpsertObservation inserts or replaces an Observation record keyed on
|
|
// (fhir_id, ehr_url). Returns the internal UUID.
|
|
func (s *Store) UpsertObservation(o *models.Observation) (string, error) {
|
|
now := time.Now().UTC()
|
|
|
|
var existingID string
|
|
err := s.db.QueryRow(
|
|
`SELECT id FROM observations WHERE fhir_id = ? AND ehr_url = ?`,
|
|
o.FHIRID, o.EHRURL,
|
|
).Scan(&existingID)
|
|
|
|
if err == nil {
|
|
_, err = s.db.Exec(`
|
|
UPDATE observations SET
|
|
patient_fhir_id = ?,
|
|
status = ?,
|
|
category = ?,
|
|
code_text = ?,
|
|
code_system = ?,
|
|
code_code = ?,
|
|
effective_date = ?,
|
|
value_quantity = ?,
|
|
value_unit = ?,
|
|
value_string = ?,
|
|
interpretation = ?,
|
|
ref_range_low = ?,
|
|
ref_range_high = ?,
|
|
synced_at = ?
|
|
WHERE id = ?`,
|
|
o.PatientFHIRID, o.Status, o.Category,
|
|
o.CodeText, o.CodeSystem, o.CodeCode,
|
|
o.EffectiveDate, o.ValueQuantity, o.ValueUnit, o.ValueString,
|
|
o.Interpretation, o.ReferenceRangeLow, o.ReferenceRangeHigh,
|
|
now, existingID,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: update observation %s: %w", existingID, err)
|
|
}
|
|
return existingID, nil
|
|
}
|
|
|
|
id := uuid.NewString()
|
|
_, err = s.db.Exec(`
|
|
INSERT INTO observations (
|
|
id, fhir_id, ehr_url, patient_fhir_id, status, category,
|
|
code_text, code_system, code_code, effective_date,
|
|
value_quantity, value_unit, value_string, interpretation,
|
|
ref_range_low, ref_range_high, synced_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
id, o.FHIRID, o.EHRURL, o.PatientFHIRID, o.Status, o.Category,
|
|
o.CodeText, o.CodeSystem, o.CodeCode, o.EffectiveDate,
|
|
o.ValueQuantity, o.ValueUnit, o.ValueString, o.Interpretation,
|
|
o.ReferenceRangeLow, o.ReferenceRangeHigh, now,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: insert observation fhir_id=%s: %w", o.FHIRID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// ListObservations returns all Observations for the given patient, newest first.
|
|
func (s *Store) ListObservations(patientFHIRID, ehrURL string) ([]models.Observation, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT id, fhir_id, ehr_url, patient_fhir_id, status, category,
|
|
code_text, code_system, code_code, effective_date,
|
|
value_quantity, value_unit, value_string, interpretation,
|
|
ref_range_low, ref_range_high, synced_at
|
|
FROM observations
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY effective_date DESC`,
|
|
patientFHIRID, ehrURL,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: list observations: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []models.Observation
|
|
for rows.Next() {
|
|
var o models.Observation
|
|
if err := rows.Scan(
|
|
&o.ID, &o.FHIRID, &o.EHRURL, &o.PatientFHIRID, &o.Status, &o.Category,
|
|
&o.CodeText, &o.CodeSystem, &o.CodeCode, &o.EffectiveDate,
|
|
&o.ValueQuantity, &o.ValueUnit, &o.ValueString, &o.Interpretation,
|
|
&o.ReferenceRangeLow, &o.ReferenceRangeHigh, &o.SyncedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("db: scan observation: %w", err)
|
|
}
|
|
out = append(out, o)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Condition
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// UpsertCondition inserts or updates a Condition record keyed on (fhir_id, ehr_url).
|
|
func (s *Store) UpsertCondition(c *models.Condition) (string, error) {
|
|
now := time.Now().UTC()
|
|
|
|
var existingID string
|
|
err := s.db.QueryRow(
|
|
`SELECT id FROM conditions WHERE fhir_id = ? AND ehr_url = ?`,
|
|
c.FHIRID, c.EHRURL,
|
|
).Scan(&existingID)
|
|
|
|
if err == nil {
|
|
_, err = s.db.Exec(`
|
|
UPDATE conditions SET
|
|
patient_fhir_id = ?,
|
|
clinical_status = ?,
|
|
verification_status = ?,
|
|
category = ?,
|
|
code_text = ?,
|
|
code_system = ?,
|
|
code_code = ?,
|
|
onset_date = ?,
|
|
recorded_date = ?,
|
|
synced_at = ?
|
|
WHERE id = ?`,
|
|
c.PatientFHIRID, c.ClinicalStatus, c.VerificationStatus,
|
|
c.Category, c.CodeText, c.CodeSystem, c.CodeCode,
|
|
c.OnsetDate, c.RecordedDate, now, existingID,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: update condition %s: %w", existingID, err)
|
|
}
|
|
return existingID, nil
|
|
}
|
|
|
|
id := uuid.NewString()
|
|
_, err = s.db.Exec(`
|
|
INSERT INTO conditions (
|
|
id, fhir_id, ehr_url, patient_fhir_id,
|
|
clinical_status, verification_status, category,
|
|
code_text, code_system, code_code,
|
|
onset_date, recorded_date, synced_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
id, c.FHIRID, c.EHRURL, c.PatientFHIRID,
|
|
c.ClinicalStatus, c.VerificationStatus, c.Category,
|
|
c.CodeText, c.CodeSystem, c.CodeCode,
|
|
c.OnsetDate, c.RecordedDate, now,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: insert condition fhir_id=%s: %w", c.FHIRID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// ListConditions returns all Conditions for the given patient, newest first.
|
|
func (s *Store) ListConditions(patientFHIRID, ehrURL string) ([]models.Condition, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT id, fhir_id, ehr_url, patient_fhir_id,
|
|
clinical_status, verification_status, category,
|
|
code_text, code_system, code_code,
|
|
onset_date, recorded_date, synced_at
|
|
FROM conditions
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY recorded_date DESC`,
|
|
patientFHIRID, ehrURL,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: list conditions: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []models.Condition
|
|
for rows.Next() {
|
|
var c models.Condition
|
|
if err := rows.Scan(
|
|
&c.ID, &c.FHIRID, &c.EHRURL, &c.PatientFHIRID,
|
|
&c.ClinicalStatus, &c.VerificationStatus, &c.Category,
|
|
&c.CodeText, &c.CodeSystem, &c.CodeCode,
|
|
&c.OnsetDate, &c.RecordedDate, &c.SyncedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("db: scan condition: %w", err)
|
|
}
|
|
out = append(out, c)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DocumentReference
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// UpsertDocumentReference inserts or updates a DocumentReference record.
|
|
func (s *Store) UpsertDocumentReference(d *models.DocumentReference) (string, error) {
|
|
now := time.Now().UTC()
|
|
|
|
var existingID string
|
|
err := s.db.QueryRow(
|
|
`SELECT id FROM document_references WHERE fhir_id = ? AND ehr_url = ?`,
|
|
d.FHIRID, d.EHRURL,
|
|
).Scan(&existingID)
|
|
|
|
if err == nil {
|
|
_, err = s.db.Exec(`
|
|
UPDATE document_references SET
|
|
patient_fhir_id = ?,
|
|
status = ?,
|
|
doc_status = ?,
|
|
type_text = ?,
|
|
type_system = ?,
|
|
type_code = ?,
|
|
category = ?,
|
|
date = ?,
|
|
description = ?,
|
|
content_type = ?,
|
|
content_url = ?,
|
|
content_data = ?,
|
|
synced_at = ?
|
|
WHERE id = ?`,
|
|
d.PatientFHIRID, d.Status, d.DocStatus,
|
|
d.TypeText, d.TypeSystem, d.TypeCode,
|
|
d.Category, d.Date, d.Description,
|
|
d.ContentType, d.ContentURL, d.ContentData,
|
|
now, existingID,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: update document_reference %s: %w", existingID, err)
|
|
}
|
|
return existingID, nil
|
|
}
|
|
|
|
id := uuid.NewString()
|
|
_, err = s.db.Exec(`
|
|
INSERT INTO document_references (
|
|
id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, doc_status, type_text, type_system, type_code,
|
|
category, date, description,
|
|
content_type, content_url, content_data, synced_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
id, d.FHIRID, d.EHRURL, d.PatientFHIRID,
|
|
d.Status, d.DocStatus, d.TypeText, d.TypeSystem, d.TypeCode,
|
|
d.Category, d.Date, d.Description,
|
|
d.ContentType, d.ContentURL, d.ContentData, now,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: insert document_reference fhir_id=%s: %w", d.FHIRID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// ListDocumentReferences returns all DocumentReferences for the given patient,
|
|
// newest first.
|
|
func (s *Store) ListDocumentReferences(patientFHIRID, ehrURL string) ([]models.DocumentReference, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, doc_status, type_text, type_system, type_code,
|
|
category, date, description,
|
|
content_type, content_url, content_data, synced_at
|
|
FROM document_references
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY date DESC`,
|
|
patientFHIRID, ehrURL,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: list document_references: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []models.DocumentReference
|
|
for rows.Next() {
|
|
var d models.DocumentReference
|
|
if err := rows.Scan(
|
|
&d.ID, &d.FHIRID, &d.EHRURL, &d.PatientFHIRID,
|
|
&d.Status, &d.DocStatus, &d.TypeText, &d.TypeSystem, &d.TypeCode,
|
|
&d.Category, &d.Date, &d.Description,
|
|
&d.ContentType, &d.ContentURL, &d.ContentData, &d.SyncedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("db: scan document_reference: %w", err)
|
|
}
|
|
out = append(out, d)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// MedicationRequest
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// UpsertMedicationRequest inserts or updates a MedicationRequest record keyed on (fhir_id, ehr_url).
|
|
func (s *Store) UpsertMedicationRequest(m *models.MedicationRequest) (string, error) {
|
|
now := time.Now().UTC()
|
|
|
|
var existingID string
|
|
err := s.db.QueryRow(
|
|
`SELECT id FROM medication_requests WHERE fhir_id = ? AND ehr_url = ?`,
|
|
m.FHIRID, m.EHRURL,
|
|
).Scan(&existingID)
|
|
|
|
if err == nil {
|
|
_, err = s.db.Exec(`
|
|
UPDATE medication_requests SET
|
|
patient_fhir_id = ?,
|
|
status = ?,
|
|
intent = ?,
|
|
med_code_text = ?,
|
|
med_code_system = ?,
|
|
med_code_code = ?,
|
|
authored_on = ?,
|
|
requester_display = ?,
|
|
dosage_text = ?,
|
|
synced_at = ?
|
|
WHERE id = ?`,
|
|
m.PatientFHIRID, m.Status, m.Intent,
|
|
m.MedCodeText, m.MedCodeSystem, m.MedCodeCode,
|
|
m.AuthoredOn, m.RequesterDisplay, m.DosageText,
|
|
now, existingID,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: update medication_request %s: %w", existingID, err)
|
|
}
|
|
return existingID, nil
|
|
}
|
|
|
|
id := uuid.NewString()
|
|
_, err = s.db.Exec(`
|
|
INSERT INTO medication_requests (
|
|
id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, intent, med_code_text, med_code_system, med_code_code,
|
|
authored_on, requester_display, dosage_text, synced_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
id, m.FHIRID, m.EHRURL, m.PatientFHIRID,
|
|
m.Status, m.Intent, m.MedCodeText, m.MedCodeSystem, m.MedCodeCode,
|
|
m.AuthoredOn, m.RequesterDisplay, m.DosageText, now,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: insert medication_request fhir_id=%s: %w", m.FHIRID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// ListMedicationRequests returns all MedicationRequests for the given patient, newest first.
|
|
func (s *Store) ListMedicationRequests(patientFHIRID, ehrURL string) ([]models.MedicationRequest, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, intent, med_code_text, med_code_system, med_code_code,
|
|
authored_on, requester_display, dosage_text, synced_at
|
|
FROM medication_requests
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY authored_on DESC`,
|
|
patientFHIRID, ehrURL,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: list medication_requests: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []models.MedicationRequest
|
|
for rows.Next() {
|
|
var m models.MedicationRequest
|
|
if err := rows.Scan(
|
|
&m.ID, &m.FHIRID, &m.EHRURL, &m.PatientFHIRID,
|
|
&m.Status, &m.Intent, &m.MedCodeText, &m.MedCodeSystem, &m.MedCodeCode,
|
|
&m.AuthoredOn, &m.RequesterDisplay, &m.DosageText, &m.SyncedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("db: scan medication_request: %w", err)
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// AllergyIntolerance
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// UpsertAllergyIntolerance inserts or updates an AllergyIntolerance record keyed on (fhir_id, ehr_url).
|
|
func (s *Store) UpsertAllergyIntolerance(a *models.AllergyIntolerance) (string, error) {
|
|
now := time.Now().UTC()
|
|
|
|
var existingID string
|
|
err := s.db.QueryRow(
|
|
`SELECT id FROM allergy_intolerances WHERE fhir_id = ? AND ehr_url = ?`,
|
|
a.FHIRID, a.EHRURL,
|
|
).Scan(&existingID)
|
|
|
|
if err == nil {
|
|
_, err = s.db.Exec(`
|
|
UPDATE allergy_intolerances SET
|
|
patient_fhir_id = ?,
|
|
clinical_status = ?,
|
|
verification_status = ?,
|
|
type = ?,
|
|
category = ?,
|
|
criticality = ?,
|
|
code_text = ?,
|
|
code_system = ?,
|
|
code_code = ?,
|
|
recorded_date = ?,
|
|
reaction_severity = ?,
|
|
reaction_manifestation = ?,
|
|
synced_at = ?
|
|
WHERE id = ?`,
|
|
a.PatientFHIRID, a.ClinicalStatus, a.VerificationStatus,
|
|
a.Type, a.Category, a.Criticality,
|
|
a.CodeText, a.CodeSystem, a.CodeCode,
|
|
a.RecordedDate, a.ReactionSeverity, a.ReactionManifestation,
|
|
now, existingID,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: update allergy_intolerance %s: %w", existingID, err)
|
|
}
|
|
return existingID, nil
|
|
}
|
|
|
|
id := uuid.NewString()
|
|
_, err = s.db.Exec(`
|
|
INSERT INTO allergy_intolerances (
|
|
id, fhir_id, ehr_url, patient_fhir_id,
|
|
clinical_status, verification_status, type, category, criticality,
|
|
code_text, code_system, code_code, recorded_date,
|
|
reaction_severity, reaction_manifestation, synced_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
id, a.FHIRID, a.EHRURL, a.PatientFHIRID,
|
|
a.ClinicalStatus, a.VerificationStatus, a.Type, a.Category, a.Criticality,
|
|
a.CodeText, a.CodeSystem, a.CodeCode, a.RecordedDate,
|
|
a.ReactionSeverity, a.ReactionManifestation, now,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: insert allergy_intolerance fhir_id=%s: %w", a.FHIRID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// ListAllergyIntolerances returns all AllergyIntolerances for the given patient, newest first.
|
|
func (s *Store) ListAllergyIntolerances(patientFHIRID, ehrURL string) ([]models.AllergyIntolerance, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT id, fhir_id, ehr_url, patient_fhir_id,
|
|
clinical_status, verification_status, type, category, criticality,
|
|
code_text, code_system, code_code, recorded_date,
|
|
reaction_severity, reaction_manifestation, synced_at
|
|
FROM allergy_intolerances
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY recorded_date DESC`,
|
|
patientFHIRID, ehrURL,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: list allergy_intolerances: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []models.AllergyIntolerance
|
|
for rows.Next() {
|
|
var a models.AllergyIntolerance
|
|
if err := rows.Scan(
|
|
&a.ID, &a.FHIRID, &a.EHRURL, &a.PatientFHIRID,
|
|
&a.ClinicalStatus, &a.VerificationStatus, &a.Type, &a.Category, &a.Criticality,
|
|
&a.CodeText, &a.CodeSystem, &a.CodeCode, &a.RecordedDate,
|
|
&a.ReactionSeverity, &a.ReactionManifestation, &a.SyncedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("db: scan allergy_intolerance: %w", err)
|
|
}
|
|
out = append(out, a)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Immunization
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// UpsertImmunization inserts or updates an Immunization record keyed on (fhir_id, ehr_url).
|
|
func (s *Store) UpsertImmunization(imm *models.Immunization) (string, error) {
|
|
now := time.Now().UTC()
|
|
|
|
var existingID string
|
|
err := s.db.QueryRow(
|
|
`SELECT id FROM immunizations WHERE fhir_id = ? AND ehr_url = ?`,
|
|
imm.FHIRID, imm.EHRURL,
|
|
).Scan(&existingID)
|
|
|
|
if err == nil {
|
|
_, err = s.db.Exec(`
|
|
UPDATE immunizations SET
|
|
patient_fhir_id = ?,
|
|
status = ?,
|
|
vaccine_text = ?,
|
|
vaccine_system = ?,
|
|
vaccine_code = ?,
|
|
occurrence_date = ?,
|
|
primary_source = ?,
|
|
lot_number = ?,
|
|
synced_at = ?
|
|
WHERE id = ?`,
|
|
imm.PatientFHIRID, imm.Status,
|
|
imm.VaccineText, imm.VaccineSystem, imm.VaccineCode,
|
|
imm.OccurrenceDate, imm.PrimarySource, imm.LotNumber,
|
|
now, existingID,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: update immunization %s: %w", existingID, err)
|
|
}
|
|
return existingID, nil
|
|
}
|
|
|
|
id := uuid.NewString()
|
|
_, err = s.db.Exec(`
|
|
INSERT INTO immunizations (
|
|
id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, vaccine_text, vaccine_system, vaccine_code,
|
|
occurrence_date, primary_source, lot_number, synced_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
id, imm.FHIRID, imm.EHRURL, imm.PatientFHIRID,
|
|
imm.Status, imm.VaccineText, imm.VaccineSystem, imm.VaccineCode,
|
|
imm.OccurrenceDate, imm.PrimarySource, imm.LotNumber, now,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: insert immunization fhir_id=%s: %w", imm.FHIRID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// ListImmunizations returns all Immunizations for the given patient, newest first.
|
|
func (s *Store) ListImmunizations(patientFHIRID, ehrURL string) ([]models.Immunization, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, vaccine_text, vaccine_system, vaccine_code,
|
|
occurrence_date, primary_source, lot_number, synced_at
|
|
FROM immunizations
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY occurrence_date DESC`,
|
|
patientFHIRID, ehrURL,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: list immunizations: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []models.Immunization
|
|
for rows.Next() {
|
|
var imm models.Immunization
|
|
if err := rows.Scan(
|
|
&imm.ID, &imm.FHIRID, &imm.EHRURL, &imm.PatientFHIRID,
|
|
&imm.Status, &imm.VaccineText, &imm.VaccineSystem, &imm.VaccineCode,
|
|
&imm.OccurrenceDate, &imm.PrimarySource, &imm.LotNumber, &imm.SyncedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("db: scan immunization: %w", err)
|
|
}
|
|
out = append(out, imm)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Procedure
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// UpsertProcedure inserts or updates a Procedure record keyed on (fhir_id, ehr_url).
|
|
func (s *Store) UpsertProcedure(p *models.Procedure) (string, error) {
|
|
now := time.Now().UTC()
|
|
|
|
var existingID string
|
|
err := s.db.QueryRow(
|
|
`SELECT id FROM procedures WHERE fhir_id = ? AND ehr_url = ?`,
|
|
p.FHIRID, p.EHRURL,
|
|
).Scan(&existingID)
|
|
|
|
if err == nil {
|
|
_, err = s.db.Exec(`
|
|
UPDATE procedures SET
|
|
patient_fhir_id = ?,
|
|
status = ?,
|
|
code_text = ?,
|
|
code_system = ?,
|
|
code_code = ?,
|
|
performed_date = ?,
|
|
reason_text = ?,
|
|
outcome = ?,
|
|
synced_at = ?
|
|
WHERE id = ?`,
|
|
p.PatientFHIRID, p.Status,
|
|
p.CodeText, p.CodeSystem, p.CodeCode,
|
|
p.PerformedDate, p.ReasonText, p.Outcome,
|
|
now, existingID,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: update procedure %s: %w", existingID, err)
|
|
}
|
|
return existingID, nil
|
|
}
|
|
|
|
id := uuid.NewString()
|
|
_, err = s.db.Exec(`
|
|
INSERT INTO procedures (
|
|
id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, code_text, code_system, code_code,
|
|
performed_date, reason_text, outcome, synced_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
id, p.FHIRID, p.EHRURL, p.PatientFHIRID,
|
|
p.Status, p.CodeText, p.CodeSystem, p.CodeCode,
|
|
p.PerformedDate, p.ReasonText, p.Outcome, now,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: insert procedure fhir_id=%s: %w", p.FHIRID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// ListProcedures returns all Procedures for the given patient, newest first.
|
|
func (s *Store) ListProcedures(patientFHIRID, ehrURL string) ([]models.Procedure, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, code_text, code_system, code_code,
|
|
performed_date, reason_text, outcome, synced_at
|
|
FROM procedures
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY performed_date DESC`,
|
|
patientFHIRID, ehrURL,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: list procedures: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []models.Procedure
|
|
for rows.Next() {
|
|
var p models.Procedure
|
|
if err := rows.Scan(
|
|
&p.ID, &p.FHIRID, &p.EHRURL, &p.PatientFHIRID,
|
|
&p.Status, &p.CodeText, &p.CodeSystem, &p.CodeCode,
|
|
&p.PerformedDate, &p.ReasonText, &p.Outcome, &p.SyncedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("db: scan procedure: %w", err)
|
|
}
|
|
out = append(out, p)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Encounter
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// UpsertEncounter inserts or updates an Encounter record keyed on (fhir_id, ehr_url).
|
|
func (s *Store) UpsertEncounter(e *models.Encounter) (string, error) {
|
|
now := time.Now().UTC()
|
|
|
|
var existingID string
|
|
err := s.db.QueryRow(
|
|
`SELECT id FROM encounters WHERE fhir_id = ? AND ehr_url = ?`,
|
|
e.FHIRID, e.EHRURL,
|
|
).Scan(&existingID)
|
|
|
|
if err == nil {
|
|
_, err = s.db.Exec(`
|
|
UPDATE encounters SET
|
|
patient_fhir_id = ?,
|
|
status = ?,
|
|
class = ?,
|
|
type_text = ?,
|
|
period_start = ?,
|
|
period_end = ?,
|
|
reason_text = ?,
|
|
synced_at = ?
|
|
WHERE id = ?`,
|
|
e.PatientFHIRID, e.Status, e.Class, e.TypeText,
|
|
e.PeriodStart, e.PeriodEnd, e.ReasonText,
|
|
now, existingID,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: update encounter %s: %w", existingID, err)
|
|
}
|
|
return existingID, nil
|
|
}
|
|
|
|
id := uuid.NewString()
|
|
_, err = s.db.Exec(`
|
|
INSERT INTO encounters (
|
|
id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, class, type_text,
|
|
period_start, period_end, reason_text, synced_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
id, e.FHIRID, e.EHRURL, e.PatientFHIRID,
|
|
e.Status, e.Class, e.TypeText,
|
|
e.PeriodStart, e.PeriodEnd, e.ReasonText, now,
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("db: insert encounter fhir_id=%s: %w", e.FHIRID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// ListEncounters returns all Encounters for the given patient, newest first.
|
|
func (s *Store) ListEncounters(patientFHIRID, ehrURL string) ([]models.Encounter, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT id, fhir_id, ehr_url, patient_fhir_id,
|
|
status, class, type_text,
|
|
period_start, period_end, reason_text, synced_at
|
|
FROM encounters
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY period_start DESC`,
|
|
patientFHIRID, ehrURL,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: list encounters: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []models.Encounter
|
|
for rows.Next() {
|
|
var e models.Encounter
|
|
if err := rows.Scan(
|
|
&e.ID, &e.FHIRID, &e.EHRURL, &e.PatientFHIRID,
|
|
&e.Status, &e.Class, &e.TypeText,
|
|
&e.PeriodStart, &e.PeriodEnd, &e.ReasonText, &e.SyncedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("db: scan encounter: %w", err)
|
|
}
|
|
out = append(out, e)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PatientSync
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// RecordSync inserts a new sync event for a patient.
|
|
func (s *Store) RecordSync(patientFHIRID, ehrURL string, obsCount, condCount, docCount int) (*models.PatientSync, error) {
|
|
ps := &models.PatientSync{
|
|
ID: uuid.NewString(),
|
|
PatientFHIRID: patientFHIRID,
|
|
EHRURL: ehrURL,
|
|
SyncedAt: time.Now().UTC(),
|
|
ObsCount: obsCount,
|
|
CondCount: condCount,
|
|
DocCount: docCount,
|
|
}
|
|
_, err := s.db.Exec(`
|
|
INSERT INTO patient_syncs (id, patient_fhir_id, ehr_url, synced_at, obs_count, cond_count, doc_count)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
ps.ID, ps.PatientFHIRID, ps.EHRURL, ps.SyncedAt, ps.ObsCount, ps.CondCount, ps.DocCount,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db: record sync for patient %s: %w", patientFHIRID, err)
|
|
}
|
|
return ps, nil
|
|
}
|
|
|
|
// LatestSync retrieves the most recent sync event for a patient.
|
|
// Returns nil, nil when no sync has ever been performed.
|
|
func (s *Store) LatestSync(patientFHIRID, ehrURL string) (*models.PatientSync, error) {
|
|
ps := &models.PatientSync{}
|
|
err := s.db.QueryRow(`
|
|
SELECT id, patient_fhir_id, ehr_url, synced_at, obs_count, cond_count, doc_count
|
|
FROM patient_syncs
|
|
WHERE patient_fhir_id = ? AND ehr_url = ?
|
|
ORDER BY synced_at DESC
|
|
LIMIT 1`,
|
|
patientFHIRID, ehrURL,
|
|
).Scan(
|
|
&ps.ID, &ps.PatientFHIRID, &ps.EHRURL, &ps.SyncedAt,
|
|
&ps.ObsCount, &ps.CondCount, &ps.DocCount,
|
|
)
|
|
if err != nil {
|
|
if err.Error() == "sql: no rows in result set" {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("db: latest sync for patient %s: %w", patientFHIRID, err)
|
|
}
|
|
return ps, nil
|
|
}
|