Skip to content

Commit 40a5ac9

Browse files
committed
wip - fixed
1 parent e5b3e8d commit 40a5ac9

File tree

4 files changed

+213
-30
lines changed

4 files changed

+213
-30
lines changed

config/oystehr/zambdas.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,7 @@
12351235
"reason": "Refresh invoice task",
12361236
"event": "create"
12371237
}
1238+
},
12381239
"GET_PATIENT_BALANCES": {
12391240
"name": "get-patient-balances",
12401241
"type": "http_auth",

packages/zambdas/src/ehr/get-invoices-tasks/index.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,7 @@ function performEffect(taskGroups: TaskGroup[], total: number): GetInvoicesTasks
115115
}
116116
const visitDate = formatDateConfigurable({ isoDate: appointment?.start, timezone });
117117

118-
const relatedPerson = resources.find(
119-
(resource) =>
120-
resource.resourceType === 'RelatedPerson' &&
121-
(resource as RelatedPerson).relationship?.find(
122-
(relationship) => relationship.coding?.find((code) => code.code === 'user-relatedperson')
123-
)
124-
) as RelatedPerson;
125-
const patientPhoneNumber = relatedPerson && getPhoneNumberForIndividual(relatedPerson);
118+
const patientPhoneNumber = group.relatedPerson && getPhoneNumberForIndividual(group.relatedPerson);
126119

