import type { ReadOptions } from '../hooks/anchorView/readOptions'; import { isHeld, overlayHeldBundle, refersToPatient, writtenSince } from '../utils/writeThroughHold'; import type { SaveTransport } from '../utils/saveOperation'; import { asRecordChanged, ifMatch, PAGE_OUT_OF_DATE_MESSAGE, RecordChangedError } from '../utils/recordChanged'; import { useState, useCallback, createContext, useContext, ReactNode, useEffect, useRef } from 'react'; import { Bundle, Condition, Patient, CodeableConcept, Practitioner, Encounter, Reference, ConditionEvidence, ConditionStage, RelatedPerson } from 'fhir/r4'; import { environment } from '../environments/environment.ts'; import { useFhirServiceContext } from './FhirService.tsx'; import { useHttpService } from './HttpService.tsx'; import type { ConditionSource, ContentClass, ContentClassResult } from '../utils/conditionContentClass'; import type { SelfReport } from '../utils/selfReport'; import { formatFhirHumanName } from '../utils/fhirHumanName.tsx'; import { useEncounterServiceContext } from './EncounterService.tsx'; import { usePatientServiceContext } from './PatientService.tsx'; import { formatFhirCodeableConcept, getCodeableConceptDisplay } from '../utils/fhirCodeableConcept.tsx'; import { useCacheOwner } from '../utils/cacheOwner'; import { applyArchive, isArchived, type ArchiveReason } from '../utils/archivedResource'; import { codeableStatusLabel } from '../utils/fhirStatusVocabulary.ts'; import { encounterWithDiagnosis } from '../utils/encounterDiagnosisLink'; import { fetchAllPagesAsBundle } from '../utils/fetchAllPagesAsBundle'; import { searchPageSize } from '../utils/pageSizeOverride'; const API_URL = environment.fhirClinicalApi; /** * The diagnosis was recorded and the link back to its visit was not. * * Its own type rather than a plain Error because the two halves have opposite consequences and a * caller cannot tell them apart from a message. The Condition exists and is the clinical fact; what * failed is the reference that makes the visit findable from it. A screen that reports this as * "failed to create" would send a clinician to record the same diagnosis twice. */ /** What a reader is told when the diagnosis is stored and only its visit link failed. */ export const DIAGNOSIS_NOT_LINKED_MESSAGE = 'The diagnosis was saved, but it could not be linked to this visit.' + " It is on the patient's record, but this visit will not show it." + ' Recording it again will not help.'; export class DiagnosisVisitLinkError extends Error { readonly condition: Condition; constructor(condition: Condition, cause?: unknown) { super('The diagnosis was saved, but it could not be linked to this visit.'); this.name = 'DiagnosisVisitLinkError'; this.condition = condition; // Kept so the underlying transport failure is still reachable for logging - the message above is // for the reader, not for whoever is debugging it. (this as { cause?: unknown }).cause = cause; } } export const useConditionService = () => { const { get, post, put, delete: del } = useHttpService(); const { patientState } = usePatientServiceContext(); const { transformToCodeableConcept, transformFromCodeableConcept, transformToFhirAge, transformFromFhirAge, createReferenceObject, formatDate, formatDateTime, formatDateTimeForInput, formatDateTimeForDisplay, addFhirPeriodIfApplicable, addPeriodControlsIfApplicable, unpackBundle, transformToReferenceCollection, transformFromReferenceCollection, extractReferenceId } = useFhirServiceContext(); const { getEncounterDisplay, changeEncounter } = useEncounterServiceContext(); const [conditionsList, setConditionsList] = useState([]); const [shouldReload, setShouldReload] = useState(false); const [lastUpserted, setLastUpserted] = useState(null); const [unsaved, setUnsaved] = useState(false); const [conditionsBundle, setConditionsBundle] = useState | null>(null); const conditionsBundleBundleRef = useRef | null>(null); const lastFetchTimeRef = useRef(0); const usCoreConditionProfilePrefix = 'http://hl7.org/fhir/us/core/StructureDefinition'; // US Core Condition Encounter Diagnosis Profile const usCoreConditionEncounterDiagnosisProfile = { category: [ { system: 'http://terminology.hl7.org/CodeSystem/condition-category', code: 'encounter-diagnosis', display: 'Encounter Diagnosis' } ], conditionProfile: `${usCoreConditionProfilePrefix}/us-core-condition-encounter-diagnosis`, }; // US Core Condition Problems and Health Concerns Profile const usCoreConditionProblemsHealthConcernsProfile = { conditionProfile: `${usCoreConditionProfilePrefix}/us-core-condition-problems-health-concerns`, }; useEffect(() => { conditionsBundleBundleRef.current = conditionsBundle; }, [conditionsBundle]); /** * WHOSE CONDITIONS ARE THESE? THE CACHE DID NOT KNOW, AND THAT IS THE DEFECT. * * This context is app-wide and keyed on nothing. The reset below clears it only when `patientState` * goes FALSY - but moving from one patient's chart to another never does that, so the bundle * survived the switch and `fetchConditionsWithPolling` served the PREVIOUS patient's conditions as * a cache hit. No request went out, and the clinician read the wrong problem list. * * MEASURED 2026-08-29 on Zelda Zztestalpha: navigating in-session showed ONE row while a full page * reload of the same URL showed TWO - "Diabetes mellitus" and "Acute pharyngitis". Both counts came * from the same intact record. * * A problem list is read to decide what to do next, and a short one reads as "little history". * * So the cache now records WHO it is for, and a different patient is a MISS rather than a hit. That * is deliberately not a refetch-on-every-navigation: the same patient with a fresh bundle is still * served from cache and still renders without a spinner. */ // Which full list read is published, for whom and when it started: a reader asking whether its list was read under // a newer entitlement looks here, never at its own request (a superseded read resolves without publishing). const [listRead, setListRead] = useState<{ patientId?: string; startedAt: number } | undefined>(undefined); const cacheOwner = useCacheOwner('Condition', () => { setLastUpserted(null); setConditionsBundle(null); setConditionsList([]); lastFetchTimeRef.current = 0; }); useEffect(() => { if (!patientState) { setConditionsBundle(null); setConditionsList([]); lastFetchTimeRef.current = 0; cacheOwner.release(); } }, [patientState, cacheOwner.release]); const fetchConditions = useCallback(async (patient: Patient, options?: ReadOptions): Promise> => { return overlayHeldBundle('Condition', await fetchAllPagesAsBundle((pageParam) => { const params = new URLSearchParams(); params.append('_count', searchPageSize('100')); params.append('subject', patient.id as string); if (pageParam) params.set('page', pageParam); return get>(`${API_URL}/Condition?${params.toString()}`, options); }), (resource) => refersToPatient(resource, patient.id)) as Bundle; }, [get]); const fetchConditionsWithPolling = useCallback(async (patient: Patient, forceReload = false): Promise> => { const now = Date.now(); const cacheAge = now - lastFetchTimeRef.current; const cacheFresh = cacheAge < 5 * 60 * 1000; // `cacheOwner.isFor` is the load-bearing clause. Without it a fresh bundle belonging to the // previous patient satisfies every other condition here and is returned as a hit. if (!forceReload && !shouldReload && !writtenSince('Condition', lastFetchTimeRef.current) && conditionsBundleBundleRef.current && cacheFresh && cacheOwner.isCurrentFor(patient.id)) { return conditionsBundleBundleRef.current; } const operation = cacheOwner.begin(); let retries = 10; const delay = 3000; let found = false; let bundle: Bundle | null = null; let entries: Condition[]; do { bundle = await fetchConditions(patient); entries = unpackBundle(bundle).filter((r) => !isArchived(r)); found = lastUpserted ? entries.some(e => e.id === lastUpserted) || isHeld('Condition', lastUpserted) : true; if (!found) { await new Promise(resolve => setTimeout(resolve, delay)); } retries--; } while (!found && retries > 0 && cacheOwner.publishes(operation)); if (!cacheOwner.publishes(operation)) return cacheOwner.answerFor(operation, bundle); setConditionsBundle(bundle); setConditionsList(entries.filter((r) => !isArchived(r))); setLastUpserted(null); setShouldReload(false); lastFetchTimeRef.current = now; // Stamped together with the data, never before it: on a thrown fetch the ref must keep naming // the patient whose list is actually in state, or the next call reads a hit for data it does // not have. cacheOwner.claim(patient.id, operation); setListRead({ patientId: patient.id, startedAt: now }); return bundle; }, [fetchConditions, unpackBundle, lastUpserted, shouldReload]); // `fresh`: the record as stored now, never the cached copy - what a refused save shows as "saved now". const fetchCondition = useCallback(async (id: string, fresh = false, transport?: SaveTransport): Promise => { const cachedCondition = !fresh && cacheOwner.isCurrent(cacheOwner.claimed) ? conditionsList.find(condition => condition.id === id) : undefined; if (cachedCondition) { return cachedCondition; } return get(`${API_URL}/Condition/${id}`, { ...transport }); }, [get, conditionsList]); const fetchConditionHistory = useCallback(async (id: string, options?: ReadOptions): Promise> => { return get>(`${API_URL}/Condition/${id}/_history`, options); }, [get]); /** * THE OTHER HALF OF THE JOIN, and without it a diagnosis recorded in a visit is invisible from * that visit forever. * * `Condition.encounter` is written on create and always was. Nothing wrote the reverse, and the * reverse is the direction that gets READ: the record stream builds a visit's subjects from * `Encounter.diagnosis[]`, so the About lens - everything about this condition, across every * practice and all time - reported `0 items` for every diagnosis the product itself recorded. * * It fails as an EMPTY RECORD rather than as a broken link, which is the dangerous shape: a * clinician reads nothing and concludes there is no history. The history is there; the pointer * is not. * * The text fallback cannot rescue it either. `visitEvent` also derives subjects from * `reasonCode[].text`, but those carry an id made from the label while the filter matches a * condition UUID, so an uncoded label can never match a coded filter. Only a real reference here * links the two. * * IT READS THE ENCOUNTER BACK RATHER THAN TRUSTING THE ONE IN CONTEXT. `encounterState` is a * snapshot from whenever the workspace last loaded it, and writing an array built from a stale * copy is how a second diagnosis erases the first. */ const linkDiagnosisToItsEncounter = useCallback(async (created: Condition, transport?: SaveTransport): Promise => { // NARROWED RATHER THAN CAST. `extractReferenceId` answers `string | string[]` because it also // serves multi-valued fields, and the neighbouring call sites in this file cast it. A cast here // would hand an array to a URL and fetch a nonsense encounter; there is exactly one encounter on // a Condition, so anything else is a reason not to write the link. const encounterId = extractReferenceId(created.encounter); if (typeof encounterId !== 'string' || !encounterId || !created.id) return; // Added to the visit as stored now and written over that version, so a diagnosis linked by another // tab in between is kept. Already linked: nothing is written, since a version saying something changed // when nothing did is a lie the history cannot later tell from a real edit. const diagnosis = createReferenceObject('Condition', created.id, formatFhirCodeableConcept(created.code)); await changeEncounter(encounterId, (current) => encounterWithDiagnosis(current, diagnosis) ?? 'unchanged', { transport }); }, [extractReferenceId, changeEncounter, createReferenceObject]); const createCondition = useCallback(async (condition: Condition, transport?: SaveTransport): Promise => { let params = new URLSearchParams(); params.append('subject', extractReferenceId(condition.subject) as string); const created = await post(`${API_URL}/Condition?${params.toString()}`, condition, { ...transport }); // AT RECORD TIME, NEVER AS A BACKFILL. A join the record only acquires later is one nobody can // rely on while the visit is open, which is exactly when the lens is read. // // The diagnosis is the clinical fact and it is already stored; the link is derived from it. So a // failure here must not lose the diagnosis - but it must not be swallowed either, because a // silent one reproduces the defect this exists to fix. It is raised as its own error carrying // the condition, so a caller can say what really happened instead of "failed to create". try { transport?.admit(); await linkDiagnosisToItsEncounter(created, transport); } catch (cause) { throw new DiagnosisVisitLinkError(created, cause); } return created; }, [post, extractReferenceId, linkDiagnosisToItsEncounter]); // Native routes validate authorship and the stated source against the edition the editor names. const createOwnCondition = useCallback(async (patientId: string, entry: Condition, source: ConditionSource, transport?: SaveTransport): Promise => { const created = await post(`${environment.orgAuthorizationApi}/native-condition?subject=${encodeURIComponent(`Patient/${patientId}`)}`, { entry, source }, { ...transport }); try { transport?.admit(); await linkDiagnosisToItsEncounter(created, transport); } catch (cause) { throw new DiagnosisVisitLinkError(created, cause); } return created; }, [post, linkDiagnosisToItsEncounter]); // A patient's own report of a condition goes to the self-report route as its code and status only; the server names // the concept from its terminology and records where the patient said it came from. const recordOwnConditionReport = useCallback((patientId: string, report: Extract, transport?: SaveTransport) => post(`${environment.orgAuthorizationApi}/self-report?subject=${encodeURIComponent(`Patient/${patientId}`)}`, report, { ...transport }), [post]); const editOwnCondition = useCallback((id: string, expectedVersion: string, entry: Condition, source: ConditionSource, transport?: SaveTransport) => { if (!expectedVersion) return Promise.reject(new RecordChangedError(PAGE_OUT_OF_DATE_MESSAGE, true)); return put(`${environment.orgAuthorizationApi}/Condition/${encodeURIComponent(id)}/native-edit`, { expectedVersion, entry, source }, { ...transport }) .catch((error: unknown) => { throw asRecordChanged(error); }); }, [put]); const classifyCondition = useCallback((id: string, expectedVersion: string, contentClass: ContentClass, assertionId: string, transport?: SaveTransport) => put(`${environment.orgAuthorizationApi}/Condition/${encodeURIComponent(id)}/content-assertion`, { expectedVersion, class: contentClass, assertionId }, { ...transport }), [put]); const updateCondition = useCallback(async (condition: Condition, basedOn: string | undefined, transport?: SaveTransport): Promise => { if (!basedOn) throw new RecordChangedError(PAGE_OUT_OF_DATE_MESSAGE, true); return put(`${API_URL}/Condition/${condition.id}`, condition, { headers: ifMatch(basedOn), ...transport }) .catch((error: unknown) => { throw asRecordChanged(error); }); }, [put]); const deleteCondition = useCallback(async (condition: Condition): Promise => { return del(`${API_URL}/Condition/${condition.id}`); }, [del]); const archiveCondition = useCallback( async (condition: Condition, reason: ArchiveReason, note?: string, transport?: SaveTransport): Promise => // Built on the record it is given, so written only over that record's version. updateCondition(applyArchive(condition, reason, note), condition.meta?.versionId, transport), [updateCondition], ); const transformToFhirCondition = useCallback((item: any, patient?: Patient, encounterState?: Encounter, recorder?: Practitioner | Patient | RelatedPerson): Condition => { const { clinicalStatus, verificationStatus, category, severity, code, bodySite, encounter, onsetType, onsetDateTime, onsetAge, onsetString, onsetRange, abatementType, abatementDateTime, abatementAge, abatementString, abatementRange, recordedDate, asserter, note, meta, conditionProfile, evidence, stage } = item; const transformedEvidence: ConditionEvidence[] = (evidence ?? []).map((e: any) => { const evidenceItem: ConditionEvidence = { ...(e.code && { code: transformToCodeableConcept(e.code) }) }; if (Object.keys(evidenceItem).length !== 0) { return evidenceItem } return null; }).filter(Boolean); const transformedStage: ConditionStage[] = (stage ?? []).map((s: any) => { const transformedStageAssessment: Reference[] = transformToReferenceCollection(s.assessment); const stageItem: ConditionStage = { ...(transformedStageAssessment && transformedStageAssessment.length !== 0 && { assessment: transformedStageAssessment }), ...(s.type && { type: transformToCodeableConcept(s.type) }), ...(s.summary && { summary: transformToCodeableConcept(s.summary) }), }; if (Object.keys(stageItem).length !== 0) { return stageItem } return null; }).filter(Boolean); let condition: Condition = { resourceType: 'Condition', ...((meta || conditionProfile) && { meta: { ...meta, ...(conditionProfile && { profile: [conditionProfile] }) } }), ...(patient && { subject: createReferenceObject('Patient', patient.id as string, formatFhirHumanName(patient.name)) }), ...(recorder && { recorder: createReferenceObject(recorder.resourceType, recorder.id as string, formatFhirHumanName(recorder.name)) }), ...(asserter && { asserter }), ...(encounterState && { encounter: createReferenceObject('Encounter', encounterState?.id as string, getEncounterDisplay(encounterState)) }), ...(encounter && { encounter: encounter }), ...(clinicalStatus && { clinicalStatus: transformToCodeableConcept(clinicalStatus) }), ...(verificationStatus && { verificationStatus: transformToCodeableConcept(verificationStatus) }), ...(category && category.length !== 0 && { category: transformToCodeableConcept(category) as CodeableConcept[] }), ...(severity && { severity: transformToCodeableConcept(severity) }), ...(code && { code: transformToCodeableConcept(code) as CodeableConcept }), ...(bodySite && bodySite.length !== 0 && { bodySite: transformToCodeableConcept(bodySite) as CodeableConcept[] }), ...(onsetType === 'dateTime' && onsetDateTime && { onsetDateTime: formatDateTime(onsetDateTime) }), ...(onsetType === 'age' && onsetAge && { onsetAge: transformToFhirAge(onsetAge) }), ...(onsetType === 'string' && onsetString && { onsetString: onsetString }), ...(onsetType === 'range' && onsetRange && { onsetRange: onsetRange }), ...(abatementType === 'dateTime' && abatementDateTime && { abatementDateTime: formatDateTime(abatementDateTime) }), ...(abatementType === 'age' && abatementAge && { abatementAge: transformToFhirAge(abatementAge) }), ...(abatementType === 'string' && abatementString && { abatementString: abatementString }), ...(abatementType === 'range' && abatementRange && { abatementRange: abatementRange }), ...(recordedDate && { recordedDate: formatDateTime(recordedDate) }), ...(note && note.length !== 0 && { note: note }), ...(transformedEvidence && transformedEvidence.length !== 0 && { evidence: transformedEvidence }), ...(transformedStage && transformedStage.length !== 0 && { stage: transformedStage }) }; if (onsetType === 'period') { condition = addFhirPeriodIfApplicable(condition, item, 'onsetPeriod'); } if (abatementType === 'period') { condition = addFhirPeriodIfApplicable(condition, item, 'abatementPeriod'); } return condition; }, [ createReferenceObject, transformToCodeableConcept, transformToFhirAge, addFhirPeriodIfApplicable, formatDateTime, getEncounterDisplay ]); const transformFromFhirCondition = useCallback((condition: Condition): any => { const { clinicalStatus, verificationStatus, category, severity, code, bodySite, subject, encounter, onsetDateTime, onsetAge, onsetPeriod, onsetRange, onsetString, abatementDateTime, abatementAge, abatementPeriod, abatementRange, abatementString, recordedDate, recorder, asserter, note, meta, evidence, stage } = condition; let onsetType = ''; if (onsetDateTime) onsetType = 'dateTime'; else if (onsetAge) onsetType = 'age'; else if (onsetPeriod) onsetType = 'period'; else if (onsetRange) onsetType = 'range'; else if (onsetString) onsetType = 'string'; let abatementType = ''; if (abatementDateTime) abatementType = 'dateTime'; else if (abatementAge) abatementType = 'age'; else if (abatementPeriod) abatementType = 'period'; else if (abatementRange) abatementType = 'range'; else if (abatementString) abatementType = 'string'; const formData = { meta: meta, conditionProfileSlug: meta?.profile?.find(profile => profile.startsWith(usCoreConditionProfilePrefix + '/'))?.replace(usCoreConditionProfilePrefix + '/', '') || '', clinicalStatus: transformFromCodeableConcept(clinicalStatus), verificationStatus: transformFromCodeableConcept(verificationStatus), category: transformFromCodeableConcept(category ?? []), severity: transformFromCodeableConcept(severity), code: transformFromCodeableConcept(code), bodySite: transformFromCodeableConcept(bodySite ?? []), subject: subject, encounter: encounter, onsetType: onsetType, onsetDateTime: formatDateTimeForInput(onsetDateTime), onsetAge: transformFromFhirAge(onsetAge), onsetString: onsetString, onsetRange: onsetRange, abatementType: abatementType, abatementDateTime: formatDateTimeForInput(abatementDateTime), abatementAge: abatementAge, abatementString: abatementString, abatementRange: abatementRange, recordedDate: formatDateTimeForInput(recordedDate), recorder: recorder, asserter: asserter, note: note ?? [], stage: (stage ?? []).map((s: ConditionStage) => ({ type: transformFromCodeableConcept(s.type), summary: transformFromCodeableConcept(s.summary), assessment: transformFromReferenceCollection(s.assessment ?? []), })), evidence: (evidence ?? []).map((e: ConditionEvidence) => ({ code: transformFromCodeableConcept(e.code) })), }; if (onsetPeriod) { addPeriodControlsIfApplicable(formData, condition, 'onsetPeriod'); } if (abatementPeriod) { addPeriodControlsIfApplicable(formData, condition, 'abatementPeriod'); } return formData; }, [transformFromCodeableConcept, transformFromFhirAge, addPeriodControlsIfApplicable, formatDateTimeForInput]); const getConditionDisplay = (condition: Condition): string => { if (!condition) { return 'Unknown Condition'; } // - the status was rendered `Asthma [Remission]`, the clinical fact wearing machine // punctuation. `codeableStatusLabel` prefers the shared vocabulary and falls back to the // terminology server's own display, so `remission` - which this product has no word for - still // reaches the reader instead of vanishing. // A CONDITION WITH A CODING BUT NO `text` USED TO READ "Unknown Condition". // // `formatFhirCodeableConcept` returns only `.text` - it never looks at `coding[0].display` - so a // coded diagnosis carrying its name in the coding fell through the `||` and was announced to a // clinician as unnamed. The record was intact the whole time; only the reading was blank. // // `getCodeableConceptDisplay` is the missing half and already exists beside it in the same // module. Chained rather than folded into the shared formatter, which ~40 surfaces read: the // wider question of whether it should fall back to a coding is worth asking once, deliberately, // and not as a side effect of naming a diagnosis. const conditionName = formatFhirCodeableConcept(condition.code) || getCodeableConceptDisplay(condition.code) || 'Unknown Condition'; const clinicalStatus = codeableStatusLabel(condition.clinicalStatus); const statusSuffix = clinicalStatus ? ` · ${clinicalStatus}` : ''; const severity = condition.severity?.coding?.[0]?.display; const severitySuffix = severity ? ` · Severity: ${severity}` : ''; const onsetDate = condition.onsetDateTime ? formatDateTimeForDisplay(condition.onsetDateTime) : null; const dateSuffix = onsetDate ? ` (${onsetDate})` : ''; // `confirmed` stays suppressed on purpose: a confirmed diagnosis is the unremarkable case, and // labelling every row with it is how `Refuted` and `Provisional` stop being noticed. const verificationStatus = condition.verificationStatus?.coding?.[0]?.code; const verificationLabel = verificationStatus && verificationStatus !== 'confirmed' ? codeableStatusLabel(condition.verificationStatus) : undefined; const verificationSuffix = verificationLabel ? ` · ${verificationLabel}` : ''; return `${conditionName}${statusSuffix}${severitySuffix}${verificationSuffix}${dateSuffix}`; }; const getConditionStatusInfo = (condition: Condition): { clinicalStatusCode: string, clinicalStatus: string, verificationStatus: string, statusText: string, isActive: boolean, isResolved: boolean } => { if (!condition) { return { clinicalStatusCode: '', clinicalStatus: '', verificationStatus: '', statusText: '', isActive: false, isResolved: false }; } const clinicalStatusCode = condition.clinicalStatus?.coding?.[0]?.code ?? ''; const clinicalStatus = condition.clinicalStatus?.coding?.[0]?.display ?? ''; const verificationStatus = condition.verificationStatus?.coding?.[0]?.display ?? ''; const statusText = `${clinicalStatus} (${verificationStatus})`; const isActive = clinicalStatusCode === 'active'; const isResolved = clinicalStatusCode === 'resolved' || clinicalStatusCode === 'remission'; return { clinicalStatusCode, clinicalStatus, verificationStatus, statusText, isActive, isResolved }; }; const getConditionDateInfo = (condition: Condition): string => { if (!condition) return ''; if (condition.onsetDateTime) { return `Onset: ${formatDate(condition.onsetDateTime)}`; } else if (condition.recordedDate) { return `Recorded: ${formatDate(condition.recordedDate)}`; } return ''; }; return { conditionsBundle, conditionsList, // WHO THE CACHED LIST IS FOR. Exposed so a consumer can ask the question the encounter panel // asks at render time - is this list the patient I am looking at? - rather than trusting an // app-wide context that has no patient in its name. cachedForPatientId: cacheOwner.ownerId, listRead, setConditionsList, shouldReload, setShouldReload, lastUpserted, setLastUpserted, unsaved, setUnsaved, fetchConditions, fetchConditionsWithPolling, fetchCondition, fetchConditionHistory, createCondition, linkDiagnosisToItsEncounter, updateCondition, deleteCondition, archiveCondition, createOwnCondition, recordOwnConditionReport, editOwnCondition, classifyCondition, transformToFhirCondition, transformFromFhirCondition, getConditionDisplay, getConditionStatusInfo, getConditionDateInfo, // US Core Condition profiles usCoreConditionEncounterDiagnosisProfile, usCoreConditionProblemsHealthConcernsProfile }; }; const ConditionServiceContext = createContext | undefined>(undefined); export const ConditionServiceProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const conditionService = useConditionService(); return ( {children} ); }; /** Absent outside its provider, for readers that must never take a surface down with them. */ export const useOptionalConditionServiceContext = () => useContext(ConditionServiceContext); export const useConditionServiceContext = () => { const context = useContext(ConditionServiceContext); if (context === undefined) { throw new Error('useConditionServiceContext must be used within a ConditionServiceProvider'); } return context; };