Upload files to ".Net Capstone Project"
This commit is contained in:
parent
0775ecf1e4
commit
4e1452f689
282
.Net Capstone Project/MedplumFhirService.cs
Normal file
282
.Net Capstone Project/MedplumFhirService.cs
Normal file
@ -0,0 +1,282 @@
|
||||
using ClinicalInsightsPro.API.Data;
|
||||
using ClinicalInsightsPro.API.DTOs;
|
||||
using ClinicalInsightsPro.API.Services.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ClinicalInsightsPro.API.Services;
|
||||
|
||||
public class MedplumFhirService : IMedplumFhirService
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
private readonly ILogger<MedplumFhirService> _logger;
|
||||
|
||||
public MedplumFhirService(
|
||||
ApplicationDbContext db,
|
||||
ILogger<MedplumFhirService> logger)
|
||||
{
|
||||
_db = db;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<List<JsonElement>> GetMedplumResourcesAsync(string resourceType, string? patientId = null)
|
||||
{
|
||||
// Offline/demo mode has normalized tables, not the raw Medplum store.
|
||||
// The unified Medplum browser is intentionally live-only so the UI
|
||||
// always shows actual FHIR JSON when the feature is enabled.
|
||||
throw new InvalidOperationException(
|
||||
"The Medplum resource browser requires Fhir:UseLiveMedplum=true. " +
|
||||
"Set MEDPLUM_USE_LIVE=true and configure MEDPLUM_BASE_URL, " +
|
||||
"MEDPLUM_TOKEN_URL, MEDPLUM_CLIENT_ID and MEDPLUM_CLIENT_SECRET.");
|
||||
}
|
||||
|
||||
public async Task<List<PatientSummaryDto>> GetPatientListAsync()
|
||||
{
|
||||
var patients = await _db.Patient
|
||||
.AsNoTracking()
|
||||
.OrderBy(x => x.GivenName)
|
||||
.ThenBy(x => x.FamilyName)
|
||||
.ToListAsync();
|
||||
|
||||
return patients.Select(p => new PatientSummaryDto
|
||||
{
|
||||
Id = p.Id,
|
||||
FullName = $"{p.GivenName} {p.FamilyName}",
|
||||
Age = CalculateAge(p.BirthDate),
|
||||
Gender = p.Gender,
|
||||
MedicalRecordNumber = p.MedicalRecordNumber
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<PatientChartDto> GetPatientChartAsync(string patientId)
|
||||
{
|
||||
var patient = await _db.Patient
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(p => p.Id == patientId);
|
||||
|
||||
if (patient == null)
|
||||
{
|
||||
throw new KeyNotFoundException(
|
||||
$"Patient '{patientId}' was not found.");
|
||||
}
|
||||
|
||||
var conditions = await _db.Condition
|
||||
.AsNoTracking()
|
||||
.Where(x => x.PatientId == patientId)
|
||||
.OrderByDescending(x => x.RecordedDate)
|
||||
.ToListAsync();
|
||||
|
||||
var observations = await _db.Observation
|
||||
.AsNoTracking()
|
||||
.Where(x => x.PatientId == patientId)
|
||||
.OrderByDescending(x => x.EffectiveDateTime)
|
||||
.ToListAsync();
|
||||
|
||||
var allergies = await _db.AllergyIntolerance
|
||||
.AsNoTracking()
|
||||
.Where(x => x.PatientId == patientId)
|
||||
.ToListAsync();
|
||||
|
||||
var medications = await _db.MedicationRequest
|
||||
.AsNoTracking()
|
||||
.Where(x => x.PatientId == patientId)
|
||||
.ToListAsync();
|
||||
|
||||
var encounters = await _db.Encounter
|
||||
.AsNoTracking()
|
||||
.Where(x => x.PatientId == patientId)
|
||||
.OrderByDescending(x => x.PeriodStart)
|
||||
.ToListAsync();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Retrieved chart for patient {PatientId}",
|
||||
patientId);
|
||||
|
||||
return new PatientChartDto
|
||||
{
|
||||
PatientId = patient.Id,
|
||||
|
||||
FullName = $"{patient.GivenName} {patient.FamilyName}",
|
||||
|
||||
Age = CalculateAge(patient.BirthDate),
|
||||
|
||||
MedicalRecordNumber = patient.MedicalRecordNumber,
|
||||
|
||||
IdentifierSystem = patient.IdentifierSystem,
|
||||
IdentifierValue = patient.IdentifierValue,
|
||||
|
||||
NameUse = patient.NameUse,
|
||||
Prefix = patient.Prefix,
|
||||
|
||||
GivenName = patient.GivenName,
|
||||
FamilyName = patient.FamilyName,
|
||||
|
||||
TelecomSystem = patient.TelecomSystem,
|
||||
TelecomUse = patient.TelecomUse,
|
||||
TelecomValue = patient.TelecomValue,
|
||||
|
||||
Gender = patient.Gender,
|
||||
|
||||
BloodGroup = patient.BloodGroup,
|
||||
Email = patient.Email,
|
||||
|
||||
BirthDate = patient.BirthDate,
|
||||
|
||||
AddressUse = patient.AddressUse,
|
||||
AddressType = patient.AddressType,
|
||||
AddressLine = patient.AddressLine,
|
||||
|
||||
City = patient.City,
|
||||
State = patient.State,
|
||||
PostalCode = patient.PostalCode,
|
||||
|
||||
MaritalStatusSystem = patient.MaritalStatusSystem,
|
||||
MaritalStatusCode = patient.MaritalStatusCode,
|
||||
MaritalStatusDisplay = patient.MaritalStatusDisplay,
|
||||
|
||||
ContactNameUse = patient.ContactNameUse,
|
||||
ContactPrefix = patient.ContactPrefix,
|
||||
|
||||
ContactTelecomSystem = patient.ContactTelecomSystem,
|
||||
ContactTelecomUse = patient.ContactTelecomUse,
|
||||
ContactTelecomValue = patient.ContactTelecomValue,
|
||||
|
||||
ContactAddressUse = patient.ContactAddressUse,
|
||||
ContactAddressType = patient.ContactAddressType,
|
||||
ContactAddressLine = patient.ContactAddressLine,
|
||||
|
||||
ContactCity = patient.ContactCity,
|
||||
ContactState = patient.ContactState,
|
||||
ContactPostalCode = patient.ContactPostalCode,
|
||||
|
||||
ContactGender = patient.ContactGender,
|
||||
ContactOrganizationId = patient.ContactOrganizationId,
|
||||
|
||||
ContactPeriodStart = patient.ContactPeriodStart,
|
||||
ContactPeriodEnd = patient.ContactPeriodEnd,
|
||||
|
||||
ManagingOrganizationId = patient.ManagingOrganizationId,
|
||||
|
||||
PhotoContentType = patient.PhotoContentType,
|
||||
PhotoUrl = patient.PhotoUrl,
|
||||
PhotoTitle = patient.PhotoTitle,
|
||||
|
||||
GeneralPractitionerId = patient.GeneralPractitionerId,
|
||||
|
||||
Conditions = conditions.Select(c => new ChartConditionDto
|
||||
{
|
||||
Id = c.Id,
|
||||
Code = c.ConditionCode,
|
||||
Display = c.ConditionDisplay,
|
||||
ClinicalStatus = c.ClinicalStatusDisplay,
|
||||
VerificationStatus = c.VerificationStatusDisplay,
|
||||
Severity = c.SeverityDisplay,
|
||||
OnsetDate = c.OnsetDateTime,
|
||||
AbatementDate = c.AbatementDateTime,
|
||||
RecordedDate = c.RecordedDate
|
||||
}).ToList(),
|
||||
|
||||
Allergies = allergies.Select(a => new ChartAllergyDto
|
||||
{
|
||||
Id = a.Id,
|
||||
Type = a.Type,
|
||||
ClinicalStatus = a.ClinicalStatus,
|
||||
VerificationStatus = a.VerificationStatus,
|
||||
Criticality = a.Criticality,
|
||||
RecorderPractitionerId = a.RecorderPractitionerId
|
||||
}).ToList(),
|
||||
|
||||
Medications = medications.Select(m => new ChartMedicationDto
|
||||
{
|
||||
Id = m.Id,
|
||||
MedicationCode = m.MedicationCode,
|
||||
MedicationDisplay = m.MedicationDisplay,
|
||||
Status = m.Status,
|
||||
Intent = m.Intent,
|
||||
Priority = m.Priority
|
||||
}).ToList(),
|
||||
|
||||
Observations = observations.Select(o => new ChartObservationDto
|
||||
{
|
||||
Id = o.Id,
|
||||
Code = o.ObservationCode,
|
||||
Display = o.ObservationDisplay,
|
||||
Status = o.Status,
|
||||
CategoryDisplay = o.CategoryDisplay,
|
||||
InterpretationCode = o.InterpretationCode,
|
||||
InterpretationDisplay = o.InterpretationDisplay,
|
||||
EffectiveDate = o.EffectiveDateTime,
|
||||
NoteText = o.NoteText
|
||||
}).ToList(),
|
||||
|
||||
Encounters = encounters.Select(e => new ChartEncounterDto
|
||||
{
|
||||
Id = e.Id,
|
||||
Status = e.Status,
|
||||
ClassCode = e.ClassCode,
|
||||
ClassDisplay = e.ClassDisplay,
|
||||
TypeDisplay = e.TypeDisplay,
|
||||
ServiceTypeDisplay = e.ServiceTypeDisplay,
|
||||
PeriodStart = e.PeriodStart,
|
||||
PeriodEnd = e.PeriodEnd
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private static int CalculateAge(DateTime birthDate)
|
||||
{
|
||||
var today = DateTime.UtcNow;
|
||||
|
||||
var age = today.Year - birthDate.Year;
|
||||
|
||||
if (birthDate.Date > today.AddYears(-age))
|
||||
{
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
}
|
||||
|
||||
// ---- Two-way sync additions ----
|
||||
// This class is the OFFLINE/local implementation (used when
|
||||
// Fhir:UseLiveMedplum=false) — it reads/writes only the local database
|
||||
// and never talks to a real Medplum server, so there is nothing to push
|
||||
// or pull here. These methods exist only so this class still satisfies
|
||||
// IMedplumFhirService; they throw/no-op with a clear message instead of
|
||||
// silently pretending to sync. Set MEDPLUM_USE_LIVE=true in .env to
|
||||
// switch the app over to LiveMedplumFhirService, which implements the
|
||||
// real sync against Medplum's FHIR API.
|
||||
public Task<string> CreatePatientInMedplumAsync(Models.Patient patient)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"CreatePatientInMedplumAsync called while running in offline/local mode " +
|
||||
"(Fhir:UseLiveMedplum=false) — patient {Id} was saved locally only, not pushed to Medplum. " +
|
||||
"Set MEDPLUM_USE_LIVE=true in .env to enable Medplum sync.", patient.Id);
|
||||
return Task.FromResult(string.Empty);
|
||||
}
|
||||
|
||||
public Task UpdatePatientInMedplumAsync(Models.Patient patient)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"UpdatePatientInMedplumAsync called while running in offline/local mode " +
|
||||
"(Fhir:UseLiveMedplum=false) — patient {Id} was updated locally only, not pushed to Medplum.", patient.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DeletePatientInMedplumAsync(string medplumId)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"DeletePatientInMedplumAsync called while running in offline/local mode " +
|
||||
"(Fhir:UseLiveMedplum=false) — nothing was deleted in Medplum.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<List<Models.Patient>> PullAllPatientsFromMedplumAsync()
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"PullAllPatientsFromMedplumAsync called while running in offline/local mode " +
|
||||
"(Fhir:UseLiveMedplum=false) — returning an empty list. Set MEDPLUM_USE_LIVE=true in .env " +
|
||||
"to pull real data from Medplum.");
|
||||
return Task.FromResult(new List<Models.Patient>());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user