127120
reports.push({
128121
claimId: taskInput.claimId ?? '---',
@@ -136,7 +129,7 @@ function performEffect(taskGroups: TaskGroup[], total: number): GetInvoicesTasks
136129
fullName: patientName,
137130
dob: patientDob,
138131
gender: patientGenderLabel,
139-
phoneNumber: patientPhoneNumber,
132+
phoneNumber: patientPhoneNumber ?? '---',
140133
},
141134
responsibleParty: {
142135
fullName: responsiblePartyName,

scripts/populate-invoices-tasks-manually.ts

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1+
import Oystehr from '@oystehr/sdk';
12
import { CandidApiClient, CandidApiEnvironment } from 'candidhealth';
23
import { InventoryRecord } from 'candidhealth/api/resources/patientAr/resources/v1';
34
import fs from 'fs';
45
import { DateTime } from 'luxon';
5-
import { getCandidInventoryPages } from 'utils';
6+
import { getCandidInventoryPagesRecursive } from 'utils';
7+
import { createTaskForEncounter, getEncountersWithoutTaskFhir } from 'zambdas/src/cron/create-invoices-tasks';
68

7-
// async function createOyst(zambdaEnv: Record<string, string>, token: string): Promise<Oystehr> {
8-
// const oystehr = new Oystehr({
9-
// accessToken: token,
10-
// projectId: zambdaEnv.PROJECT_ID,
11-
// services: {
12-
// fhirApiUrl: zambdaEnv.FHIR_API,
13-
// projectApiUrl: zambdaEnv.PROJECT_API,
14-
// },
15-
// });
16-
// console.log(`Created Oystehr client`);
17-
// return oystehr;
18-
// }
9+
async function createOyst(zambdaEnv: Record<string, string>, token: string): Promise<Oystehr> {
10+
const oystehr = new Oystehr({
11+
accessToken: token,
12+
projectId: zambdaEnv.PROJECT_ID,
13+
services: {
14+
fhirApiUrl: zambdaEnv.FHIR_API,
15+
projectApiUrl: zambdaEnv.PROJECT_API,
16+
},
17+
});
18+
console.log(`Created Oystehr client`);
19+
return oystehr;
20+
}
1921

2022
async function createCandid(
2123
zambdaEnv: Record<string, string>,
@@ -39,14 +41,14 @@ async function main(): Promise<void> {
3941
const candidEnv = environment === 'production' ? CandidApiEnvironment.Production : CandidApiEnvironment.Staging;
4042
const startFrom = DateTime.fromFormat('01/14/2026', 'MM/dd/yyyy');
4143
const endOn = startFrom.plus({ weeks: 2 });
42-
// const token = '<a-key-from-ottehr-console-here>';
44+
const token = '<a-key-from-ottehr-console-here>';
4345
const maxCandidPages = 18;
4446

4547
console.log(`Reading environment variables from packages/zambdas/.env/${environment}.json.`);
4648
const zambdaEnv: Record<string, string> = JSON.parse(
4749
fs.readFileSync(`packages/zambdas/.env/${environment}.json`, 'utf8')
4850
);
49-
// const oystehr = await createOyst(zambdaEnv, token);
51+
const oystehr = await createOyst(zambdaEnv, token);
5052
const candid = await createCandid(zambdaEnv, candidEnv);
5153

5254
let candidClaims = await getAllCandidClaims(candid, startFrom, maxCandidPages);
@@ -63,13 +65,13 @@ async function main(): Promise<void> {
6365
}
6466
console.log(`Found ${candidClaims.length} claims between ${firstDate} and ${lastDate}`);
6567

66-
// const pkgs = await getEncountersWithoutTaskFhir(oystehr, candid, candidClaims);
67-
// console.log('Encounters without a task and positive amount: ', pkgs.length);
68+
const pkgs = await getEncountersWithoutTaskFhir(oystehr, candid, candidClaims);
69+
console.log('Encounters without a task and positive amount: ', pkgs.length);
6870

6971
const promises: Promise<void>[] = [];
70-
// pkgs.forEach((encounter) => {
71-
// promises.push(createTaskForEncounter(oystehr, encounter));
72-
// });
72+
pkgs.forEach((encounter) => {
73+
promises.push(createTaskForEncounter(oystehr, encounter));
74+
});
7375
await Promise.all(promises);
7476
}
7577

@@ -78,9 +80,11 @@ async function getAllCandidClaims(
7880
sinceDate: DateTime,
7981
maxPages?: number
8082
): Promise<InventoryRecord[]> {
81-
const inventoryPages = await getCandidInventoryPages({
83+
const inventoryPages = await getCandidInventoryPagesRecursive({
8284
candid,
85+
claims: [],
8386
limitPerPage: 100,
87+
pageCount: 0,
8488
maxPages,
8589
onlyInvoiceable: true,
8690
since: sinceDate,
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import Oystehr from '@oystehr/sdk';
2+
import { SearchParam } from '@oystehr/sdk/dist/esm';
3+
import { CandidApiClient, CandidApiEnvironment } from 'candidhealth';
4+
import { InventoryRecord } from 'candidhealth/api/resources/patientAr/resources/v1';
5+
import { Encounter, Task } from 'fhir/r4b';
6+
import fs from 'fs';
7+
import { createInvoiceTaskInput, findClaimsBy, parseInvoiceTaskInput, RCM_TASK_SYSTEM, RcmTaskCode } from 'utils';
8+
import { getCandidEncounterIdFromEncounter } from 'zambdas/src/shared';
9+
10+
interface TaskGroup {
11+
task: Task;
12+
encounter: Encounter;
13+
}
14+
15+
async function createOyst(zambdaEnv: Record<string, string>, token: string): Promise<Oystehr> {
16+
const oystehr = new Oystehr({
17+
accessToken: token,
18+
projectId: zambdaEnv.PROJECT_ID,
19+
services: {
20+
fhirApiUrl: zambdaEnv.FHIR_API,
21+
projectApiUrl: zambdaEnv.PROJECT_API,
22+
},
23+
});
24+
console.log(`Created Oystehr client`);
25+
return oystehr;
26+
}
27+
28+
async function createCandid(
29+
zambdaEnv: Record<string, string>,
30+
candidEnv: CandidApiEnvironment
31+
): Promise<CandidApiClient> {
32+
const candidClientId = zambdaEnv.CANDID_CLIENT_ID || '';
33+
const candidClientSecret = zambdaEnv.CANDID_CLIENT_SECRET || '';
34+
console.log(`Creating Candid client`);
35+
36+
return new CandidApiClient({
37+
clientId: candidClientId,
38+
clientSecret: candidClientSecret,
39+
environment: candidEnv,
40+
});
41+
}
42+
43+
async function main(): Promise<void> {
44+
const environment: 'local' | 'development' | 'testing' | 'staging' | 'production' = 'local';
45+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
46+
// @ts-expect-error
47+
const candidEnv = environment === 'production' ? CandidApiEnvironment.Production : CandidApiEnvironment.Staging;
48+
const token =
49+
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlRRc2xGbWlRX01ZTzg4Z3BRUnlvRCJ9.eyJpc3MiOiJodHRwczovL2F1dGguemFwZWhyLmNvbS8iLCJzdWIiOiJhdXRoMHw2NzU3NTM3NTRhYWY3OGU0NTllOWQ4OTMiLCJhdWQiOlsiaHR0cHM6Ly9hcGkuemFwZWhyLmNvbSIsImh0dHBzOi8vemFwZWhyLnVzLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE3NzEzNDE3NDAsImV4cCI6MTc3MTQyODE0MCwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCBvZmZsaW5lX2FjY2VzcyIsImF6cCI6Im8zcnN6bDJuS0k2STFhSDZzYmw4ZEZyRmdNVkcyaU1jIn0.mAy92nUGhy7QatsSG2GAx7WxJz6OsKBA5IS04vgTKxu2hYVHm_-984s3THsII2JN58nvbYv7d-JByECkM8SmvEfTdflqNIK0qx2VAUBpADALBlAXfl3WgE_31yJz13BDFdXHxRcSUTfdtm-XGjCb9j2uSaljhSzPXHTreyMCYq2xXoomvkOSQYTVw6KoPnF-gap2wTPal8Wk8-m18KhSky8pR1Hp8o4U28RupLB0E4bTPjFVBaAaTHxMSYaT70CHi6sygCzjoTJICJ5xtPTiyDIkBg0GvlKTz57OHiywxiqu06btjgRsBFJXJiOZJCFa9peS4TUVgeB8xthfvQlltQ';
50+
51+
console.log(`Reading environment variables from packages/zambdas/.env/${environment}.json.`);
52+
const zambdaEnv: Record<string, string> = JSON.parse(
53+
fs.readFileSync(`packages/zambdas/.env/${environment}.json`, 'utf8')
54+
);
55+
const oystehr = await createOyst(zambdaEnv, token);
56+
const candid = await createCandid(zambdaEnv, candidEnv);
57+
58+
let page = 0;
59+
const pageSize = 100;
60+
let tasksGroups = await getTasksPaginated(oystehr, 0, pageSize);
61+
console.log('Total tasks: ', tasksGroups.total);
62+
// const allPages = Math.ceil(tasksGroups.total / pageSize);
63+
const allPages = 2;
64+
65+
do {
66+
const tasksMissingData: TaskGroup[] = [];
67+
tasksGroups.groups.forEach((group) => {
68+
const taskInput = parseInvoiceTaskInput(group.task);
69+
if (!taskInput.claimId || !taskInput.finalizationDate || !group.task.for) {
70+
tasksMissingData.push(group);
71+
}
72+
});
73+
74+
const candidEncounterIdsToLookFor: string[] = [];
75+
tasksMissingData.forEach((group) => {
76+
const candidEncounterId = getCandidEncounterIdFromEncounter(group.encounter);
77+
candidEncounterIdsToLookFor.push(candidEncounterId);
78+
});
79+
80+
const inventoryRecords = await findClaimsBy({ candid, candidEncountersIds: candidEncounterIdsToLookFor });
81+
console.log('Found claims: ', inventoryRecords.length);
82+
83+
const promises: Promise<void>[] = [];
84+
tasksMissingData.forEach((group) => {
85+
const inventoryRecord = inventoryRecords.find(
86+
(record) => record.encounterId === getCandidEncounterIdFromEncounter(group.encounter)
87+
);
88+
if (inventoryRecord) {
89+
promises.push(updateTask(oystehr, group, inventoryRecord));
90+
}
91+
});
92+
await Promise.all(promises);
93+
94+
tasksGroups = await getTasksPaginated(oystehr, page);
95+
page++;
96+
} while (page < allPages - 1);
97+
}
98+
99+
async function getTasksPaginated(
100+
oystehr: Oystehr,
101+
pageNumber: number,
102+
pageSize = 100
103+
): Promise<{ groups: TaskGroup[]; total: number }> {
104+
const params: SearchParam[] = [
105+
{
106+
name: '_sort',
107+
value: '-authored-on',
108+
},
109+
{
110+
name: '_total',
111+
value: 'accurate',
112+
},
113+
{
114+
name: '_count',
115+
value: pageSize,
116+
},
117+
{
118+
name: 'code',
119+
value: `${RCM_TASK_SYSTEM}|${RcmTaskCode.sendInvoiceToPatient}`,
120+
},
121+
{
122+
name: '_include',
123+
value: 'Task:encounter',
124+
},
125+
{
126+
name: '_offset',
127+
value: pageNumber * pageSize,
128+
},
129+
];
130+
131+
const bundle = await oystehr.fhir.search({
132+
resourceType: 'Task',
133+
params,
134+
});
135+
136+
const resources = bundle.unbundle();
137+
const tasks = resources.filter((r) => r.resourceType === 'Task') as Task[];
138+
console.log('Tasks found: ', tasks.length);
139+
140+
const resultGroups: TaskGroup[] = [];
141+
tasks.forEach((task) => {
142+
const encounterId = task.encounter?.reference?.replace('Encounter/', '');
143+
const encounter = resources.find((res) => res.resourceType === 'Encounter' && res.id === encounterId) as Encounter;
144+
if (!encounter) {
145+
console.error(
146+
`Task with id: ${task.id} was not included in the bundle because it's missing encounter with id: ${encounterId}`
147+
);
148+
return;
149+
}
150+
151+
resultGroups.push({
152+
task,
153+
encounter,
154+
});
155+
});
156+
return { groups: resultGroups, total: bundle.total ?? 0 };
157+
}
158+
159+
async function updateTask(oystehr: Oystehr, group: TaskGroup, inventoryRecord: InventoryRecord): Promise<void> {
160+
try {
161+
const taskInput = parseInvoiceTaskInput(group.task);
162+
taskInput.claimId = inventoryRecord.claimId.toString();
163+
taskInput.finalizationDate = inventoryRecord.timestamp.toISOString();
164+
165+
const patientRef = group.encounter.subject;
166+
console.log(
167+
`Adding to task: ${group.task.id}, patientRef: ${patientRef.reference}, claimId: ${taskInput.claimId}, finalizationDate: ${taskInput.finalizationDate}`
168+
);
169+
170+
await oystehr.fhir.patch({
171+
resourceType: 'Task',
172+
id: group.task.id,
173+
operations: [
174+
{ op: 'replace', path: '/input', value: createInvoiceTaskInput(taskInput) },
175+
{ op: 'add', path: '/for', value: patientRef },
176+
],
177+
});
178+
} catch (e) {
179+
console.error(`Error updating task: ${group.task.id} with error: ${e.message ?? e.toString() ?? 'unknown error'}`);
180+
}
181+
}
182+
183+
main().catch((error) => {
184+
console.log('Error: ', error);
185+
});

0 commit comments

Comments
 (0)