Skip to content

Commit

Permalink
Merge from main repo: Release Version 2.1.2 (#2943)
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Sep 28, 2023
1 parent c57df02 commit 2b305a4
Show file tree
Hide file tree
Showing 21 changed files with 2,067 additions and 262 deletions.
4 changes: 4 additions & 0 deletions .turbo/turbo-build.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

> [email protected] build
> tsc

26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# Medplum Demo Bots

This repo contains code for [Medplum Bots](https://docs.medplum.com/app/bots). Bots power many of the integrations you see in Medplum apps. You can view your deployed bots online on the [Medplum App](https://app.medplum.com).
This repo contains code for [Medplum Bots](https://docs.medplum.com/app/bots). Bots power many of the integrations you see in Medplum apps. You can view your deployed bots online on the [Medplum App](https://app.medplum.com).

Bots make heavy use of the [Medplum JS Client Library](https://docs.medplum.com/typedoc/core/index.html).

## Setup

To set up your bot deployment you will need to do the following:

* [Create a Bot](https://app.medplum.com/admin/project) on Medplum and note its `id`. (All Bots in your account can be found [here](https://app.medplum.com/Bot))
* Create a new typescript file (e.g. `my-bot.ts`) and copy the contents of `examples/hello-patient.ts` into your new file.
* With the `id` of the Bot `id` in hand, add a section to `medplum.config.json` like so
- [Create a Bot](https://app.medplum.com/admin/project) on Medplum and note its `id`. (All Bots in your account can be found [here](https://app.medplum.com/Bot))
- Create a new typescript file (e.g. `my-bot.ts`) and copy the contents of `examples/hello-patient.ts` into your new file.
- With the `id` of the Bot `id` in hand, add a section to `medplum.config.json` like so

```json
{
"name": "sample-account-setup",
"id": "aa3a0383-a97b-4172-b65d-430f6241646f",
"source": "src/examples/sample-account-setup.ts",
"dist": "dist/sample-account-setup.js"
}
{
"name": "sample-account-setup",
"id": "aa3a0383-a97b-4172-b65d-430f6241646f",
"source": "src/examples/sample-account-setup.ts",
"dist": "dist/sample-account-setup.js"
}
```

* [Create an ClientApplication](https://app.medplum.com/ClientApplication/new) on Medplum. (All ClientApplications in your account can be found [here](https://app.medplum.com/ClientApplication))
* Create a .env file locally by copying `.env.example` and put the `ClientId` and `ClientSecret` from the `ClientApplication` into the file.
* (Optional) Create an [AccessPolicy]((https://app.medplum.com/AccessPolicy)) on Medplum that can only read/write Bots and add it to the Bot in the [admin panel](https://app.medplum.com/admin/project).
- [Create an ClientApplication](https://app.medplum.com/ClientApplication/new) on Medplum. (All ClientApplications in your account can be found [here](https://app.medplum.com/ClientApplication))
- Create a .env file locally by copying `.env.example` and put the `ClientId` and `ClientSecret` from the `ClientApplication` into the file.
- (Optional) Create an [AccessPolicy](<(https://app.medplum.com/AccessPolicy)>) on Medplum that can only read/write Bots and add it to the Bot in the [admin panel](https://app.medplum.com/admin/project).

## Installation

Expand Down
24 changes: 24 additions & 0 deletions medplum.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,30 @@
"id": "",
"source": "src/stripe-bots/stripe-create-invoice.ts",
"dist": "dist/stripe-bots/stripe-create-invoice.js"
},
{
"name": "merge-bot",
"id": "",
"source": "src/deduplication/merge-bot.ts",
"dist": "dist/deduplication/merge-bot.js"
},
{
"name": "patient-deduplication",
"id": "",
"source": "src/deduplication/patient-deduplication.ts",
"dist": "dist/deduplication/patient-deduplication.js"
},
{
"name": "find-matching-patients",
"id": "",
"source": "src/deduplication/find-matching-patients.ts",
"dist": "dist/deduplication/find-matching-patients.js"
},
{
"name": "merge-matching-patients",
"id": "",
"source": "src/deduplication/merge-matching-patients.ts",
"dist": "dist/deduplication/merge-matching-patients.js"
}
]
}
34 changes: 17 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"type": "module",
"name": "medplum-demo-bots",
"version": "2.0.29",
"version": "2.1.2",
"description": "Medplum Demo Bots",
"scripts": {
"build": "tsc",
Expand All @@ -14,23 +13,23 @@
"author": "Medplum <[email protected]>",
"license": "Apache-2.0",
"devDependencies": {
"@medplum/cli": "2.0.29",
"@medplum/core": "2.0.29",
"@medplum/eslint-config": "2.0.29",
"@medplum/fhirtypes": "2.0.29",
"@medplum/mock": "2.0.29",
"@types/node": "20.4.5",
"@types/node-fetch": "2.6.4",
"@medplum/cli": "2.1.2",
"@medplum/core": "2.1.2",
"@medplum/eslint-config": "2.1.2",
"@medplum/fhirtypes": "2.1.2",
"@medplum/mock": "2.1.2",
"@types/node": "20.6.2",
"@types/node-fetch": "2.6.5",
"@types/ssh2-sftp-client": "9.0.0",
"@vitest/coverage-v8": "0.33.0",
"@vitest/ui": "0.33.0",
"@vitest/coverage-v8": "0.34.4",
"@vitest/ui": "0.34.4",
"form-data": "4.0.0",
"node-fetch": "2.6.12",
"node-fetch": "2.7.0",
"pdfmake": "0.2.7",
"ssh2-sftp-client": "9.1.0",
"stripe": "12.14.0",
"typescript": "5.1.6",
"vitest": "0.33.0"
"stripe": "13.6.0",
"typescript": "5.2.2",
"vitest": "0.34.4"
},
"eslintConfig": {
"extends": [
Expand All @@ -39,9 +38,10 @@
},
"prettier": {
"printWidth": 120,
"singleQuote": true
"singleQuote": true,
"trailingComma": "es5"
},
"dependencies": {
"esbuild": "0.18.17"
"esbuild": "0.19.3"
}
}
95 changes: 95 additions & 0 deletions src/deduplication/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Patient Deduplication Demo

This folder contains a reference implementation for a simple, human-in-the-loop patient deduplication pipeline, based on the guidance in [Patient Deduplication Architectures](https://www.medplum.com/docs/fhir-datastore/patient-deduplication#architecture-overview).

The implementation consists of two bots:

- [`find-matching-patients`](./find-matching-patients.ts) - Identifies duplicated Patients and performs a series of steps:
- Check for the first name, last name, birth date, and zip code.
- If it's a match, it will check a list to see if it's not on the doNotMatch list
- Creates a `RiskAssessment`, with a numeric score, representing the candidate match.
- Creates a `Task` for a human to review the match
- [`merge-matching-patients`](./merge-matching-patients.ts) -The other that merges the two Patients into one
- [Link the Patient records](https://www.medplum.com/docs/fhir-datastore/patient-deduplication#linking-patient-records-in-fhir)
- [Merge the Contact info](https://www.medplum.com/docs/fhir-datastore/patient-deduplication#merge-rules)
- [Rewrite Clinical Resource](https://www.medplum.com/docs/fhir-datastore/patient-deduplication#rewriting-references-from-clinical-data)
- Delete the source record, if the user has requested it

This implementation also consists of FHIR bundles with sample data, which can be uploaded using the [batch upload tool](https://www.medplum.com/docs/tutorials/importing-sample-data):

- [patient-data.json](./patient-data.json) - Three sample `Patient` resources, two of whom are known duplicates, and one of which is a false positive
- [merge-questionnaire.json](./merge-questionnaire.json) - An example `Questionnaire` resource that describes a form that a reviewer might use to evaluate a candidate match.

## Setup

To run and deploy your Bot do the following steps:

Install:

```bash
npm i
```

Build:

```bash
npm run build
```

Test:

```bash
npm t
```

[Create first Bot](https://www.medplum.com/docs/cli#bots) :

```bash
npx medplum bot create find-matching-patients <project id> "src/deduplication/find-matching-patients.ts" "dist/deduplication/find-matching-patients.js"
```

[Deploy first Bot](https://www.medplum.com/docs/cli#bots) :

```bash
npx medplum bot deploy find-matching-patients
```

```bash
Update bot code.....
Success! New bot version: <botID>
Deploying bot...
Deploy result: All OK
```

Set up a `Subscription` following the instructions [here](https://www.medplum.com/docs/bots/bot-basics#executing-automatically-using-a-subscription) to trigger the Bot when a `Patient` record is updated.

- Critera: `Patient?active=true`
- Endpoint: `Bot/:find-matching-patients-bot-id`

[Create second Bot](https://www.medplum.com/docs/cli#bots) :

```bash
npx medplum bot create merge-matching-patients <project id> "src/deduplication/merge-matching-patients.ts" "dist/deduplication/merge-matching-patients.js"
```

[Deploy second Bot](https://www.medplum.com/docs/cli#bots) :

```bash
npx medplum bot deploy merge-matching-patients
```

You will see the following in your command prompt if all goes well:

```bash
Update bot code.....
Success! New bot version: <botID>
Deploying bot...
Deploy result: All OK
```

Use the [batch upload tool](https://www.medplum.com/docs/tutorials/importing-sample-data) to import the [merge questionnaire](./merge-questionnaire.json)

Set up a `Subscription` following the instructions [here](https://www.medplum.com/docs/bots/bot-basics#executing-automatically-using-a-subscription) to trigger the bot when the questionnaire is submitted.

- Critera: `QuestionnaireResponse?questionnaire=Questionnaire/:merge-questionnaire-id`
- Endpoint: `Bot/:merge-matching-patients-bot-id`
72 changes: 72 additions & 0 deletions src/deduplication/find-matching-patients.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
ContentType,
MedplumClient,
createReference,
getReferenceString,
indexSearchParameterBundle,
indexStructureDefinitionBundle,
resolveId,
} from '@medplum/core';
import { readJson } from '@medplum/definitions';
import { Bundle, List, Patient, SearchParameter } from '@medplum/fhirtypes';
import { MockClient } from '@medplum/mock';
import { handler } from './find-matching-patients';
import patientData from './patient-data.json';

interface TestContext {
medplum: MedplumClient;
}

// npm t src/examples/patient-deduplication.test.ts
// This test demostrates a automatically linking patients with three matching identifiers
describe('Link Patient', async () => {
// Load the FHIR definitions to enable search parameter indexing
beforeAll(() => {
indexStructureDefinitionBundle(readJson('fhir/r4/profiles-types.json') as Bundle);
indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle);
indexSearchParameterBundle(readJson('fhir/r4/search-parameters.json') as Bundle<SearchParameter>);
});

// Load the sample data from patient-data.json
beforeEach<TestContext>(async (context) => {
context.medplum = new MockClient();
await context.medplum.executeBatch(patientData as Bundle);
});

test<TestContext>('Created RiskAssessment', async ({ medplum }) => {
// Read the patient
const patients = await medplum.searchResources('Patient', { given: 'Alex' });

await handler(medplum, { input: patients?.[0] as Patient, contentType: ContentType.FHIR_JSON, secrets: {} });

// We expect two risk assessments to be created for the two candidate matches
const riskAssessments = await medplum.searchResources('RiskAssessment');
expect(riskAssessments.length).toBe(2);
expect(riskAssessments.every((assessment) => resolveId(assessment.subject) === patients[0].id));
});

test<TestContext>('Does not create RiskAssessment due to doNotMatch List', async ({ medplum }) => {
// Read two patients that should not be matched
const alexSmith = await medplum.searchOne('Patient', { given: 'Alex', gender: 'male' });
const alexisSmith = await medplum.searchOne('Patient', { given: 'Alex', gender: 'female' });

if (!alexSmith || !alexisSmith) {
throw new Error('Missing Input Patient');
}

// Add each patient to the other's
const doNotMatchAlex = (await medplum.searchOne('List', { subject: getReferenceString(alexSmith) })) as List;
const doNotMatchAlexis = (await medplum.searchOne('List', { subject: getReferenceString(alexisSmith) })) as List;
doNotMatchAlex.entry = [{ item: createReference(alexisSmith) }];
doNotMatchAlexis.entry = [{ item: createReference(alexSmith) }];
await medplum.updateResource(doNotMatchAlex);
await medplum.updateResource(doNotMatchAlexis);

await handler(medplum, { input: alexSmith, contentType: ContentType.FHIR_JSON, secrets: {} });

const riskAssessment = await medplum.searchResources('RiskAssessment');
expect(riskAssessment.length).toBe(1);
expect(riskAssessment?.[0]?.subject?.reference).toBe(getReferenceString(alexSmith));
expect(riskAssessment?.[0]?.basis?.[0]).not.toBe(getReferenceString(alexisSmith));
});
});
Loading

0 comments on commit 2b305a4

Please sign in to comment.