UI Upgrades

This commit is contained in:
2026-02-20 23:59:46 -05:00
parent 2171991dc8
commit c25b6e0c80
15 changed files with 2494 additions and 892 deletions

View File

@@ -401,22 +401,25 @@ func (s *Store) UpsertAllergyIntolerance(a *models.AllergyIntolerance) (string,
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 = ?,
synced_at = ?
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, now, existingID,
a.RecordedDate, a.ReactionSeverity, a.ReactionManifestation,
now, existingID,
)
if err != nil {
return "", fmt.Errorf("db: update allergy_intolerance %s: %w", existingID, err)
@@ -429,11 +432,13 @@ func (s *Store) UpsertAllergyIntolerance(a *models.AllergyIntolerance) (string,
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, synced_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
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, now,
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)
@@ -446,7 +451,8 @@ func (s *Store) ListAllergyIntolerances(patientFHIRID, ehrURL string) ([]models.
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, synced_at
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`,
@@ -463,7 +469,8 @@ func (s *Store) ListAllergyIntolerances(patientFHIRID, ehrURL string) ([]models.
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.SyncedAt,
&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)
}
@@ -472,6 +479,262 @@ func (s *Store) ListAllergyIntolerances(patientFHIRID, ehrURL string) ([]models.
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
// ---------------------------------------------------------------------------

View File

@@ -271,6 +271,414 @@ func TestListDocumentReferences_Empty(t *testing.T) {
}
}
// ---------------------------------------------------------------------------
// MedicationRequest tests
// ---------------------------------------------------------------------------
func TestUpsertMedicationRequest_NewAndUpdate(t *testing.T) {
store := newTestStore(t)
med := &models.MedicationRequest{
FHIRID: "med-001",
EHRURL: testEHRURL,
PatientFHIRID: testPatientID,
Status: "active",
Intent: "order",
MedCodeText: "Metformin 500mg",
MedCodeSystem: "http://www.nlm.nih.gov/research/umls/rxnorm",
MedCodeCode: "860975",
AuthoredOn: "2024-01-10",
RequesterDisplay: "Dr. Smith",
DosageText: "1 tablet twice daily",
}
id1, err := store.UpsertMedicationRequest(med)
if err != nil {
t.Fatalf("initial UpsertMedicationRequest: %v", err)
}
if id1 == "" {
t.Fatal("expected non-empty ID")
}
med.Status = "stopped"
id2, err := store.UpsertMedicationRequest(med)
if err != nil {
t.Fatalf("update UpsertMedicationRequest: %v", err)
}
if id1 != id2 {
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
}
rows, err := store.ListMedicationRequests(testPatientID, testEHRURL)
if err != nil {
t.Fatalf("ListMedicationRequests: %v", err)
}
if len(rows) != 1 {
t.Fatalf("expected 1 med, got %d", len(rows))
}
if rows[0].Status != "stopped" {
t.Errorf("Status: got %q, want stopped", rows[0].Status)
}
}
func TestListMedicationRequests_Empty(t *testing.T) {
store := newTestStore(t)
rows, err := store.ListMedicationRequests("no-such-patient", testEHRURL)
if err != nil {
t.Fatalf("ListMedicationRequests: %v", err)
}
if len(rows) != 0 {
t.Errorf("expected 0, got %d", len(rows))
}
}
// ---------------------------------------------------------------------------
// AllergyIntolerance tests (including reaction fields)
// ---------------------------------------------------------------------------
func TestUpsertAllergyIntolerance_WithReaction(t *testing.T) {
store := newTestStore(t)
allergy := &models.AllergyIntolerance{
FHIRID: "allergy-001",
EHRURL: testEHRURL,
PatientFHIRID: testPatientID,
ClinicalStatus: "active",
VerificationStatus: "confirmed",
Type: "allergy",
Category: "medication",
Criticality: "high",
CodeText: "Penicillin",
CodeSystem: "http://www.nlm.nih.gov/research/umls/rxnorm",
CodeCode: "7980",
RecordedDate: "2018-05-01",
ReactionSeverity: "severe",
ReactionManifestation: "Anaphylaxis",
}
id1, err := store.UpsertAllergyIntolerance(allergy)
if err != nil {
t.Fatalf("initial UpsertAllergyIntolerance: %v", err)
}
if id1 == "" {
t.Fatal("expected non-empty ID")
}
// Update reaction.
allergy.ReactionSeverity = "moderate"
allergy.ReactionManifestation = "Rash"
id2, err := store.UpsertAllergyIntolerance(allergy)
if err != nil {
t.Fatalf("update UpsertAllergyIntolerance: %v", err)
}
if id1 != id2 {
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
}
rows, err := store.ListAllergyIntolerances(testPatientID, testEHRURL)
if err != nil {
t.Fatalf("ListAllergyIntolerances: %v", err)
}
if len(rows) != 1 {
t.Fatalf("expected 1 allergy, got %d", len(rows))
}
got := rows[0]
if got.ReactionSeverity != "moderate" {
t.Errorf("ReactionSeverity: got %q, want moderate", got.ReactionSeverity)
}
if got.ReactionManifestation != "Rash" {
t.Errorf("ReactionManifestation: got %q, want Rash", got.ReactionManifestation)
}
}
func TestUpsertAllergyIntolerance_NoReaction(t *testing.T) {
store := newTestStore(t)
allergy := &models.AllergyIntolerance{
FHIRID: "allergy-no-rxn",
EHRURL: testEHRURL,
PatientFHIRID: testPatientID,
ClinicalStatus: "active",
CodeText: "Latex",
}
_, err := store.UpsertAllergyIntolerance(allergy)
if err != nil {
t.Fatalf("UpsertAllergyIntolerance (no reaction): %v", err)
}
rows, err := store.ListAllergyIntolerances(testPatientID, testEHRURL)
if err != nil {
t.Fatalf("ListAllergyIntolerances: %v", err)
}
if len(rows) != 1 {
t.Fatalf("expected 1, got %d", len(rows))
}
if rows[0].ReactionSeverity != "" {
t.Errorf("expected empty ReactionSeverity, got %q", rows[0].ReactionSeverity)
}
}
// ---------------------------------------------------------------------------
// Immunization tests
// ---------------------------------------------------------------------------
func TestUpsertImmunization_NewAndUpdate(t *testing.T) {
store := newTestStore(t)
imm := &models.Immunization{
FHIRID: "imm-001",
EHRURL: testEHRURL,
PatientFHIRID: testPatientID,
Status: "completed",
VaccineText: "Influenza, seasonal",
VaccineSystem: "http://hl7.org/fhir/sid/cvx",
VaccineCode: "141",
OccurrenceDate: "2023-10-01",
PrimarySource: true,
LotNumber: "LOT123",
}
id1, err := store.UpsertImmunization(imm)
if err != nil {
t.Fatalf("initial UpsertImmunization: %v", err)
}
if id1 == "" {
t.Fatal("expected non-empty ID")
}
imm.LotNumber = "LOT456"
id2, err := store.UpsertImmunization(imm)
if err != nil {
t.Fatalf("update UpsertImmunization: %v", err)
}
if id1 != id2 {
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
}
rows, err := store.ListImmunizations(testPatientID, testEHRURL)
if err != nil {
t.Fatalf("ListImmunizations: %v", err)
}
if len(rows) != 1 {
t.Fatalf("expected 1 immunization, got %d", len(rows))
}
if rows[0].LotNumber != "LOT456" {
t.Errorf("LotNumber: got %q, want LOT456", rows[0].LotNumber)
}
if !rows[0].PrimarySource {
t.Error("PrimarySource should be true")
}
}
func TestListImmunizations_Empty(t *testing.T) {
store := newTestStore(t)
rows, err := store.ListImmunizations("no-such-patient", testEHRURL)
if err != nil {
t.Fatalf("ListImmunizations: %v", err)
}
if len(rows) != 0 {
t.Errorf("expected 0, got %d", len(rows))
}
}
func TestListImmunizations_OrderedNewestFirst(t *testing.T) {
store := newTestStore(t)
for _, item := range []struct {
id string
date string
}{
{"imm-a", "2022-09-01"},
{"imm-b", "2023-10-15"},
{"imm-c", "2021-03-01"},
} {
_, err := store.UpsertImmunization(&models.Immunization{
FHIRID: item.id,
EHRURL: testEHRURL,
PatientFHIRID: testPatientID,
Status: "completed",
OccurrenceDate: item.date,
})
if err != nil {
t.Fatalf("UpsertImmunization %s: %v", item.id, err)
}
}
rows, err := store.ListImmunizations(testPatientID, testEHRURL)
if err != nil {
t.Fatalf("ListImmunizations: %v", err)
}
if len(rows) != 3 {
t.Fatalf("expected 3, got %d", len(rows))
}
if rows[0].FHIRID != "imm-b" {
t.Errorf("first: got %q, want imm-b", rows[0].FHIRID)
}
if rows[2].FHIRID != "imm-c" {
t.Errorf("last: got %q, want imm-c", rows[2].FHIRID)
}
}
// ---------------------------------------------------------------------------
// Procedure tests
// ---------------------------------------------------------------------------
func TestUpsertProcedure_NewAndUpdate(t *testing.T) {
store := newTestStore(t)
proc := &models.Procedure{
FHIRID: "proc-001",
EHRURL: testEHRURL,
PatientFHIRID: testPatientID,
Status: "completed",
CodeText: "Appendectomy",
CodeSystem: "http://snomed.info/sct",
CodeCode: "80146002",
PerformedDate: "2019-06-15",
ReasonText: "Acute appendicitis",
Outcome: "Successful procedure",
}
id1, err := store.UpsertProcedure(proc)
if err != nil {
t.Fatalf("initial UpsertProcedure: %v", err)
}
if id1 == "" {
t.Fatal("expected non-empty ID")
}
proc.Outcome = "Procedure completed without complications"
id2, err := store.UpsertProcedure(proc)
if err != nil {
t.Fatalf("update UpsertProcedure: %v", err)
}
if id1 != id2 {
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
}
rows, err := store.ListProcedures(testPatientID, testEHRURL)
if err != nil {
t.Fatalf("ListProcedures: %v", err)
}
if len(rows) != 1 {
t.Fatalf("expected 1 procedure, got %d", len(rows))
}
if rows[0].Outcome != "Procedure completed without complications" {
t.Errorf("Outcome: got %q", rows[0].Outcome)
}
}
func TestListProcedures_Empty(t *testing.T) {
store := newTestStore(t)
rows, err := store.ListProcedures("no-such-patient", testEHRURL)
if err != nil {
t.Fatalf("ListProcedures: %v", err)
}
if len(rows) != 0 {
t.Errorf("expected 0, got %d", len(rows))
}
}
// ---------------------------------------------------------------------------
// Encounter tests
// ---------------------------------------------------------------------------
func TestUpsertEncounter_NewAndUpdate(t *testing.T) {
store := newTestStore(t)
enc := &models.Encounter{
FHIRID: "enc-001",
EHRURL: testEHRURL,
PatientFHIRID: testPatientID,
Status: "finished",
Class: "AMB",
TypeText: "Office visit",
PeriodStart: "2024-03-10",
PeriodEnd: "2024-03-10",
ReasonText: "Annual physical",
}
id1, err := store.UpsertEncounter(enc)
if err != nil {
t.Fatalf("initial UpsertEncounter: %v", err)
}
if id1 == "" {
t.Fatal("expected non-empty ID")
}
enc.Status = "cancelled"
id2, err := store.UpsertEncounter(enc)
if err != nil {
t.Fatalf("update UpsertEncounter: %v", err)
}
if id1 != id2 {
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
}
rows, err := store.ListEncounters(testPatientID, testEHRURL)
if err != nil {
t.Fatalf("ListEncounters: %v", err)
}
if len(rows) != 1 {
t.Fatalf("expected 1 encounter, got %d", len(rows))
}
if rows[0].Status != "cancelled" {
t.Errorf("Status: got %q, want cancelled", rows[0].Status)
}
if rows[0].Class != "AMB" {
t.Errorf("Class: got %q, want AMB", rows[0].Class)
}
}
func TestListEncounters_Empty(t *testing.T) {
store := newTestStore(t)
rows, err := store.ListEncounters("no-such-patient", testEHRURL)
if err != nil {
t.Fatalf("ListEncounters: %v", err)
}
if len(rows) != 0 {
t.Errorf("expected 0, got %d", len(rows))
}
}
func TestListEncounters_OrderedNewestFirst(t *testing.T) {
store := newTestStore(t)
for _, item := range []struct {
id string
start string
}{
{"enc-a", "2023-01-01"},
{"enc-b", "2024-06-01"},
{"enc-c", "2022-12-01"},
} {
_, err := store.UpsertEncounter(&models.Encounter{
FHIRID: item.id,
EHRURL: testEHRURL,
PatientFHIRID: testPatientID,
Status: "finished",
PeriodStart: item.start,
})
if err != nil {
t.Fatalf("UpsertEncounter %s: %v", item.id, err)
}
}
rows, err := store.ListEncounters(testPatientID, testEHRURL)
if err != nil {
t.Fatalf("ListEncounters: %v", err)
}
if len(rows) != 3 {
t.Fatalf("expected 3, got %d", len(rows))
}
if rows[0].FHIRID != "enc-b" {
t.Errorf("first: got %q, want enc-b", rows[0].FHIRID)
}
if rows[2].FHIRID != "enc-c" {
t.Errorf("last: got %q, want enc-c", rows[2].FHIRID)
}
}
// ---------------------------------------------------------------------------
// PatientSync tests
// ---------------------------------------------------------------------------

View File

@@ -239,12 +239,81 @@ var migrations = []migration{
CREATE INDEX IF NOT EXISTS idx_allergy_intolerances_patient ON allergy_intolerances(patient_fhir_id, ehr_url);
`,
},
// Future migrations: append new entries here with incrementing version numbers.
// Example:
// {
// version: 2,
// sql: `ALTER TABLE users ADD COLUMN phone TEXT NOT NULL DEFAULT '';`,
// },
{
version: 5,
sql: `
ALTER TABLE allergy_intolerances ADD COLUMN reaction_severity TEXT NOT NULL DEFAULT '';
ALTER TABLE allergy_intolerances ADD COLUMN reaction_manifestation TEXT NOT NULL DEFAULT '';
`,
},
{
version: 6,
sql: `
CREATE TABLE IF NOT EXISTS immunizations (
id TEXT PRIMARY KEY,
fhir_id TEXT NOT NULL,
ehr_url TEXT NOT NULL,
patient_fhir_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT '',
vaccine_text TEXT NOT NULL DEFAULT '',
vaccine_system TEXT NOT NULL DEFAULT '',
vaccine_code TEXT NOT NULL DEFAULT '',
occurrence_date TEXT NOT NULL DEFAULT '',
primary_source INTEGER NOT NULL DEFAULT 0,
lot_number TEXT NOT NULL DEFAULT '',
synced_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(fhir_id, ehr_url)
);
CREATE INDEX IF NOT EXISTS idx_immunizations_patient ON immunizations(patient_fhir_id, ehr_url);
`,
},
{
version: 7,
sql: `
CREATE TABLE IF NOT EXISTS procedures (
id TEXT PRIMARY KEY,
fhir_id TEXT NOT NULL,
ehr_url TEXT NOT NULL,
patient_fhir_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT '',
code_text TEXT NOT NULL DEFAULT '',
code_system TEXT NOT NULL DEFAULT '',
code_code TEXT NOT NULL DEFAULT '',
performed_date TEXT NOT NULL DEFAULT '',
reason_text TEXT NOT NULL DEFAULT '',
outcome TEXT NOT NULL DEFAULT '',
synced_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(fhir_id, ehr_url)
);
CREATE INDEX IF NOT EXISTS idx_procedures_patient ON procedures(patient_fhir_id, ehr_url);
`,
},
{
version: 8,
sql: `
CREATE TABLE IF NOT EXISTS encounters (
id TEXT PRIMARY KEY,
fhir_id TEXT NOT NULL,
ehr_url TEXT NOT NULL,
patient_fhir_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT '',
class TEXT NOT NULL DEFAULT '',
type_text TEXT NOT NULL DEFAULT '',
period_start TEXT NOT NULL DEFAULT '',
period_end TEXT NOT NULL DEFAULT '',
reason_text TEXT NOT NULL DEFAULT '',
synced_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(fhir_id, ehr_url)
);
CREATE INDEX IF NOT EXISTS idx_encounters_patient ON encounters(patient_fhir_id, ehr_url);
`,
},
{
version: 9,
sql: `
ALTER TABLE users ADD COLUMN mrn TEXT NOT NULL DEFAULT '';
`,
},
}
// migrate applies any migrations that have not yet been run, in order.
@@ -315,6 +384,7 @@ func (s *Store) UpsertUser(u *models.User) (string, error) {
first_name = ?,
middle_name = ?,
last_name = ?,
mrn = ?,
dob = ?,
gender = ?,
email = ?,
@@ -322,7 +392,7 @@ func (s *Store) UpsertUser(u *models.User) (string, error) {
role = ?,
updated_at = ?
WHERE id = ?`,
u.FirstName, u.MiddleName, u.LastName,
u.FirstName, u.MiddleName, u.LastName, u.MRN,
u.DOB, u.Gender, u.Email,
u.FHIRResourceType, string(u.Role),
now, existingID,
@@ -342,11 +412,11 @@ func (s *Store) UpsertUser(u *models.User) (string, error) {
_, err = s.db.Exec(`
INSERT INTO users (
id, fhir_resource_type, fhir_id, ehr_url, role,
first_name, middle_name, last_name, dob, gender, email,
first_name, middle_name, last_name, mrn, dob, gender, email,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
id, u.FHIRResourceType, u.FHIRID, u.EHRURL, string(u.Role),
u.FirstName, u.MiddleName, u.LastName, u.DOB, u.Gender, u.Email,
u.FirstName, u.MiddleName, u.LastName, u.MRN, u.DOB, u.Gender, u.Email,
now, now,
)
if err != nil {
@@ -361,13 +431,13 @@ func (s *Store) GetUserByFHIRID(fhirID, ehrURL string) (*models.User, error) {
u := &models.User{}
err := s.db.QueryRow(`
SELECT id, fhir_resource_type, fhir_id, ehr_url, role,
first_name, middle_name, last_name, dob, gender, email,
first_name, middle_name, last_name, mrn, dob, gender, email,
created_at, updated_at
FROM users WHERE fhir_id = ? AND ehr_url = ?`,
fhirID, ehrURL,
).Scan(
&u.ID, &u.FHIRResourceType, &u.FHIRID, &u.EHRURL, &u.Role,
&u.FirstName, &u.MiddleName, &u.LastName, &u.DOB, &u.Gender, &u.Email,
&u.FirstName, &u.MiddleName, &u.LastName, &u.MRN, &u.DOB, &u.Gender, &u.Email,
&u.CreatedAt, &u.UpdatedAt,
)
if err != nil {
@@ -381,12 +451,12 @@ func (s *Store) GetUserByID(id string) (*models.User, error) {
u := &models.User{}
err := s.db.QueryRow(`
SELECT id, fhir_resource_type, fhir_id, ehr_url, role,
first_name, middle_name, last_name, dob, gender, email,
first_name, middle_name, last_name, mrn, dob, gender, email,
created_at, updated_at
FROM users WHERE id = ?`, id,
).Scan(
&u.ID, &u.FHIRResourceType, &u.FHIRID, &u.EHRURL, &u.Role,
&u.FirstName, &u.MiddleName, &u.LastName, &u.DOB, &u.Gender, &u.Email,
&u.FirstName, &u.MiddleName, &u.LastName, &u.MRN, &u.DOB, &u.Gender, &u.Email,
&u.CreatedAt, &u.UpdatedAt,
)
if err != nil {
@@ -399,7 +469,7 @@ func (s *Store) GetUserByID(id string) (*models.User, error) {
func (s *Store) ListUsersByRole(role models.Role, ehrURL string) ([]models.User, error) {
rows, err := s.db.Query(`
SELECT id, fhir_resource_type, fhir_id, ehr_url, role,
first_name, middle_name, last_name, dob, gender, email,
first_name, middle_name, last_name, mrn, dob, gender, email,
created_at, updated_at
FROM users WHERE role = ? AND ehr_url = ?
ORDER BY last_name ASC, first_name ASC`,
@@ -415,7 +485,7 @@ func (s *Store) ListUsersByRole(role models.Role, ehrURL string) ([]models.User,
var u models.User
if err := rows.Scan(
&u.ID, &u.FHIRResourceType, &u.FHIRID, &u.EHRURL, &u.Role,
&u.FirstName, &u.MiddleName, &u.LastName, &u.DOB, &u.Gender, &u.Email,
&u.FirstName, &u.MiddleName, &u.LastName, &u.MRN, &u.DOB, &u.Gender, &u.Email,
&u.CreatedAt, &u.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("db: scan user: %w", err)