diff --git a/src/ui/components/PatientViewer/FilterPresets.jsx b/src/ui/components/PatientViewer/FilterPresets.jsx new file mode 100644 index 0000000..620fe52 --- /dev/null +++ b/src/ui/components/PatientViewer/FilterPresets.jsx @@ -0,0 +1,78 @@ + +// map a list to a group of fhir ORs, eg [1,2,3] --> string "'1' | '2' | '3'" +const fhirList = (list) => list.map(i => `'${i}'`).join(' | '); + + +const DR_CONDITIONS = [ + '44054006', // diabetes + '15777000', // prediabetes + '1551000119108', // NPDR + '1501000119109', // PDR + '97331000119101', // DME + '60951000119105', // blindness +]; + +const SDOH_CONDITIONS = [ + '314529007', + '5251000175109', + '424393004', + '160903007', + '706893006', + '73595000', + '160904001', + '741062008', + '10939881000119105', + '422650009', + '423315002', + '706893006', + '266948004', + '5251000175109' +]; + +const FILTER_PRESETS = Object.freeze({ + "Hide Resolved Conditions": { + description: "Hides conditions with an abatement date", + mode: 'exclude', + filterOnGroupByEncounter: false, + filters: { + Condition: ['Condition.abatement'] // is not null + } + }, + "Hide Stopped Medications": { + description: "Hides medications with a status of 'stopped'", + mode: 'exclude', + filterOnGroupByEncounter: false, + filters: { + MedicationRequest: ["MedicationRequest.status = 'stopped'"] + } + }, + 'Disease Focus: Diabetic Retinopathy': { + description: 'Keeps entries relevant to Diabetic Retinopathy', + mode: 'include', + filterOnGroupByEncounter: true, + filters: { + Encounter: ["Encounter.type.coding.code = '185349003'"], + Condition: [`Condition.code.coding.code in (${fhirList(DR_CONDITIONS)})`], + Observation: [] + } + }, + 'Hide SDOH Conditions': { + description: 'Hides most SDOH conditions, such as "stress", employment status, etc', + mode: 'exclude', + filterOnGroupByEncounter: true, + filters: { + Condition: [`Condition.code.coding.code in (${fhirList(SDOH_CONDITIONS)})`] + } + } + // Coming soon... + // 'Cardiology': { + // description: 'Keeps entries relevant to Cardiology', + // mode: 'include', + // filters: { + + // } + // } + +}); + +export default FILTER_PRESETS; \ No newline at end of file diff --git a/src/ui/components/PatientViewer/PatientViewer.jsx b/src/ui/components/PatientViewer/PatientViewer.jsx index 435afaf..4b27468 100644 --- a/src/ui/components/PatientViewer/PatientViewer.jsx +++ b/src/ui/components/PatientViewer/PatientViewer.jsx @@ -48,6 +48,9 @@ import csvToFhir from './csvToFhir'; import { evaluateResource, appliesToResource } from '../../fhirpath_utils'; +import FILTER_PRESETS from './FilterPresets'; + + const getDropzone = (setLoading, callback) => { const onDrop = files => { const reader = new FileReader(); @@ -116,15 +119,16 @@ const PatientViewer = props => { setIsLoading(false); } - - const [isGroupByEncounter, setIsGroupByEncounter] = useLocalStorage("group-by-encounter", false); - const [filters, setFilters] = useLocalStorage("filters", {}); - const [filterMode, setFilterMode] = useLocalStorage("filter-mode", "include"); - // TODO: probably a better way to make these generic. don't want to pass around 100 variables for different settings - const [hideStoppedMeds, setHideStoppedMeds] = useLocalStorage("Hide Stopped Medications", false); - const [hideResolvedConditions, setHideResolvedConditions] = useLocalStorage("Hide Resolved Conditions", false); + const loadedPresets = []; + for (const presetKey of Object.keys(FILTER_PRESETS)) { + const [isPresetLoaded,] = useLocalStorage(presetKey, false); + + if (isPresetLoaded) { + loadedPresets.push(presetKey); + } + } useEffect(() => { if (id && !bundle) { @@ -152,14 +156,17 @@ const PatientViewer = props => { let allResources = bundle.entry.map(e => e.resource); - if (Object.keys(filters).length) { + for (const presetKey of loadedPresets) { + const preset = FILTER_PRESETS[presetKey]; + if (isGroupByEncounter && !preset.filterOnGroupByEncounter) continue; allResources = allResources.filter(r => { - const filtersByResourceType = filters[r.resourceType]; + const filtersByResourceType = preset.filters[r.resourceType]; if (!filtersByResourceType) return true; const anyMatch = filtersByResourceType.some(f => appliesToResource(r, f)); - return filterMode === 'exclude' ? !anyMatch : anyMatch; + return preset.mode === 'exclude' ? !anyMatch : anyMatch; }); + } const patient = allResources.find(r => r.resourceType === 'Patient'); @@ -186,9 +193,7 @@ const PatientViewer = props => { <> + allResources={allResources} /> )} @@ -229,18 +234,10 @@ const LinksByType = () => { }; const EntireRecord = props => { - const { allResources, hideStoppedMeds, hideResolvedConditions } = props; + const { allResources } = props; const getByType = type => allResources.filter(r => r.resourceType === type); - let conditions = getByType('Condition'); - if (hideResolvedConditions) { - conditions = conditions.filter(c => !c.abatementDateTime); - } - - let medications = getByType('MedicationRequest'); - if (hideStoppedMeds) { - medications = medications.filter(m => m.status !== 'stopped'); - } - + const conditions = getByType('Condition'); + const medications = getByType('MedicationRequest'); const meds = getByType('Medication'); medications.forEach(m => { if (m.medicationReference) { diff --git a/src/ui/components/PatientViewer/Settings.jsx b/src/ui/components/PatientViewer/Settings.jsx index 5d25336..0574ff5 100644 --- a/src/ui/components/PatientViewer/Settings.jsx +++ b/src/ui/components/PatientViewer/Settings.jsx @@ -15,6 +15,7 @@ import TextField from '@mui/material/TextField'; import InfoIcon from '@mui/icons-material/Info'; +import FILTER_PRESETS from './FilterPresets'; const style = { position: 'absolute', @@ -31,10 +32,11 @@ const style = { const CONFIG_OPTIONS = [ // { key: "filters", defaultValue: "{}", // description: "TBD" }, - { key: "Hide Resolved Conditions", defaultValue: false, type: "boolean", - description: "If true, conditions with an abatement date will be hidden" }, - { key: "Hide Stopped Medications", defaultValue: false, type: "boolean", - description: "If true, medications with a status of 'stopped' will be hidden" }, + // { key: "Hide Resolved Conditions", defaultValue: false, type: "boolean", + // description: "If true, conditions with an abatement date will be hidden" }, + // { key: "Hide Stopped Medications", defaultValue: false, type: "boolean", + // description: "If true, medications with a status of 'stopped' will be hidden" }, + ...Object.entries(FILTER_PRESETS).map(([key, value]) => ({ key, description: value.description, type: "boolean" })) ]; const Settings = () => { @@ -61,7 +63,11 @@ const Settings = () => { for (const configOpt of CONFIG_OPTIONS) { const key = configOpt.key; - if (configOpt.type == 'boolean') { + if (configOpt.type == 'separator') { + fields.push((
)); + } else if (configOpt.type == 'header') { + fields.push(( { configOpt.key })); + } else if (configOpt.type == 'boolean') { fields.push(( {key}