Files
FHIR-Sandbox/app/db/clinical.go

787 lines
25 KiB
Go
Raw Normal View History

// 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
2026-02-20 17:17:26 -05:00
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,
2026-02-20 17:17:26 -05:00
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,
2026-02-20 17:17:26 -05:00
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,
2026-02-20 17:17:26 -05:00
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,
2026-02-20 17:17:26 -05:00
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,
2026-02-20 17:17:26 -05:00
&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()
}
2026-02-20 17:17:26 -05:00
// ---------------------------------------------------------------------------
// 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
2026-02-20 23:59:46 -05:00
patient_fhir_id = ?,
clinical_status = ?,
verification_status = ?,
type = ?,
category = ?,
criticality = ?,
code_text = ?,
code_system = ?,
code_code = ?,
recorded_date = ?,
reaction_severity = ?,
reaction_manifestation = ?,
synced_at = ?
2026-02-20 17:17:26 -05:00
WHERE id = ?`,
a.PatientFHIRID, a.ClinicalStatus, a.VerificationStatus,
a.Type, a.Category, a.Criticality,
a.CodeText, a.CodeSystem, a.CodeCode,
2026-02-20 23:59:46 -05:00
a.RecordedDate, a.ReactionSeverity, a.ReactionManifestation,
now, existingID,
2026-02-20 17:17:26 -05:00
)
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,
2026-02-20 23:59:46 -05:00
code_text, code_system, code_code, recorded_date,
reaction_severity, reaction_manifestation, synced_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2026-02-20 17:17:26 -05:00
id, a.FHIRID, a.EHRURL, a.PatientFHIRID,
a.ClinicalStatus, a.VerificationStatus, a.Type, a.Category, a.Criticality,
2026-02-20 23:59:46 -05:00
a.CodeText, a.CodeSystem, a.CodeCode, a.RecordedDate,
a.ReactionSeverity, a.ReactionManifestation, now,
2026-02-20 17:17:26 -05:00
)
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,
2026-02-20 23:59:46 -05:00
code_text, code_system, code_code, recorded_date,
reaction_severity, reaction_manifestation, synced_at
2026-02-20 17:17:26 -05:00
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,
2026-02-20 23:59:46 -05:00
&a.CodeText, &a.CodeSystem, &a.CodeCode, &a.RecordedDate,
&a.ReactionSeverity, &a.ReactionManifestation, &a.SyncedAt,
2026-02-20 17:17:26 -05:00
); err != nil {
return nil, fmt.Errorf("db: scan allergy_intolerance: %w", err)
}
out = append(out, a)
}
return out, rows.Err()
}
2026-02-20 23:59:46 -05:00
// ---------------------------------------------------------------------------
// 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
}