Skip to content

Commit

Permalink
Merge pull request #123 from Rugute/main
Browse files Browse the repository at this point in the history
Sorted Patient Summaries
  • Loading branch information
Rugute authored Jul 24, 2024
2 parents ee18a11 + 2b0407e commit b68541a
Show file tree
Hide file tree
Showing 24 changed files with 591 additions and 67 deletions.
Binary file modified .DS_Store
Binary file not shown.
4 changes: 2 additions & 2 deletions packages/esm-care-panel-app/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
![Node.js CI](https://github.com/palladiumkenya/ampath-esm-3.x/workflows/Node.js%20CI/badge.svg)
![Node.js CI](https://github.com/AMPATH/ampath-esm-3.x/workflows/Node.js%20CI/badge.svg)

# ESM Care Panel App

This repository provides a starting point for creating ampath patient care panels
This repository provides a starting point for creating AMPATH patient care panels
6 changes: 3 additions & 3 deletions packages/esm-care-panel-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"main": "src/index.ts",
"source": true,
"license": "MPL-2.0",
"homepage": "https://github.com/palladiumkenya/ampath-esm-core#readme",
"homepage": "https://github.com/AMPATH/ampath-esm-core#readme",
"scripts": {
"start": "openmrs develop",
"serve": "webpack serve --mode=development",
Expand All @@ -31,10 +31,10 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/palladiumkenya/ampath-esm-core#readme"
"url": "git+https://github.com/AMPATH/ampath-esm-core#readme"
},
"bugs": {
"url": "https://github.com/palladiumkenya/ampath-esm-core/issues"
"url": "https://github.com/AMPATH/ampath-esm-core/issues"
},
"dependencies": {
"@carbon/react": "^1.42.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Layer, Tab, TabList, TabPanel, TabPanels, Tabs, Tile } from '@carbon/react';
import { Analytics, CloudMonitoring, Dashboard } from '@carbon/react/icons';
import React from 'react';
import { Tabs, TabList, Tab, TabPanel, TabPanels, Layer, Tile } from '@carbon/react';
import { Dashboard, CloudMonitoring, Printer } from '@carbon/react/icons';
import { useTranslation } from 'react-i18next';
import CarePanel from '../care-panel/care-panel.component';
import CarePrograms from '../care-programs/care-programs.component';
import PatientSummary from '../patient-summary/patient-summary.component';

import CarePanelMachineLearning from '../machine-learning/machine-learning.component';
import styles from './care-panel-dashboard.scss';

type CarePanelDashboardProps = { patientUuid: string; formEntrySub: any; launchPatientWorkspace: Function };
Expand All @@ -28,6 +28,7 @@ const CarePanelDashboard: React.FC<CarePanelDashboardProps> = ({
<TabList contained activation="manual" aria-label="List of care panels">
<Tab renderIcon={Dashboard}>{t('panelSummary', 'Panel summary')}</Tab>
<Tab renderIcon={CloudMonitoring}>{t('enrollments', 'Program enrollment')}</Tab>
<Tab renderIcon={Analytics}>{t('machineLearning', 'Machine Learning')}</Tab>
</TabList>
<TabPanels>
<TabPanel>
Expand All @@ -40,6 +41,9 @@ const CarePanelDashboard: React.FC<CarePanelDashboardProps> = ({
<TabPanel>
<CarePrograms patientUuid={patientUuid} />
</TabPanel>
<TabPanel>
<CarePanelMachineLearning patientUuid={patientUuid} />
</TabPanel>
</TabPanels>
</Tabs>
</div>
Expand Down
6 changes: 6 additions & 0 deletions packages/esm-care-panel-app/src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface CarePanelConfig {
regimenObs: {
encounterProviderRoleUuid: string;
};
hivProgramUuid: string;
}

export const configSchema = {
Expand All @@ -14,4 +15,9 @@ export const configSchema = {
_description: "The provider role to use for the regimen encounter. Default is 'Unkown'.",
},
},
hivProgramUuid: {
_type: Type.String,
_description: 'HIV Program UUID',
_default: 'dfdc6d40-2f2f-463d-ba90-cc97350441a8',
},
};
18 changes: 18 additions & 0 deletions packages/esm-care-panel-app/src/hooks/usePatientHIVStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { openmrsFetch, useConfig } from '@openmrs/esm-framework';
import useSWR from 'swr';
import { type CarePanelConfig } from '../config-schema';
import { type Enrollment } from '../types';

const usePatientHIVStatus = (patientUuid: string) => {
const customeRepresentation = 'custom:(uuid,program:(name,uuid))';
const url = `/ws/rest/v1/programenrollment?v=${customeRepresentation}&patient=${patientUuid}`;
const config = useConfig<CarePanelConfig>();
const { data, error, isLoading } = useSWR<{ data: { results: Enrollment[] } }>(url, openmrsFetch);
return {
error,
isLoading,
isPositive: (data?.data?.results ?? []).find((en) => en.program.uuid === config.hivProgramUuid) !== undefined,
};
};

export default usePatientHIVStatus;
19 changes: 19 additions & 0 deletions packages/esm-care-panel-app/src/hooks/usePatientIITScore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import useSWR from 'swr';
import { type IITRiskScore } from '../types';
import { patientRiskScore } from '../iit-risk-score/risk-score.mock';
import { useMemo } from 'react';

const usePatientIITScore = (patientUuid: string) => {
const url = `${restBaseUrl}/keml/patientiitscore?patientUuid=${patientUuid}`;
const { data, error, isLoading } = useSWR<FetchResponse<IITRiskScore>>(url, openmrsFetch);

const riskScore = useMemo(() => data?.data ?? patientRiskScore.at(-1), [patientUuid]);
return {
isLoading: false,
error,
riskScore,
};
};

export default usePatientIITScore;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { LineChart, type LineChartOptions, ScaleTypes } from '@carbon/charts-react';
import '@carbon/charts/styles.css';
import React from 'react';
import styles from './iit-risk-score.scss';
import usePatientIITScore from '../hooks/usePatientIITScore';
import { CardHeader } from '@openmrs/esm-patient-common-lib';
import { patientRiskScore } from './risk-score.mock';
import { formatDate, parseDate } from '@openmrs/esm-framework';

interface CarePanelRiskScorePlotProps {
patientUuid: string;
}

const CarePanelRiskScorePlot: React.FC<CarePanelRiskScorePlotProps> = ({ patientUuid }) => {
const { isLoading, error, riskScore } = usePatientIITScore(patientUuid);
const options: LineChartOptions = {
title: 'KenyaHMIS ML Model',
legend: { enabled: false },
axes: {
bottom: {
title: 'Evaluation Time',
mapsTo: 'evaluationDate',
scaleType: ScaleTypes.LABELS,
},
left: {
mapsTo: 'riskScore',
title: 'Risk Score (%)',
percentage: true,
scaleType: ScaleTypes.LINEAR,
includeZero: true,
},
},
curve: 'curveMonotoneX',
height: '400px',
tooltip: {
// Tooltip configuration for displaying descriptions
enabled: true,

valueFormatter(value, label) {
if (label === 'Risk Score (%)') {
return `${value} (${patientRiskScore.find((r) => `${r.riskScore}` === `${value}`)?.description ?? ''})`;
}
return `${value}`;
},
},
};

return (
<div className={styles['risk-score-card']}>
<span className={styles.sectionHeader}>IIT Risk Score Trend</span>
<center>
<strong>Latest risk score: </strong>
{`${riskScore.riskScore}%`}
</center>
<div style={{ padding: '1rem' }}>
<LineChart
data={patientRiskScore.map((risk) => ({
...risk,
evaluationDate: formatDate(parseDate(risk.evaluationDate)),
}))}
options={options}
/>
</div>
</div>
);
};

export default CarePanelRiskScorePlot;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Column, Row, SkeletonText } from '@carbon/react';
import { ErrorState, formatDate, parseDate } from '@openmrs/esm-framework';
import React from 'react';
import { useTranslation } from 'react-i18next';
import usePatientIITScore from '../hooks/usePatientIITScore';
import styles from './iit-risk-score.scss';

interface CarePanellIITRiskScoreProps {
patientUuid: string;
}

const CarePanellIITRiskScore: React.FC<CarePanellIITRiskScoreProps> = ({ patientUuid }) => {
const { riskScore, error, isLoading } = usePatientIITScore(patientUuid);
const { t } = useTranslation();

if (isLoading) {
return (
<div className={styles['risk-score-card']}>
<Row style={{ display: 'flex' }}>
<Column lg={4} md={4} sm={4} className={styles['risk-score-card__item']}>
<SkeletonText />
<SkeletonText />
</Column>
<Column lg={4} md={4} sm={4} className={styles['risk-score-card__item']}>
<SkeletonText />
<SkeletonText />
</Column>
</Row>
<Row style={{ display: 'flex' }}>
<Column lg={4} md={4} sm={4} className={styles['risk-score-card__item']}>
<SkeletonText />
<SkeletonText />
</Column>
<Column lg={12} md={12} sm={12} className={styles['risk-score-card__item']}>
<SkeletonText />
<SkeletonText />
</Column>
</Row>
</div>
);
}

// if (error) {
// return <ErrorState error={error} headerTitle={t('iitRiscScore', 'IIT Risk Score')} />;
// }
return (
<div className={styles['risk-score-card']}>
<span className={styles.sectionHeader}>IIT Risk Score</span>
<Row style={{ display: 'flex' }}>
<Column lg={4} md={4} sm={4} className={styles['risk-score-card__item']}>
<strong>Risk Score:</strong>
<p>{`${riskScore?.riskScore ?? 0}%`}</p>
</Column>
<Column lg={4} md={4} sm={4} className={styles['risk-score-card__item']}>
<strong>Evaluation Date:</strong>
<p>{formatDate(parseDate(riskScore?.evaluationDate))}</p>
</Column>
</Row>
<Row style={{ display: 'flex' }}>
<Column lg={4} md={4} sm={4} className={styles['risk-score-card__item']}>
<strong>Description:</strong>
<p>{riskScore?.description}</p>
</Column>
<Column lg={12} md={12} sm={12} className={styles['risk-score-card__item']}>
<strong>Risk Factors:</strong>
<p>{riskScore?.riskFactors}</p>
</Column>
</Row>
</div>
);
};

export default CarePanellIITRiskScore;
39 changes: 39 additions & 0 deletions packages/esm-care-panel-app/src/iit-risk-score/iit-risk-score.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@use '@carbon/styles/scss/type';
@use '@carbon/styles/scss/spacing';
@use '@carbon/colors';
@import '~@openmrs/esm-styleguide/src/vars';

.risk-score-card {
width: 100%;
border: 0.15rem solid $grey-2;
margin-bottom: spacing.$spacing-08;
}

.risk-score-card__item {
flex: 1;
padding: spacing.$spacing-05;
}

.sectionHeader {
@include type.type-style('heading-02');
display: flex;
align-items: center;
justify-content: space-between;
margin: spacing.$spacing-05;
row-gap: 1.5rem;
position: relative;

&::after {
content: '';
display: block;
width: 2rem;
border-bottom: 0.375rem solid var(--brand-03);
position: absolute;
bottom: -0.75rem;
left: 0;
}

& > span {
@include type.type-style('body-01');
}
}
56 changes: 56 additions & 0 deletions packages/esm-care-panel-app/src/iit-risk-score/risk-score.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export const patientRiskScore = [
{
evaluationDate: '2023-01-12T00:00:00.000Z',
riskScore: 0,
description: 'Low Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
{
evaluationDate: '2023-04-10T00:00:00.000Z',
riskScore: 0,
description: 'Low Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
{
evaluationDate: '2023-07-06T00:00:00.000Z',
riskScore: 3,
description: 'Medium Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
{
evaluationDate: '2023-10-13T00:00:00.000Z',
riskScore: 3,
description: 'Medium Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
{
evaluationDate: '2023-11-23T00:00:00.000Z',
riskScore: 3,
description: 'Medium Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
{
evaluationDate: '2023-12-17T00:00:00.000Z',
riskScore: 3,
description: 'Medium Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
{
evaluationDate: '2024-01-17T00:00:00.000Z',
riskScore: 3,
description: 'Medium Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
{
evaluationDate: '2024-04-09T00:00:00.000Z',
riskScore: 11,
description: 'High Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
{
evaluationDate: '2024-07-07T00:00:00.000Z',
riskScore: 11,
description: 'High Risk',
riskFactors: 'Poor adherance, Missed appointments, Late drug pickup',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import styles from './machine-learning.scss';
import CarePanellIITRiskScore from '../iit-risk-score/iit-risk-score.component';
import CarePanelRiskScorePlot from '../iit-risk-score/iit-risk-score-plot';
import usePatientHIVStatus from '../hooks/usePatientHIVStatus';
import { ErrorState } from '@openmrs/esm-framework';
import { useTranslation } from 'react-i18next';
import { EmptyState } from '@openmrs/esm-patient-common-lib';

interface CarePanelMachineLearningProps {
patientUuid: string;
}

const CarePanelMachineLearning: React.FC<CarePanelMachineLearningProps> = ({ patientUuid }) => {
const { error, isLoading, isPositive } = usePatientHIVStatus(patientUuid);
const { t } = useTranslation();
const header = t('machineLearning', 'Machine Learning');
if (error) {
return <ErrorState error={error} headerTitle={header} />;
}
return (
<div className={styles['machine-learning']}>
{!isPositive && <EmptyState headerTitle={header} displayText="machine learning predictions" />}
{isPositive && <CarePanellIITRiskScore patientUuid={patientUuid} />}
{isPositive && <CarePanelRiskScorePlot patientUuid={patientUuid} />}
</div>
);
};

export default CarePanelMachineLearning;
Loading

0 comments on commit b68541a

Please sign in to comment.