FhirClient.js•19.7 kB
import axios from "axios";
import { calculateTimeframeDate } from "./Helper.js";
export class FhirClient {
client;
accessToken;
constructor(baseUrl) {
this.client = axios.create({
baseURL: baseUrl,
headers: {
//Authorization: `Bearer ${apiKey}`,
Accept: "application/fhir+json",
Authorization: `Bearer ${this.accessToken}`
},
});
}
setAccessToken(token) {
this.accessToken = token;
this.client.defaults.headers.Authorization = `Bearer ${this.accessToken}`;
}
async search(resourceType, params = {}) {
const searchParams = new URLSearchParams(params);
const response = await this.client.get(`/${resourceType}?${searchParams}`);
return response.data;
}
async get(resourceType, id) {
const response = await this.client.get(`/${resourceType}/${id}`);
return response.data;
}
async executeQuery(queryParams) {
const params = this.buildSearchParams(queryParams);
const response = await this.client.get(`/${queryParams.resourceType}?${params}`);
return this.formatResults(response.data, queryParams.resourceType);
}
async getActiveConditions() {
const response = await this.client.get('/Condition?clinical-status=active');
return this.formatResponse("fhir://Condition/active", response.data);
}
async findPatient(args) {
const params = new URLSearchParams();
if (args.lastName)
params.append('family', args.lastName);
if (args.firstName)
params.append('given', args.firstName);
if (args.birthDate)
params.append('birthdate', args.birthDate);
const response = await this.client.get(`/Patient?${params}`);
// Check if we have results
if (!response.data?.entry?.[0]?.resource) {
return this.formatResponse("fhir://Patient/search", { message: "No patients found" });
}
const resource = response.data.entry[0].resource;
const name = resource.name?.[0] ?? {};
const address = resource.address?.[0] ?? {};
const patient = {
name: name.given?.[0] ?? 'Unknown',
familyName: name.family ?? 'Unknown',
dob: resource.birthDate ?? 'Unknown',
gender: resource.gender ?? 'Unknown',
address: address.line?.[0] ?? 'Unknown',
city: address.city ?? 'Unknown',
state: address.state ?? 'Unknown',
zip: address.postalCode ?? 'Unknown',
id: resource.id ?? 'Unknown'
};
return this.formatResponse("fhir://Patient/search", patient);
}
handleError(error) {
const errorMessage = axios.isAxiosError(error)
? `FHIR API error: ${error.response?.data?.issue?.[0]?.details?.text ?? error.message}`
: error.message;
return { content: [{ type: "text", text: errorMessage }], isError: true };
}
buildSearchParams(queryParams) {
const params = new URLSearchParams();
if (queryParams.codes?.length)
params.append('code', queryParams.codes.join(','));
if (queryParams.dateRange) {
if (queryParams.dateRange.start)
params.append('date', `ge${queryParams.dateRange.start}`);
if (queryParams.dateRange.end)
params.append('date', `le${queryParams.dateRange.end}`);
}
return params;
}
formatResults(data, resourceType) {
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
formatResponse(uri, data) {
return {
content: [{
type: "text",
text: JSON.stringify(data, null, 2)
}]
};
}
async getPatientObservations(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.code)
params.append('code', args.code);
if (args.status)
params.append('status', args.status);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/Observation?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/observations`, response.data);
}
async getPatientConditions(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.status)
params.append('clinical-status', args.status);
if (args.onsetDate)
params.append('onset-date', args.onsetDate);
const response = await this.client.get(`/Condition?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/conditions`, response.data);
}
async getPatientMedications(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.status)
params.append('status', args.status);
const response = await this.client.get(`/MedicationRequest?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/medications`, response.data);
}
async getPatientEncounters(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.status)
params.append('status', args.status);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/Encounter?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/encounters`, response.data);
}
async getPatientAllergies(args) {
const params = new URLSearchParams();
params.append('patient', args.patientId);
if (args.status)
params.append('clinical-status', args.status);
if (args.type)
params.append('type', args.type);
if (args.category)
params.append('category', args.category);
const response = await this.client.get(`/AllergyIntolerance?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/allergies`, response.data);
}
async getPatientProcedures(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.status)
params.append('status', args.status);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/Procedure?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/procedures`, response.data);
}
async getPatientCareTeam(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.status)
params.append('status', args.status);
const response = await this.client.get(`/CareTeam?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/careteam`, response.data);
}
async getPatientCarePlans(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.status)
params.append('status', args.status);
if (args.category)
params.append('category', args.category);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/CarePlan?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/careplans`, response.data);
}
async getPatientVitalSigns(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/Observation?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/vital-signs`, response.data);
}
async getMedicationHistory(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/MedicationStatement?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/medication-history`, response.data);
}
async getPatientLabResults(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/Observation?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/lab-results`, response.data);
}
async getPatientAppointments(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/Appointment?${params}`);
return this.formatResponse(`fhir://Patient/${args.patientId}/appointments`, response.data);
}
async getPatientDocumentReferences(args) {
const params = new URLSearchParams();
params.append('patient', `${args.patientId}`);
if (args.type)
params.append('type', args.type);
if (args.category)
params.append('category', args.category);
if (args.status)
params.append('status', args.status);
if (args.dateFrom)
params.append('date', `ge${args.dateFrom}`);
if (args.dateTo)
params.append('date', `le${args.dateTo}`);
const response = await this.client.get(`/DocumentReference?${params}`);
// If we have document references, fetch and decode their content
if (response.data?.entry?.length > 0) {
const documentsWithContent = await Promise.all(response.data.entry.map(async (entry) => {
const docRef = entry.resource;
const processedDoc = { ...docRef, decodedContent: [] };
// Process each content attachment
if (docRef.content?.length > 0) {
for (const content of docRef.content) {
const attachment = content.attachment;
if (attachment?.data) {
// Inline base64 data
try {
const decodedData = Buffer.from(attachment.data, 'base64').toString('utf-8');
processedDoc.decodedContent.push({
contentType: attachment.contentType,
title: attachment.title,
decodedData: decodedData
});
}
catch (error) {
processedDoc.decodedContent.push({
contentType: attachment.contentType,
title: attachment.title,
error: 'Failed to decode base64 data'
});
}
}
else if (attachment?.url) {
// Reference to Binary resource
const binaryId = attachment.url.split('/').pop();
if (binaryId && attachment.url.includes('Binary/')) {
try {
const binaryResponse = await this.client.get(`/Binary/${binaryId}`);
let decodedData = binaryResponse.data;
// If the binary data is base64 encoded, decode it
if (typeof decodedData === 'string' && attachment.contentType?.includes('text')) {
try {
decodedData = Buffer.from(decodedData, 'base64').toString('utf-8');
}
catch {
// If decoding fails, keep original data
}
}
processedDoc.decodedContent.push({
contentType: attachment.contentType,
title: attachment.title,
url: attachment.url,
decodedData: decodedData
});
}
catch (error) {
processedDoc.decodedContent.push({
contentType: attachment.contentType,
title: attachment.title,
url: attachment.url,
error: `Failed to fetch Binary resource: ${error}`
});
}
}
}
}
}
return processedDoc;
}));
return this.formatResponse(`fhir://Patient/${args.patientId}/documents`, {
...response.data,
entry: documentsWithContent.map(doc => ({ resource: doc }))
});
}
return this.formatResponse(`fhir://Patient/${args.patientId}/documents`, response.data);
}
async getBinaryResource(args) {
const response = await this.client.get(`/Binary/${args.binaryId}`);
let processedData = response.data;
// Attempt to decode if it appears to be base64 encoded text
if (typeof response.data === 'string') {
try {
// Check if it's base64 by trying to decode it
const decoded = Buffer.from(response.data, 'base64').toString('utf-8');
// If successful and contains readable text, include both versions
processedData = {
originalData: response.data,
decodedData: decoded,
encoding: 'base64'
};
}
catch (error) {
// If decoding fails, keep original data
processedData = {
originalData: response.data,
note: 'Data may be binary or not base64 encoded'
};
}
}
return this.formatResponse(`fhir://Binary/${args.binaryId}`, processedData);
}
async getVitalSigns(patientId, timeframe) {
const parms = {
patient: patientId,
category: 'vital-signs',
_sort: '-date',
_count: '50'
};
if (timeframe) {
const date = calculateTimeframeDate(timeframe);
if (date) {
parms["date"] = `ge${date}`;
}
}
return await this.client.get(`/Observation?${parms}`);
}
async getPatientSummaryData(patientId) {
const [patient, conditions, medications, allergies, immunizations, procedures, carePlans, recentLabs, encounters, appointments] = await Promise.all([
this.get("Patient", patientId),
this.search("Condition", { patient: patientId }),
this.search("MedicationRequest", { patient: patientId }),
this.search("AllergyIntolerance", { patient: patientId }),
this.search("Immunization", { patient: patientId }),
this.search("Procedure", { patient: patientId }),
this.search("CarePlan", { patient: patientId }),
this.getPatientLabData(patientId),
this.search("Encounter", { patient: patientId }),
this.search("Appointment", { patient: patientId })
]);
return {
patient,
conditions,
medications,
allergies,
immunizations,
procedures,
carePlans,
recentLabs,
encounters,
appointments
};
}
// Additional helper functions for other prompts
async getPatientConditionData(patientId, timeframe) {
const searchParams = {
patient: patientId,
_sort: "date"
};
if (timeframe) {
const date = calculateTimeframeDate(timeframe);
if (date) {
searchParams["date"] = `ge${date}`;
}
}
return await this.search("Condition", searchParams);
}
async getPatientLabData(patientId, labType) {
const searchParams = {
patient: patientId,
category: "laboratory",
_sort: "-date"
};
if (labType) {
searchParams["code"] = labType;
}
return await this.search("Observation", searchParams);
}
async getPatientCareGapsData(patientId) {
const [patient, immunizations, procedures, carePlans] = await Promise.all([
this.get("Patient", patientId),
this.search("Immunization", { patient: patientId }),
this.search("Procedure", { patient: patientId }),
this.search("CarePlan", { patient: patientId, status: "active" })
]);
return {
patient,
immunizations,
procedures,
carePlans
};
}
getRelevantMetrics(observations, condition) {
// Map conditions to relevant LOINC codes
const metricMap = {
'Diabetes': ['4548-4', '17856-6'], // HbA1c, Glucose
'Hypertension': ['8480-6', '8462-4'], // Systolic BP, Diastolic BP
'Hyperlipidemia': ['2093-3', '2571-8'], // Cholesterol, Triglycerides
};
const conditionName = condition.code?.coding?.[0]?.display || '';
const relevantCodes = metricMap[conditionName] || [];
return observations
.filter((obs) => {
const obsCode = obs.resource.code?.coding?.[0]?.code;
return relevantCodes.includes(obsCode);
})
.map((obs) => {
const value = obs.resource.valueQuantity?.value || 'No value';
const unit = obs.resource.valueQuantity?.unit || '';
const date = obs.resource.effectiveDateTime?.split('T')[0] || 'unknown date';
const name = obs.resource.code?.coding?.[0]?.display || 'Unknown metric';
return `${name}: ${value} ${unit} (${date})`;
});
}
}