· 6 years ago · Jul 21, 2019, 01:12 PM
1var async = require("async");
2var moment = require("moment");
3//this object is dictionery for finding the name in top manufacture and foundation
4var potentialSavingSourceMapping = {
5 "Munson Medical Center": "Local Charity",
6 "Leelanau County Cancer Foundation": "Local Charity",
7 "Women’s Cancer Funds": "Local Charity",
8 "Colon Cancer Alliance": "Patient Foundation",
9 "General Cancer Funds": "Local Charity",
10 "Cancer Care": "Patient Foundation",
11 "Healthwell Foundation": "Patient Foundation",
12 "Patient Access Network Foundation": "Patient Foundation",
13 "The Leukemia & Lymphoma Society": "Patient Foundation",
14 "Christina S. Walsh Breast Cancer Foundation": "Patient Foundation",
15 "National Pancreatic Cancer Foundation": "Patient Foundation",
16 "21st Century": "Patient Foundation",
17 "Catherine Fund": "Patient Foundation",
18 "Allyson Whitney Foundation - Life Interrupted Grant": "Patient Foundation",
19 "Patient Advocate Foundation": "Patient Foundation",
20 "Remember Betty": "Patient Foundation",
21 "Jill's Wish": "Patient Foundation",
22 "Children's Leukemia Foundation of Michigan": "Patient Foundation",
23 "Darren Daulton Foundation": "Patient Foundation",
24 "Her Nexx Chapter": "Patient Foundation",
25 Ovarcome: "Patient Foundation",
26 "Glenn Garcelon Foundation": "Patient Foundation",
27 "Social Security": "Government Plan",
28 Genentech: "Pharma Copay",
29 "Merck Patient Assistance, Inc.": "Pharma Copay",
30 Celgene: "Pharma Copay",
31 Amgen: "Pharma Copay",
32 Bayer: "Pharma Copay",
33 AbbVie: "Pharma Copay",
34 BMS: "Pharma Copay",
35 Novartis: "Pharma Copay",
36 Medicare: "Government Plan",
37 "Munson Healthcare": "Local Charity",
38 "MIU Men's health fundation": "Patient Foundation",
39 "Testicular Cancer Awareness Foundation": "Patient Foundation",
40 Lilly: "Pharma Copay",
41 "Michigan Department of Health & Human Services": "Insurance Optimization",
42 "The Breast Cancer Charities of America": "Patient Foundation",
43 "Johnson & Johnson": "Pharma Copay",
44 "Cancer Recovery Foundation": "Patient Foundation",
45 "Genentech Inc.": "Pharma Copay",
46 "Genentech Access to Care Foundation": "Pharma Copay",
47 "Pfizer Inc.": "Pharma Copay",
48 "Good Days": "Patient Foundation",
49 "National Organization for Rare Disorders (NORD)": "Patient Foundation",
50 "Cancer Care Assist": "Patient Foundation",
51 "Genentech, Inc.": "Pharma Copay",
52 "Amgen, Inc.": "Pharma Copay",
53 "Patient Advocate Foundation (PAF)": "Patient Foundation",
54 "Allyson Whitney Foundation": "Patient Foundation",
55 "Provision Project": "Patient Foundation"
56};
57
58/**
59 * @name filterRusultsByPatientIds
60 * @params :: patientResults is an array of patients, patientIDsQaMode array of patients to test on them
61 * @description :: filter array of patients by qa patients array to continue with the qa patients only
62 * @return :: patientsFiltered array, the only qaPatients that are in the patientResults
63 */
64var filterRusultsByPatientIds = function (patientResults, patientIDsQaMode) {
65 patientsFiltered = [];
66 patientResults = _.groupBy(patientResults, "id");
67 patientIDsQaMode.forEach(QaPatientID => {
68 if (patientResults[QaPatientID]) {
69 patientsFiltered.push(patientResults[QaPatientID][0]);
70 }
71 })
72 return patientsFiltered;
73}
74
75/**
76 * @name findAllPatientsData
77 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date), cb is a callback
78 * @description :: find all active patients, then filter patients (by facility, assignee, date) and then
79 * calc patients data:
80 * 1.calc age distribution
81 * 2.number of assignedActivePatients
82 * 3.number of active patients
83 * 4.number of recievedAssistance
84 * @return :: return json that contains activePatients field - number of active patient, and assignedActivePatients field - all assignee patients(means there is user that connect to patient)
85 * ageDistribution - of active patients, recievedAssistance - number of active patients recievedAssistance
86 */
87async function findAllPatientsData(filterObj, cb) {
88 let patientsArr = [];
89 let assignUsers = [];
90 let recievedAssistance = 0;
91 let ageDistribution = 0;
92 //in this method we get patients
93 searchForActivePatientFilter(async (err, patients) => {
94 if (err) return cb(err);
95 let patientsByFilter = [];
96 //--------------------------------------- start: QA needs ---------------------------------------//
97 var patientIDsQaMode = await Setting.findOne({
98 key: "QaModeInsights"
99 });
100 if (patientIDsQaMode) {
101 try {
102 patientIDsQaMode = JSON.parse(patientIDsQaMode.value);
103 patients = filterRusultsByPatientIds(patients, patientIDsQaMode);
104 } catch (err) {
105 sails.log.info("patientIDsQaMode exist but no value set.");
106 }
107 }
108 //--------------------------------------- end: QA needs ---------------------------------------//
109 //-------------------------filter step------------------------//
110 patientsByFilter = filterByDateBase(patients, filterObj, "createdAt");
111
112 patientsByFilter = await filterByFacilityPatientBase(patientsByFilter, filterObj, "id");
113
114 //filter by assignee:
115 patientsByFilter = await filterByAssigneePatientBase(patientsByFilter, filterObj);
116
117 //create the number of assignee patients
118 //TODO: NAVOT check this
119 // assignUsers = patientsByFilter.filter((patient) => patient.user || _.get(patient, "user.length"));
120 assignUsers = patientsByFilter.filter((patient) => patient.user);
121 //add age field to each patient
122 if (patientsByFilter.length > 0) {
123 //get age of each patient
124 patientsArr = await populatePatientsAges(patientsByFilter);
125 ageDistribution = calculateActivePatientAgeDistribution(patientsArr);
126 findAmountOfActivePatientsByFilter(filterObj, function (err, patientsRecievedAssistanceAmount) {
127 if (err) cb(err);
128 let patientsDashboardObj = {
129 activePatients: this.patientsArr.length,
130 assignedActivePatients: this.assignUsers.length,
131 ageDistribution: this.ageDistribution,
132 recievedAssistance: patientsRecievedAssistanceAmount
133 }
134 return cb(null, patientsDashboardObj);
135 }.bind({ patientsArr: patientsArr, ageDistribution: ageDistribution, assignUsers: assignUsers }))
136 } else {
137 return cb(null, {
138 activePatients: patientsArr.length, assignedActivePatients: assignUsers.length,
139 ageDistribution: ageDistribution, recievedAssistance: recievedAssistance
140 });
141 }
142 });
143}
144
145/**
146 * @name searchForActivePatientFilter
147 * @params :: cb callback
148 * @description :: helper method for findAllPatientsData: filter all active, first find the journeys that relevant to patients id, then get the latest calculation of each journey group
149 * and then filter from the latest calculation record the data of the patient
150 * @return :: send to callback an array of patients that attached to the relevant calculationRecords after the filter
151 */
152function searchForActivePatientFilter(cb) {
153 let patientsIds;
154 let todayDate = moment().format('MM/DD/YYYY');
155 //join data from patients into journey(we need it to match the journey id to patient who holds the user id)
156 let firstMySqlQuery = `SELECT * FROM \`journey\` AS j INNER JOIN \`patient\` AS p ON (j.patient = p.id)`
157 Journey.query(firstMySqlQuery, [], function (err, journeys) {
158 let journeysIdsByPatients = journeys.map(j => j.id);
159 //let journeysGroupById = _.groupBy(journeys, "id");
160 //this query return all the latest record that related to the journeys and in the valid range
161 let subQuery = `(SELECT * FROM \`calculationrecord\`
162 WHERE journey IN (${journeysIdsByPatients}) AND id IN
163 (
164 SELECT MAX(\`id\`) FROM \`calculationrecord\` GROUP BY \`journey\`
165 )
166 AND ${todayDate} BETWEEN '\`journeyStartDate\`' AND \`journeyEndDate\`)`
167 //here we join journey to use patient data of each calculation record later
168 let mySqlQuery = `SELECT * FROM ${subQuery} AS c INNER JOIN \`journey\` j ON (c.journey = j.id) INNER JOIN \`patient\` pt ON (pt.id = j.patient)`
169 CalculationRecord.query(mySqlQuery, [], function (err, calculationRecordsResults) {
170 if (!calculationRecordsResults || err) return cb(new TMError("calculation Records Results not found"));
171 //extract patient id's from calculation records
172 var calculationRecordGroupByPatients = _.groupBy(calculationRecordsResults, "patient")
173 patientsIds = Object.keys(calculationRecordGroupByPatients);
174 let patientsData = [];
175 //extract patient data by patient id
176 patientsIds.forEach(patientID => {
177 if (calculationRecordGroupByPatients[patientID]) {
178 let tmpPatientData = extractPatientDataFromCalcRecord(calculationRecordGroupByPatients[patientID][0]);
179 if (tmpPatientData.id) {
180 patientsData.push(tmpPatientData);
181 }
182 }
183 });
184 return cb(null, patientsData);
185 });
186 })
187}
188
189/**
190 * @name findAmountOfActivePatientsByFilter
191 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date), cb is a callback
192 * @description :: 1.find all the paps with status approved
193 * 2.find all the relevant journeys by the paps and get thae patients of those journeys
194 * @return :: json with field patientsReceivedAssistance that contains the number of unique patients after filter(means recieve assistance)
195 */
196function findAmountOfActivePatientsByFilter(filterObj, cb) {
197 //Table assistanceprogramapplication get all paps with status approved... then pap.find by ids
198 AssistanceProgramApplication.find({ status: "approved", select: ["pap", "approvedDate"] }).exec((err, approvedApplications) => {
199 if (!approvedApplications) return cb(new TMError("Approved assistance applications not found"));
200 if (err) return cb(err);
201 var papsIds = approvedApplications.map((obj => obj.pap)).filter(pap => pap);
202 approvedApplications = _.groupBy(approvedApplications, 'pap');
203 Pap.find({ id: papsIds, select: ["approvedDate", "id", "journey"] })
204 .exec((err, approvedPaps) => {
205 if (!approvedPaps) return cb(new TMError("Approved Paps not found"));
206 if (err) return cb(err);
207 var journeysIds = [];
208 approvedPaps.forEach(item => {
209 if (!filterObj.date) {
210 journeysIds.push(item.journey.toString());
211 }
212 //filter paps by date:
213 if (filterObj && filterObj.date && approvedApplications[item.id][0].approvedDate && dateValidator(approvedApplications[item.id][0].approvedDate, filterObj)) {
214 journeysIds.push(item.journey.toString());
215 }
216 });
217 Journey.find({ id: journeysIds, select: ["patient", "id"] })
218 .populate("patient")
219 .exec(async (err, approvedPaps) => {
220 if (!approvedPaps) return cb(new TMError("journeys not found"));
221 if (err) return cb(err);
222 var patients = approvedPaps.map(item => item.patient);
223
224 var patientIDsQaMode = await Setting.findOne({
225 key: "QaModeInsights"
226 });
227 if (patientIDsQaMode) {
228 try {
229 patientIDsQaMode = JSON.parse(patientIDsQaMode.value);
230 patients = filterRusultsByPatientIds(patients, patientIDsQaMode);
231 } catch (err) {
232 sails.log.info("patientIDsQaMode exist but no value set.");
233 }
234 }
235 var uniquePatients = _.uniq(patients, "id");
236 //TODO: NAVOT tell maor you change
237 return cb(null, uniquePatients.length);
238 });
239 });
240 })
241}
242
243/**
244 * @name findTop5DrugsManufacturesAndFoundations
245 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date), cb is a callback
246 * @description :: find the relevant paps, filter them by filter obj, calc top manufacturers and foundations by priority( manufacturers - 1,2 foundations - 3 ), calc top drugs
247 * @return :: excute cb (null,json) where json contains drugs, manufacturers, foundations fields
248 * if err return cb(err)
249 */
250function findTop5DrugsManufacturesAndFoundations(filterObj, cb) {
251 let topNumber = 5;
252 AssistanceProgramApplication.find({ status: "approved", select: ["pap", "approvedDate"] }).exec((err, approvedApplications) => {
253 if (!approvedApplications) return cb(new TMError("Approved assistance applications not found"));
254 if (err) return cb(err);
255 var papsIds = approvedApplications.map((obj => obj.pap)).filter(pap => pap);
256 Pap.find({ id: papsIds, associatedDrugEntry: { "!": null }, select: ["createdAt", "id", "assistanceProgram", "journey", "associatedDrugEntry", "currentApplication", "grantTotal"] })
257 .populate("journey", { select: ["patient"] })
258 .populate("currentApplication")
259 .populate("associatedDrugEntry", { select: ["drug"] })
260 .populate("assistanceProgram")
261 .exec(async (err, paps) => {
262 if (err) {
263 cb(err);
264 }
265 let filteredPaps = await filterPaps(paps, filterObj);
266 var dictioneryOfTop = calcByPriority(filteredPaps, topNumber);
267 dictioneryOfTop.drugs = await calcTopDrugs(filteredPaps, topNumber);
268 return cb(null, { drugs: dictioneryOfTop.drugs, manufacturers: dictioneryOfTop.manufacturers, foundations: dictioneryOfTop.foundations });
269 });
270 });
271}
272
273/**
274 * @name findApprovedAssistanceOverTime
275 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date), cb is a callback
276 * @description :: 1. get the AssistanceProgramApplication with status approved
277 * 2. remove undefined, and get the journeys id's of each pap
278 * 3. get each journey from the journeysIds we found
279 * 4. for each assistanceProgram in assistanceProgramsApplication assign assistanceProgram.pap.journey with the correct journy
280 * 5. if filter date bigger then 395 days - reduce the diff to 395 days
281 * 6. filter assistanceProgramsApplication by date, assignee, facility
282 * @return :: approvedAssistanceOverTime array of the approve assistance(includes date, approvedAmount, Amount of Approved, Total Approved)
283 */
284function findApprovedAssistanceOverTime(filterObj, cb) {
285 let approvedAssistanceOverTime = [];
286 AssistanceProgramApplication.find({ status: "approved", approvedDate: { "!": null }, select: ["id", "pap", "approvedAmount", "approvedDate", "user"] })
287 .populate("pap", { select: ["id", "journey"] })
288 .sort('approvedDate ASC')
289 .exec(async (err, assistanceProgramsApplication) => {
290 if (err) return cb(err);
291 if (!assistanceProgramsApplication) return cb(new TMError("Approved programs applications not found"));
292 assistanceProgramsApplication = assistanceProgramsApplication.filter(assistance => assistance.pap);
293 var journeysIds = Object.keys(_.groupBy(assistanceProgramsApplication, 'pap.journey'));
294 var patientsByJourneys = await Journey.find({ id: journeysIds, select: ["id", "patient"] }).populate("patient", { select: ["id", "user"] });
295
296 patientsByJourneys = _.groupBy(patientsByJourneys, 'id');
297 assistanceProgramsApplication = assistanceProgramsApplication.map(assistanceProgram => {
298 assistanceProgram.pap.journey = patientsByJourneys[assistanceProgram.pap.journey][0];
299 return assistanceProgram;
300 })
301 //--------------------------------------- start: QA needs ---------------------------------------//
302 var patientIDsQaMode = await Setting.findOne({
303 key: "QaModeInsights"
304 });
305
306 if (patientIDsQaMode) {
307 try {
308 patientIDsQaMode = JSON.parse(patientIDsQaMode.value);
309 var FiltreredAssistanceProgramsApplication = assistanceProgramsApplication.filter(assistance => {
310 if (_.get(assistance, 'pap.journey.patient.id')) {
311 return patientIDsQaMode.some(patientQaID => patientQaID == assistance.pap.journey.patient.id);
312 }
313 return false;
314 })
315 assistanceProgramsApplication = FiltreredAssistanceProgramsApplication;
316 } catch (err) {
317 sails.log.info("patientIDsQaMode exist but no value set.");
318 }
319 }
320 //--------------------------------------- end: QA needs ---------------------------------------//
321
322 let dateField = "approvedDate";
323 // support date range for approved saving over time. max limit to 395 days
324 if (_.get(filterObj, 'date')) {
325 var daysBetweenDates = moment(filterObj.date.to).diff(moment(filterObj.date.from), 'days');
326 if (daysBetweenDates > 395) {
327 filterObj.date.from = moment(filterObj.date.to).subtract(395, "days").format('YYYY-MM-DD');
328 }
329 }
330
331 approvedAssistanceOverTime = filterByDateBase(assistanceProgramsApplication, filterObj, dateField)
332
333 let assigneeField = "user";
334 approvedAssistanceOverTime = await filterByAssigneeApprovedAssistanceBase(approvedAssistanceOverTime, patientsByJourneys, filterObj, assigneeField);
335
336 approvedAssistanceOverTime = await filterByFacilityApproveAssistanceBase(approvedAssistanceOverTime, filterObj);
337
338 approvedAssistanceOverTime = approvedAssitanceOverTimeFormat(filterObj, approvedAssistanceOverTime, daysBetweenDates);
339 return cb(null, { approvedAssistanceOverTime });
340
341 });
342}
343
344/**
345 * @name getTopDiagnoses
346 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date), cb is a callback
347 * @description :: 1. get all patient diagnosis that isPrimary: 1, isActive: 1
348 * 2. filter patient diagnosis by date, facility, assignee by patients base
349 * 3. sort by length and slice by numberOfTopDiagnoses the filterPatientDiagnosisDetails array
350 * @return :: top diagnosis json , in topDiagnosis will be the names of the top diagnosis as fields and for each diagnosis the patients that has the diagnosis
351 */
352async function getTopDiagnoses(filterObj, cb) {
353 let topDiagnoses = {};
354 let filterPatientDiagnosisDetails = []
355 let numberOfTopDiagnoses = 5;
356 var patientDiagnosisDetails = await PatientDiagnosis.find({
357 where: { isPrimary: 1, isActive: 1 },
358 select: ['icd', 'diagnosis', 'patient', 'diagnosisDate', 'createdAt']
359 });
360 //--------------filter step-----------------//
361 filterPatientDiagnosisDetails = filterByDateBase(patientDiagnosisDetails, filterObj, "diagnosisDate", "createdAt");
362
363 filterPatientDiagnosisDetails = await filterByFacilityPatientBase(filterPatientDiagnosisDetails, filterObj, "patient");
364
365 filterPatientDiagnosisDetails = await filterByAssigneePatientBase(filterPatientDiagnosisDetails, filterObj, "patient", "patientRelatedData")
366
367 try {
368 //--------------------------------------- start: QA needs ---------------------------------------//
369 var patientIDsQaMode = await Setting.findOne({
370 key: "QaModeInsights"
371 });
372
373 if (patientIDsQaMode) {
374 try {
375 patientIDsQaMode = JSON.parse(patientIDsQaMode.value);
376 var filteredDetails = filterPatientDiagnosisDetails.filter(patientDiagnosisDetail => {
377 if (_.get(patientDiagnosisDetail, 'patient')) {
378 return patientIDsQaMode.some(patientQaID => patientQaID == patientDiagnosisDetail.patient);
379 }
380 return false;
381 })
382 filterPatientDiagnosisDetails = filteredDetails;
383 } catch (err) {
384 sails.log.info("patientIDsQaMode exist but no value set.");
385 }
386 }
387 //--------------------------------------- end: QA needs ---------------------------------------//
388
389 filterPatientDiagnosisDetails = _.groupBy(filterPatientDiagnosisDetails, function (obj) {
390 obj['diagnosis'] = titualCase(obj['diagnosis']);
391 return obj['diagnosis'];
392 });
393 filterPatientDiagnosisDetails = _.sortBy(filterPatientDiagnosisDetails, 'length');
394 filterPatientDiagnosisDetails.reverse();
395 filterPatientDiagnosisDetails = filterPatientDiagnosisDetails.slice(0, numberOfTopDiagnoses);
396 //checks if we have less then 5 patientDiagnosis
397 if (filterPatientDiagnosisDetails.length < numberOfTopDiagnoses) {
398 numberOfTopDiagnoses = filterPatientDiagnosisDetails.length;
399 }
400 //parse the object we want to return
401 for (let i = 0; i < numberOfTopDiagnoses; i++) {
402 topDiagnoses[`${filterPatientDiagnosisDetails[i][0].diagnosis}`] = filterPatientDiagnosisDetails[i].length;
403 }
404 } catch (err) {
405 throw err;
406 }
407 //check if we found data in the db query
408 if (!patientDiagnosisDetails) return cb(new TMError("patientDiagnosisDetails not found"));
409 return cb(null, topDiagnoses);
410}
411
412/**
413 * @name getPayerMix
414 * @params :: filterObj object that contains fields to filter by(npi, facilityMatchingString, assignee. date), cb is a callback
415 * @description :: 1. get all coverages
416 * 2. filter by date, facility, assignee by patients filter base
417 * 3. get the policy plan market of each coverage
418 * 4. group by plan market and sum the length of each plan market
419 * @return :: payer mix json that contains the fields "Medicare part d", "Original medicare", "Medigap", "Commercials" with there amount values
420 */
421async function getPayerMix(filterObj, cb) {
422 let payerMixObj = {};
423 let tempKey = "";
424 let patients;
425 try {
426 var coverageDetails = await Coverage.find()
427 .populate("policy")
428 .populate("coverageInfoValues")
429 .populate("patient");
430
431 //--------------filter step-----------------//
432 let filterCovarage = filterByDateBase(coverageDetails, filterObj, null, null, "Coverage");
433
434 filterCovarage = await filterByFacilityPatientBase(filterCovarage, filterObj, "id");
435
436 filterCovarage = await filterByAssigneePatientBase(filterCovarage, filterObj, "id", "patientRelatedData");
437 //--------------------------------------- start: QA needs ---------------------------------------//
438 var patientIDsQaMode = await Setting.findOne({
439 key: "QaModeInsights"
440 });
441 if (patientIDsQaMode) {
442 try {
443 patientIDsQaMode = JSON.parse(patientIDsQaMode.value);
444 if (!patients) {
445 patients = await Patient.find({ id: patientIDsQaMode, select: ['id', 'user', 'createdAt'] });
446 }
447 var filteredCoveragesByPatientsID = [];
448 patients = _.groupBy(patients, "id");
449 _.map(filterCovarage, function (coverage) {
450 if (patients[coverage.patient] !== undefined) {
451 filteredCoveragesByPatientsID.push(coverage);
452 }
453 })
454 filterCovarage = filteredCoveragesByPatientsID;
455 } catch (err) {
456 sails.log.info("patientIDsQaMode exist but no value set.");
457 }
458 }
459 //--------------------------------------- end: QA needs ---------------------------------------//
460 filterCovarage = _.map(filterCovarage, 'policy.planMarket');
461 filterCovarage = _.groupBy(filterCovarage, function (planMarket) { return planMarket });
462 Object.keys(filterCovarage).forEach(key => {
463 switch (key) {
464 case 'medicare_part_d':
465 tempKey = titualCase('Medicare part d');
466 payerMixObj[`${tempKey}`] = filterCovarage.medicare_part_d.length;
467 break;
468 case 'original_medicare':
469 tempKey = titualCase('Original Medicare');
470 payerMixObj[`${tempKey}`] = filterCovarage.original_medicare.length;
471 break;
472 case 'medicare_advantage':
473 tempKey = titualCase('Medicare Advantage');
474 payerMixObj[`${tempKey}`] = filterCovarage.medicare_advantage.length;
475 break;
476 case 'medigap':
477 tempKey = titualCase('Medigap');
478 payerMixObj[`${tempKey}`] = filterCovarage.medigap.length;
479 break;
480 case 'individual':
481 case 'small_group':
482 case 'large_employer':
483 case 'state':
484 tempKey = titualCase('Commercials');
485 if (payerMixObj.hasOwnProperty('Commercials')) {
486 payerMixObj[`${tempKey}`] += filterCovarage[`${key}`].length;
487 } else {
488 payerMixObj[`${tempKey}`] = filterCovarage[`${key}`].length;
489 }
490 break;
491 default:
492 //TODO:NAVOT Def commarcials (only if active)
493 }
494 });
495 } catch (err) {
496 throw err;
497 }
498 if (!coverageDetails) return cb(new TMError("coverageDetails not found"));
499 return cb(null, payerMixObj);
500}
501
502/**
503 * @name getSavingStatistics
504 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date), cb is a callback
505 * @description :: 1. get all the AssistanceProgramApplication with status approved
506 * 2. get the paps that are relevant to the AssistanceProgramApplication
507 * 3. filter the paps by date, facility, assignee
508 * 4. build savingStatisticsDetails object by sorting the filterd paps by the fields : isLivingExpenses, isCopayAssistance, isFreeDrug, isPremiumAssistance, isInsuranceOptimization
509 * 5. sum for each field the total and approved amount
510 * 6. build savingNavigator object: if pap.currentApplication.user exist the name of the field is the name of the user, if not the name of the field is "Tailor Med"
511 * 7. sum for each userName the total and approved amount
512 * @return :: detailsObj json that includes savingStatisticsDetails object and savingNavigator object
513 */
514async function getSavingStatistics(filterObj, cb) {
515 let filterPapObj = {};
516 //this is the object we will return
517 let detailsObj = {
518 savingStatisticsDetails: {},
519 savingByNavigator: {}
520 };
521 let filterSavingNavigatorObj = {};
522 let savingStatisticsDetails;
523 let savingByNavigator = {};
524 var approvedAssictanceProgramApplications = await AssistanceProgramApplication.find({ status: "approved", select: ["pap", "approvedDate", "status"] });
525 let arrOfFilterdPapsId = createArrayByField(approvedAssictanceProgramApplications, "pap");
526 var paps = await Pap.find({ id: arrOfFilterdPapsId }).populate("currentApplication").populate("assistanceProgram").populate("journey");
527
528 //--------------------------------------- start: QA needs ---------------------------------------//
529 var patientIDsQaMode = await Setting.findOne({
530 key: "QaModeInsights"
531 });
532 if (patientIDsQaMode) {
533 try {
534 patientIDsQaMode = JSON.parse(patientIDsQaMode.value);
535 var Filtreredpaps = paps.filter(pap => {
536 if (_.get(pap, 'journey.patient')) {
537 return patientIDsQaMode.some(patientQaID => patientQaID == pap.journey.patient);
538 }
539 return false;
540 })
541 paps = Filtreredpaps;
542 } catch (err) {
543 sails.log.info("patientIDsQaMode exist but no value set.");
544 }
545
546 }
547 //--------------------------------------- end: QA needs ---------------------------------------//
548 let filterdPaps = await filterPaps(paps, filterObj);
549
550 if (filterdPaps.length !== 0) {
551 savingStatisticsDetails = {
552 LivingExpenses: {},
553 CopayAssistance: {},
554 FreeDrug: {},
555 PremiumAssistance: {},
556 InsuranceOptimization: {}
557 };
558 filterPapObj = sortPaps(filterdPaps);
559 //here we are building the saving staistics object
560 //calc by the paps that relevant to living expenses
561 let LivingExpensesObjectTotalResult = calclSavingNavigatorTotalAmount(filterPapObj.LivingExpenses);
562 //TODO: change to total amount - talk with maor
563 savingStatisticsDetails.LivingExpenses.Total = LivingExpensesObjectTotalResult.totalSum;
564 savingStatisticsDetails.LivingExpenses.TotalElements = LivingExpensesObjectTotalResult.totalElements;
565
566 let LivingExpensesObjectApprovedResult = calclSavingNavigatorApprovedAmount(filterPapObj.LivingExpenses);
567 savingStatisticsDetails.LivingExpenses.ApprovedAmount = LivingExpensesObjectApprovedResult.totalSum;
568 savingStatisticsDetails.LivingExpenses.ApprovedTotalElements = LivingExpensesObjectApprovedResult.totalElements;
569 savingStatisticsDetails.LivingExpenses.Count = filterPapObj.LivingExpenses.length;
570
571 //calc by the paps that relevant to copay assistance
572 let CopayAssistanceObjectTotalResult = calclSavingNavigatorTotalAmount(filterPapObj.CopayAssistance);
573 savingStatisticsDetails.CopayAssistance.Total = CopayAssistanceObjectTotalResult.totalSum;
574 savingStatisticsDetails.CopayAssistance.TotalElements = CopayAssistanceObjectTotalResult.totalElements;
575
576 let CopayAssistanceObjectApprovedResult = calclSavingNavigatorApprovedAmount(filterPapObj.CopayAssistance);
577 savingStatisticsDetails.CopayAssistance.ApprovedAmount = CopayAssistanceObjectApprovedResult.totalSum;
578 savingStatisticsDetails.CopayAssistance.ApprovedTotalElements = CopayAssistanceObjectApprovedResult.totalElements;
579 savingStatisticsDetails.CopayAssistance.Count = filterPapObj.CopayAssistance.length;
580
581 //calc by the paps that relevant to free drug
582 let FreeDrugObjectTotalResult = calclSavingNavigatorTotalAmount(filterPapObj.FreeDrug);
583 savingStatisticsDetails.FreeDrug.Total = FreeDrugObjectTotalResult.totalSum;
584 savingStatisticsDetails.FreeDrug.TotalElements = FreeDrugObjectTotalResult.totalElements;
585
586 let FreeDrugObjectApprovedResult = calclSavingNavigatorApprovedAmount(filterPapObj.FreeDrug);
587 savingStatisticsDetails.FreeDrug.ApprovedAmount = FreeDrugObjectApprovedResult.totalSum;
588 savingStatisticsDetails.FreeDrug.ApprovedTotalElements = FreeDrugObjectApprovedResult.totalElements;
589 savingStatisticsDetails.FreeDrug.Count = filterPapObj.FreeDrug.length;
590
591 //calc by the paps that relevant to premium assistance
592 let PremiumAssistanceObjectTotalResult = calclSavingNavigatorTotalAmount(filterPapObj.PremiumAssistance);
593 savingStatisticsDetails.PremiumAssistance.Total = PremiumAssistanceObjectTotalResult.totalSum;
594 savingStatisticsDetails.PremiumAssistance.TotalElements = PremiumAssistanceObjectTotalResult.totalElements;
595
596 let PremiumAssistanceObjectApprovedResult = calclSavingNavigatorApprovedAmount(filterPapObj.PremiumAssistance)
597 savingStatisticsDetails.PremiumAssistance.ApprovedAmount = PremiumAssistanceObjectApprovedResult.totalSum;
598 savingStatisticsDetails.PremiumAssistance.ApprovedTotalElements = PremiumAssistanceObjectApprovedResult.totalElements;
599 savingStatisticsDetails.PremiumAssistance.Count = filterPapObj.PremiumAssistance.length;
600
601 //calc by the paps that relevant to insurance optimization
602 let InsuranceOptimizationObjectTotalResult = calclSavingNavigatorTotalAmount(filterPapObj.InsuranceOptimization);
603 savingStatisticsDetails.InsuranceOptimization.Total = InsuranceOptimizationObjectTotalResult.totalSum;
604 savingStatisticsDetails.InsuranceOptimization.TotalElements = InsuranceOptimizationObjectTotalResult.totalElements;
605
606 let InsuranceOptimizationObjectApprovedResult = calclSavingNavigatorApprovedAmount(filterPapObj.InsuranceOptimization);
607 savingStatisticsDetails.InsuranceOptimization.ApprovedAmount = InsuranceOptimizationObjectApprovedResult.totalSum;
608 savingStatisticsDetails.InsuranceOptimization.ApprovedTotalElements = InsuranceOptimizationObjectApprovedResult.totalElements;
609 savingStatisticsDetails.InsuranceOptimization.Count = filterPapObj.InsuranceOptimization.length;
610 var isEmpty = true;
611 Object.keys(savingStatisticsDetails).forEach((key, index) => {
612 if (savingStatisticsDetails[key].Total != 0 && savingStatisticsDetails[key].ApprovedAmount != 0) {
613 isEmpty = false;
614 }
615 });
616
617 //here we are building the saving by navigator object
618 var usersIdArr = [];
619 if (!journeysByFilterPaps) {//means we didn't filter by assignee
620 var patientsId = _.groupBy(filterdPaps, function (pap) {
621 return pap.journey.patient;
622 });
623 let arrOfPatientId = Object.keys(patientsId);
624 let arrOfFilterPatientsId = arrOfPatientId.filter(id => id !== "null");
625 var journeysByFilterPaps = await Journey.find({ patient: arrOfFilterPatientsId, select: ["id", "patient", "name"] }).populate("patient");
626 journeysByFilterPaps = _.groupBy(journeysByFilterPaps, "id");
627 }
628 filterSavingNavigatorObj = _.groupBy(filterdPaps, function (pap) {
629 if (journeysByFilterPaps[pap.journey.id][0].patient.user === null) {
630 return "Unassigned";
631 } else {
632 usersIdArr.push(journeysByFilterPaps[pap.journey.id][0].patient.user);
633 return journeysByFilterPaps[pap.journey.id][0].patient.user;
634 }
635 });
636
637 //avoid duplicate values
638 usersIdArr = _.uniq(usersIdArr);
639 //get users data
640 let users = await User.find({ id: usersIdArr, select: ["id", "firstName", "lastName"] });
641 users = _.groupBy(users, "id");
642 //calculate saving by navigator
643 Object.keys(filterSavingNavigatorObj).forEach(key => {
644 if (key !== "Unassigned") {
645 let nameOfUser = jsUcfirst(users[Number(key)][0].firstName) + " " + jsUcfirst(users[Number(key)][0].lastName);
646 savingByNavigator[`${nameOfUser}`] = {};
647 savingByNavigator[`${nameOfUser}`].Total = calclSavingNavigatorTotalAmount(filterSavingNavigatorObj[`${key}`]).totalSum;
648 savingByNavigator[`${nameOfUser}`].ApprovedSaving = calclSavingNavigatorApprovedAmount(filterSavingNavigatorObj[`${key}`]).totalSum;
649 } else {
650 savingByNavigator[`${key}`] = {};
651 savingByNavigator[`${key}`].Total = calclSavingNavigatorTotalAmount(filterSavingNavigatorObj[`${key}`]).totalSum;
652 savingByNavigator[`${key}`].ApprovedSaving = calclSavingNavigatorApprovedAmount(filterSavingNavigatorObj[`${key}`]).totalSum;
653 }
654
655 });
656 }
657 //buiding the object to return : includes savingByNavigator and savingStatisticsDetails
658 if (isEmpty) {
659 detailsObj.savingStatisticsDetails = undefined;
660 } else {
661 detailsObj.savingStatisticsDetails = savingStatisticsDetails;
662 }
663 detailsObj.savingByNavigator = savingByNavigator;
664 return cb(null, detailsObj);
665}
666
667/**
668 * @name getRevenueIncreaseAndCommunityBenefits
669 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date), cb is a callback
670 * @description :: 1. get all the approvedSavingCalculationRecord that validationStatus is "validated" or "freeze" and filter them by filter patients base
671 * 2. filterApprovedSavingCalculationRecord by facility, assignee
672 * 3. groupBy pap and for each pap find if there is freeze record
673 * 3.1 if there is freeze record for pap put the freeze record as latestUpdatedDetail
674 * 3.2 else find the latest record in the records that are relevant to this pap
675 * 4. for the latest record that you found add the revenue amount and comunity benefit to detailsObj by the saving type value of the latest record
676 * @return :: return detailsObj json that contains recoupedRevenueAmount field and communityBenefitAmount field that seperates to all the savings type
677 */
678async function getRevenueIncreaseAndCommunityBenefits(filterObj, cb) {
679 //this is the object we will return
680 let detailsObj = {
681 recoupedRevenueAmount: {
682 livingExpenses: 0,
683 copay: 0,
684 freeDrug: 0,
685 premiumAssistance: 0,
686 insuranceOptimization: 0,
687 governmentPlan: 0
688 },
689 communityBenefitAmount: {
690 livingExpenses: 0,
691 copay: 0,
692 freeDrug: 0,
693 premiumAssistance: 0,
694 insuranceOptimization: 0,
695 governmentPlan: 0
696 }
697 };
698 let filterApprovedSavingCalculationRecord = [];
699 var approvedSavingCalculationRecord = await ApprovedSavingCalculationRecord
700 .find({ select: ["validationStatus", "currentApplication", "pap", "patient", "savingType", "recoupedRevenueAmount", "communityBenefitAmount", "createdAt", "validationAt"] })
701 .where({ or: [{ validationStatus: 'validated' }, { validationStatus: 'freeze' }] })
702 .populate("currentApplication");
703
704 filterApprovedSavingCalculationRecord = filterByDateBase(approvedSavingCalculationRecord, filterObj, "createdAt");
705
706 filterApprovedSavingCalculationRecord = await filterByFacilityPatientBase(filterApprovedSavingCalculationRecord, filterObj, "patient");
707
708 filterApprovedSavingCalculationRecord = await filterByAssigneePatientBase(filterApprovedSavingCalculationRecord, filterObj, "patient", "patientRelatedData");
709
710 //--------------------------------------- start: QA needs ---------------------------------------//
711 var patientIDsQaMode = await Setting.findOne({
712 key: "QaModeInsights"
713 });
714 if (patientIDsQaMode) {
715 try {
716 patientIDsQaMode = JSON.parse(patientIDsQaMode.value);
717 var filteredApprovedSavingCalculationRecord = filterApprovedSavingCalculationRecord.filter(calcRecord => {
718 if (_.get(calcRecord, 'patient')) {
719 return patientIDsQaMode.some(patientQaID => patientQaID == calcRecord.patient);
720 }
721 return false;
722 })
723 filterApprovedSavingCalculationRecord = filteredApprovedSavingCalculationRecord;
724 } catch (err) {
725 sails.log.info("patientIDsQaMode exist but no value set.");
726 }
727 }
728 //--------------------------------------- end: QA needs ---------------------------------------//
729 filterApprovedSavingCalculationRecord = _.groupBy(filterApprovedSavingCalculationRecord, "pap");
730 //validate that we have relevant paps according the filter options
731 var arrOfKeys = Object.keys(filterApprovedSavingCalculationRecord);
732 if (arrOfKeys.length !== 0) {
733 ApprovedSavingFreezeRecord.find({ pap: arrOfKeys }).sort("createdAt DESC").exec((err, approveFreezeRecords) => {
734 if (err) return err;
735 let latestUpdatedDetail = 0;
736 approveFreezeRecords = _.groupBy(approveFreezeRecords, "pap");
737 arrOfKeys.forEach(function (key) {
738 //we found freeze record
739 if (approveFreezeRecords[key] !== undefined) {
740 latestUpdatedDetail = approveFreezeRecords[key][0];
741 } else {
742 //find the most updated record - from the approved ones
743 latestUpdatedDetail = findLatestRecord(filterApprovedSavingCalculationRecord[key]);
744 }
745 //sum revenue amount
746 if (latestUpdatedDetail.recoupedRevenueAmount !== undefined) {
747 detailsObj.recoupedRevenueAmount[`${latestUpdatedDetail.savingType}`] += latestUpdatedDetail.recoupedRevenueAmount;
748 }
749 //sum community benefit amount
750 if (latestUpdatedDetail.communityBenefitAmount !== undefined) {
751 detailsObj.communityBenefitAmount[`${latestUpdatedDetail.savingType}`] += latestUpdatedDetail.communityBenefitAmount;
752 }
753 });
754 return cb(null, detailsObj);
755 });
756 } else {
757 //no approved saving calculation record
758 return cb(null, detailsObj);
759 }
760
761}
762
763//-------------------------------------- helper methods --------------------------------------//
764
765//-------------------------------------- filter patients -------------------------------------//
766
767/**
768 * @name filterByDateBase
769 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date)
770 * patients - the patients to filter (not only patients)
771 * firstDateField - first field of patient to filter by
772 * secondDateField - second fielt of patient to filter by
773 * context - "Coverage" or default, beacuse we are filter patientdRelatedData
774 * @description :: we are filter all collections by firstField and if not by secondField, the context is for other unique collections
775 * @return :: return array of collection that we filterd
776 */
777function filterByDateBase(patients, filterObj, firstDateField, secondDateField, context) {
778 // filterObj.date = {};
779 // filterObj.date.from = "2018-07-10"
780 // filterObj.date.to = "2019-07-07"
781 let filterPatientsData = [];
782 if (filterObj && filterObj.date && patients.length > 0) {
783 patients.forEach(patientData => {
784 switch (context) {
785 case "Coverage":
786 if (patientData.coverageInfoValues[0] && dateValidator(patientData.coverageInfoValues[0].planStartDate, filterObj) && dateValidator(patientData.coverageInfoValues[0].planEndDate, filterObj)) {
787 filterPatientsData.push(patientData);
788 }
789 break;
790 default:
791 if (patientData[firstDateField] && dateValidator(patientData[firstDateField], filterObj)) {
792 filterPatientsData.push(patientData);
793 } else if (patientData[secondDateField] && dateValidator(patientData[secondDateField], filterObj)) {
794 filterPatientsData.push(patientData);
795 }
796 break;
797 }
798 });
799 } else {
800 //no filter
801 filterPatientsData = patients;
802 }
803 return filterPatientsData;
804}
805/**
806 * @name filterByAssigneePatientBase
807 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date)
808 * patients - the patients to filter
809 * firstAssigneeField - first field of patient to filter by(in future could be second)
810 * context - "patientRelatedData" for topDiagnosis or payerMix or default, for findAllActivePatients
811 * @description :: if context == "patientRelatedData" bring patients
812 * then check if patient.user == filterObj.assignee and patient in patients
813 * @return :: return array of collection that we filterd
814 */
815async function filterByAssigneePatientBase(patients, filterObj, firstAssigneeField, context) {
816 // filterObj.assignee = 61;
817 let filterAssigneePatients = [];
818 if (filterObj && filterObj.assignee && patients.length > 0) {
819 switch (context) {
820 case "patientRelatedData"://topDiagnosis & payerMix
821 let patientsData = patients;
822 var patients = await Patient.find({ select: ['id', 'user', 'createdAt'] }).where({ user: Number(filterObj.assignee) });
823 patients = _.groupBy(patients, "id");
824 _.map(patientsData, function (patientData) {
825 if (patients[patientData[`${firstAssigneeField}`]] && patients[patientData[`${firstAssigneeField}`]][0].user === Number(filterObj.assignee)) {
826 filterAssigneePatients.push(patientData);
827 }
828 });
829 break;
830 default://findAllActive
831 _.map(patients, function (patient) {
832 if (assigneeValidator(patient, filterObj)) {
833 filterAssigneePatients.push(patient);
834 }
835 });
836 break;
837 }
838 } else {
839 filterAssigneePatients = patients;
840 }
841 return filterAssigneePatients;
842}
843
844/**
845 * @name filterByAssigneeApprovedAssistanceBase
846 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date)
847 * assistanceProgram - the assistanceProgram to filter
848 * patientsByJourneys - patients and there related journeys
849 * firstAssigneeField - first field of assistanceProgram to filter by(in future could be second)
850 * @description :: if context == "patientRelatedData" bring patients
851 * then check if assistanceProgram.user == filterObj.assignee and assistanceProgram in assistancePrograms
852 * if we have assistanceProgram with user null value we check in patientsByJourneys by assistanceProgram.pap.journey.id
853 * @return :: return array of collection that we filterd
854 */
855async function filterByAssigneeApprovedAssistanceBase(assistanceProgram, patientsByJourneys, filterObj, firstAssigneeField) {
856 // filterObj.assignee = 8;
857 let filterAssigneeApproved = [];
858 if (filterObj && filterObj.assignee && assistanceProgram.length > 0) {
859 let approvedAssistanceProgramGroupedByUser = _.groupBy(assistanceProgram, firstAssigneeField);
860 Object.keys(approvedAssistanceProgramGroupedByUser).forEach((approvedId) => {
861 if (approvedId !== 'null') {
862 approvedAssistanceProgramGroupedByUser[approvedId].forEach(approved => {
863 if (approved.user == Number(filterObj.assignee)) {
864 filterAssigneeApproved.push(approved);
865 }
866 });
867 }
868 });
869 //null values - check approved.pap.journey.id == filterObj.assignee
870 if (approvedAssistanceProgramGroupedByUser["null"]) {
871 approvedAssistanceProgramGroupedByUser['null'].map(approved => {
872 if (patientsByJourneys[approved.pap.journey.id]) {
873 if (approved.pap.journey.patient.user === Number(filterObj.assignee)) {
874 filterAssigneeApproved.push(approved);
875 }
876 }
877 })
878 }
879 } else { //no filter
880 filterAssigneeApproved = assistanceProgram;
881 }
882 return filterAssigneeApproved;
883}
884/**
885 * @name filterByFacilityPatientBase
886 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date)
887 * patients - the patients to filter
888 * firstFacilityField - first field of patient to filter by(in future could be second)
889 * @description :: find the patients data that the vaue of facility key is equal to facilityMatchingStrings
890 * filter the exists patients with the patients data that we found by patient.id
891 * @return :: return array of collection that we filterd
892 */
893async function filterByFacilityPatientBase(patients, filterObj, firstFacilityField) {
894 try {
895 // filterObj.facilityMatchingStrings = "Facility1";
896 let filterFacilityPatients = [];
897 if (filterObj && filterObj.facilityMatchingStrings && (patients.length > 0)) {
898 let patientsData = await PatientData.find({ key: "facility", value: filterObj.facilityMatchingStrings })
899 .populate("patient", { select: ["id", "patient", "published"] });
900 patientsData = _.groupBy(patientsData, "patient.id");
901 patients.forEach((patientData) => {
902 //firstFacilityField - from where to bring the patient id in patientData
903 if (patientsData[patientData[`${firstFacilityField}`]]) {
904 filterFacilityPatients.push(patientData);
905 }
906 });
907 } else {
908 filterFacilityPatients = patients;
909 }
910 return filterFacilityPatients;
911 } catch (error) {
912 throw error;
913 }
914}
915/**
916 * @name filterByFacilityApproveAssistanceBase
917 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date)
918 * approveAssistnaces - the approveAssistnaces to filter
919 * @description :: find the patients data that the vaue of facility key is equal to facilityMatchingStrings
920 * filter the exists approveAssistnaces with the patients data that we found by approved.pap.journey.patient.id
921 * @return :: return array of collection that we filterd
922 */
923async function filterByFacilityApproveAssistanceBase(approveAssistnaces, filterObj) {
924 // filterObj.facilityMatchingStrings = "cowell"
925 let filterApproveAssistnaces = [];
926 if (filterObj && filterObj.facilityMatchingStrings && (approveAssistnaces.length > 0)) {
927 let patientsData = await PatientData.find({ key: "facility", value: filterObj.facilityMatchingStrings });
928 //iterate the approved assistance
929 approveAssistnaces.forEach(approved => {
930 //if approved related to patient after filter - keep approved assistance
931 let found = patientsData.some((patient) => {
932 return patient.patient === approved.pap.journey.patient.id;
933 });
934 if (found) {
935 filterApproveAssistnaces.push(approved);
936 }
937 });
938 } else {//no filter
939 filterApproveAssistnaces = approveAssistnaces;
940 }
941 return filterApproveAssistnaces;
942}
943
944/**
945 * @name filterPaps
946 * @params :: filterObj object that contains fields to filter by(facilityMatchingString, assignee. date)
947 * paps - the paps to filter
948 * @description :: filter paps by date, facility and assignee
949 * filter the exists approveAssistnaces with the patients data that we found by approved.pap.journey.patient.id
950 * @return :: return array of collection that we filterd
951 */
952async function filterPaps(paps, filterObj) {
953 let filterdPaps = [];
954 // filterObj.date = {};
955 // filterObj.date.from = "2018-07-10"
956 // filterObj.date.to = "2019-07-07"
957 //filter by date:
958 //TODO: NAVOT this can be changed to byDateBase
959 if (filterObj && filterObj.date) {
960 paps.forEach(item => {
961 if (_.get(item, "currentApplication.approvedDate") && dateValidator(item.currentApplication.approvedDate, filterObj)) {
962 filterdPaps.push(item);
963 } else if (_.get(item, "currentApplication.createdAt")) {
964 if (dateValidator(item.currentApplication.createdAt, filterObj)) {
965 filterdPaps.push(item);
966 }
967 }
968 });
969
970 } else {
971 //no filter
972 filterdPaps = paps;
973 }
974 //filter by facility:
975 // filterObj.facilityMatchingStrings = "Facility1"
976 if (filterObj && filterObj.facilityMatchingStrings) {
977 var patientData = await PatientData.find({ key: "facility", value: filterObj.facilityMatchingStrings });
978 var FilteredByFacility = [];
979 patientData = _.groupBy(patientData, "patient");
980 _.map(filterdPaps, function (pap) {
981 if (pap.journey && patientData[pap.journey.patient]) {
982 FilteredByFacility.push(pap);
983 }
984 });
985 filterdPaps = FilteredByFacility;
986 }
987 //filter by assignee
988 // filterObj.assignee = 61;
989 if (filterObj && filterObj.assignee) {
990 var patientsId = _.groupBy(filterdPaps, function (pap) {
991 return pap.journey.patient;
992 });
993 let arrOfPatientId = Object.keys(patientsId);
994 let arrOfFilterPatientsId = arrOfPatientId.filter(id => id !== "null");
995 var journeysByFilterPaps = await Journey.find({ patient: arrOfFilterPatientsId, select: ["id", "patient", "name"] }).populate("patient");
996 journeysByFilterPaps = _.groupBy(journeysByFilterPaps, "id");
997 var FilteredByAssignee = [];
998 //filterdPaps with approve status
999 FilteredByAssignee = _.filter(filterdPaps, function (filterPap) {
1000 //the user that assignee to the patient
1001 if (filterPap.journey && journeysByFilterPaps[filterPap.journey.id][0].patient.user === Number(filterObj.assignee)) {
1002 return filterPap;
1003 } else { //the user that assign to the approved assistance program
1004 if (filterPap.currentApplication && filterPap.currentApplication.user === Number(filterObj.assignee)) {
1005 return filterPap;
1006 }
1007 }
1008 });
1009 filterdPaps = FilteredByAssignee;
1010 }
1011 return filterdPaps;
1012}
1013
1014/**
1015 * @name extractPatientDataFromCalcRecord
1016 * @params :: calculationRecord is data from calculationRecord, journeyData is data from journey
1017 * @description :: this method takes user from journeyData and id,createdAt from calculationRecord and create patient object
1018 * @return :: return patientData json
1019 */
1020function extractPatientDataFromCalcRecord(calculationRecord) {
1021 let patientData = {};
1022 patientData.id = calculationRecord.patient;
1023 patientData.user = calculationRecord.user;
1024 patientData.createdAt = calculationRecord.createdAt;
1025 return patientData;
1026}
1027
1028/**
1029 * @name calcByPriority
1030 * @params :: paps - to calc there priority
1031 * numberOfTop - number of top to return
1032 * @description :: group paps by priority - 1,2 manufacturers / 3 foundations
1033 * then for each saving type and foreach pap of saving extract the providedBy name and calc the fundsMax
1034 * @return :: return dic json with numberOfTop foundations and numberOfTop manufacturers fields
1035 */
1036function calcByPriority(paps, numberOfTop) {
1037 var groupedByPriority = _.groupBy(paps, 'assistanceProgram.priority');
1038 var filteredByPriority = { manufacturers: [], foundations: [] };
1039 var dic = { manufacturers: {}, foundations: {} };
1040 if (groupedByPriority['1']) {
1041 filteredByPriority.manufacturers.push(...groupedByPriority['1'])
1042 }
1043 if (groupedByPriority['2']) {
1044 filteredByPriority.manufacturers.push(...groupedByPriority['2'])
1045 }
1046 if (groupedByPriority['3']) {
1047 filteredByPriority.foundations.push(...groupedByPriority['3']);
1048 }
1049 Object.keys(filteredByPriority).forEach(saving => {
1050 filteredByPriority[saving].forEach(pap => {
1051 var assistanceProgram = _.get(pap, 'assistanceProgram');
1052 var providedBy = _.get(pap, 'assistanceProgram.providedBy');
1053 if (providedBy && !_.get(dic[saving], providedBy)) {
1054 dic[saving][providedBy] = { value: 0 };
1055 }
1056 if (_.get(pap, 'grantTotal')) {
1057 dic[saving][providedBy].value += pap.grantTotal;
1058 } else if (_.get(assistanceProgram, 'fundsMax')) {
1059 dic[saving][providedBy].value += assistanceProgram.fundsMax;
1060 }
1061 })
1062 })
1063 dic.manufacturers = getTop(dic.manufacturers, numberOfTop)
1064 dic.foundations = getTop(dic.foundations, numberOfTop)
1065 return dic;
1066}
1067
1068/**
1069 * @name calcTopDrugs
1070 * @params :: paps - to calc there priority
1071 * topDrugsNumber - number of top to return
1072 * @description :: get the GenericDrug list
1073 * filter pap with pap.associatedDrugEntry.drug field
1074 * forEach drugName calc the sum of grant total
1075 * filter by topDrugsNumber
1076 * @return :: return drugs with topDrugsNumber length
1077 */
1078async function calcTopDrugs(paps, topDrugsNumber) {
1079 let drugs = {};
1080 let drugsIds = [];
1081 //here we start to build the top5Drugs object
1082 paps.forEach(pap => {
1083 if (pap.associatedDrugEntry.drug) {
1084 drugsIds.push(pap.associatedDrugEntry.drug);
1085 }
1086 });
1087 var genericDrugsList = await GenericDrug.find({ id: drugsIds, select: ["id", "name"] });
1088 var groupedGenericDrugsList = _.groupBy(genericDrugsList, "id");
1089
1090 //replace drug id with drug name
1091 paps = paps.map(pap => {
1092 if (pap.associatedDrugEntry.drug) {
1093 drugs[groupedGenericDrugsList[pap.associatedDrugEntry.drug][0].name] = 0;
1094 pap.associatedDrugEntry.drug = groupedGenericDrugsList[pap.associatedDrugEntry.drug][0].name;
1095 }
1096 return pap;
1097 });
1098 let papsGroupByDrugsName = _.groupBy(paps, "associatedDrugEntry.drug");
1099 Object.keys(papsGroupByDrugsName).forEach((drugName) => {
1100 let total = 0;
1101 //calc the sum of grant total
1102 for (let i = 0; i < papsGroupByDrugsName[drugName].length; i++) {
1103 total += papsGroupByDrugsName[drugName][i].grantTotal;
1104 }
1105 if (drugs[drugName] !== undefined) {
1106 drugs[drugName] = total;
1107 }
1108 });
1109 drugs = getTop(drugs, topDrugsNumber);
1110 return drugs;
1111}
1112
1113/**
1114 * @name dateValidator
1115 * @params :: date - the date to check validation of, filterObj - json that contains date.from date.to fields
1116 * @description :: checks if the date is exist and between filterObj.date.from and filterObj.date.to
1117 * @return :: boolean if the date is between filterObj.date.from and filterObj.date.to . else return false
1118 */
1119function dateValidator(date, filterObj) {
1120 return moment(moment(date).format("YYYY-MM-DD")).isBetween(filterObj.date.from, filterObj.date.to, null, []);
1121}
1122
1123
1124/**
1125 * @name assigneeValidator
1126 * @params :: patient - json patient to check validation of, filterObj - json that contains date.from date.to fields
1127 * @description :: checks if patient.user is a number
1128 * @return :: boolean if patient.user is a number. else false
1129 */
1130function assigneeValidator(patient, filterObj) {
1131 return patient.user === Number(filterObj.assignee);
1132}
1133
1134/**
1135 * @name populatePatientsAges
1136 * @params :: patients array of patients
1137 * @description :: 1. get all the patientsData age of the patients in the array
1138 * 2. assign the age of each patient to the relevant patient in the patients array
1139 * @return :: patients array that contains for each patient is age
1140 */
1141async function populatePatientsAges(patients) {
1142 var patientsIds = patients.map(patient => patient.id);
1143 var patientsAges = await PatientData.find({ patient: patientsIds, key: "age", select: ["patient", "value"] })
1144 patientsAges = _.groupBy(patientsAges, "patient");
1145 patients = patients.map(patient => {
1146 if (patientsAges[patient.id]) {
1147 patient.age = patientsAges[patient.id][0].value;
1148 return patient;
1149 }
1150 })
1151 patients = _.filter(patients, function (patient) { if (patient) return patient })
1152 return patients;
1153}
1154
1155
1156/**
1157 * @name sumDrugApprovedAmount
1158 * @params :: potentialSavingWorkFlowActionResults - array of potential with status approved, drugsApprovedAmount - json, approvedPaps - json
1159 * @description :: 1. iterate the keys , each key is pap id
1160 * 2. if length of the current pap is 1 add the value of approvedAmount to the relevant associatedDrugEntry.drug.id of the current pap
1161 * 2.1 else, get the latest updated of the pap and add the value of approvedAmount to the relevant associatedDrugEntry.drug.id of the current pap
1162 * 3. sort drugsApprovedAmount in decending order by value
1163 * @return :: return drugsApprovedAmount json in decending order
1164 */
1165function sumDrugApprovedAmount(potentialSavingWorkFlowActionResults, drugsApprovedAmount, approvedPaps) {
1166 Object.keys(potentialSavingWorkFlowActionResults).forEach(element => {
1167 if (potentialSavingWorkFlowActionResults[element].length === 1) {
1168 drugsApprovedAmount[approvedPaps[element][0].associatedDrugEntry.drug.id].value += potentialSavingWorkFlowActionResults[element][0].approvedAmount;
1169 } else {
1170 const today = new Date();
1171 var latestUpdatedDetail = potentialSavingWorkFlowActionResults[element].reduce((a, b) => a.createdAt - today > b.createdAt - today ? a : b);
1172 drugsApprovedAmount[approvedPaps[element][0].associatedDrugEntry.drug.id].value += potentialSavingWorkFlowActionResults[element][0].approvedAmount;
1173 }
1174 });
1175 return _.sortBy(drugsApprovedAmount, 'value').reverse();
1176}
1177
1178/**
1179 * @name sortPaps
1180 * @params :: paps - array of paps
1181 * @description :: helper method for getSavingStatistics, sort the paps by isFreeDrug, isInsuranceOptimization, isLivingExpenses, isCopayAssistance, isPremiumAssistance
1182 * if one the fields above are true, push the pap to the relevant array(could be one pap in multiple arrays)
1183 * @return :: filterPapObj json that contains array of paps for each field above
1184 */
1185function sortPaps(paps) {
1186 let filterPapObj = {
1187 LivingExpenses: [],
1188 CopayAssistance: [],
1189 FreeDrug: [],
1190 PremiumAssistance: [],
1191 InsuranceOptimization: []
1192 };
1193 paps.forEach(pap => {
1194 if (pap.isFreeDrug) {
1195 filterPapObj.FreeDrug.push(pap);
1196 }
1197 if (pap.isInsuranceOptimization) {
1198 filterPapObj.InsuranceOptimization.push(pap);
1199 }
1200 if (pap.isLivingExpenses) {
1201 filterPapObj.LivingExpenses.push(pap);
1202 }
1203 if (pap.isCopayAssistance) {
1204 filterPapObj.CopayAssistance.push(pap);
1205 }
1206 if (pap.isPremiumAssistance) {
1207 filterPapObj.PremiumAssistance.push(pap);
1208 }
1209 })
1210 return filterPapObj;
1211}
1212
1213/**
1214 * @name findLatestRecord
1215 * @params :: listOfRecords - list of records to iterate over
1216 * @description :: helper for increaseRevenue
1217 * 1. checks if there is more than one record, if not thae the first
1218 * 2. if yes: for each record get the "currentApplication.approvedDate" if not exist get the "currentApplication.createdAt"
1219 * 2.1 for each 2 recordss compare values, if equal get the "validationAt" for buth records
1220 * 3. take the record that his value is the most updated then today
1221 * @return :: the latest record as json (the latest record date from today)
1222 */
1223function findLatestRecord(listOfRecords) {
1224 let latestRecord;
1225 const today = new Date();
1226 if (listOfRecords.length > 1) {
1227 listOfRecords.reduce((a, b) => {
1228 approvedDateA = _.get(a, "currentApplication.approvedDate") ? _.get(a, "currentApplication.approvedDate") : _.get(a, "currentApplication.createdAt");
1229 approvedDateB = _.get(b, "currentApplication.approvedDate") ? _.get(b, "currentApplication.approvedDate") : _.get(b, "currentApplication.createdAt");
1230 //if even, compare by validationAt
1231 if (approvedDateA === approvedDateB) {
1232 approvedDateA = _.get(a, "validationAt") ? _.get(a, "validationAt") : _.get(a, "validationAt");
1233 approvedDateB = _.get(b, "validationAt") ? _.get(b, "validationAt") : _.get(b, "validationAt");
1234 }
1235 latestRecord = approvedDateA - today > approvedDateB - today ? a : b;
1236 });
1237 } else {
1238 //array in size 1, return the first one
1239 latestRecord = listOfRecords[0];
1240 }
1241 return latestRecord;
1242}
1243
1244
1245function approvedAssitanceOverTimeFormat(filterObj, dates, daysBetweenDates) {
1246 var format = 'month';
1247 if (filterObj.date) {
1248 if (daysBetweenDates <= 13) {
1249 format = 'day';
1250 } else if (daysBetweenDates >= 14 && daysBetweenDates <= 30) {
1251 format = 'isoWeek';
1252 } else if (daysBetweenDates >= 31 && daysBetweenDates <= 91) {
1253 if (!filterObj.mode) {
1254 format = 'isoWeek';
1255 } else {
1256 format = filterObj.mode;
1257 }
1258 }
1259 }
1260 var datesGroupedBy = _.groupBy(dates, (result) => {
1261 if (format === 'isoWeek') {
1262 var checkMonthChangedInStartWeek = moment(result['approvedDate'], 'DD/MM/YYYY').startOf(format).format('M');
1263 var checkMonthChangedInEndWeek = moment(result['approvedDate'], 'DD/MM/YYYY').endOf(format).format('M');
1264 if (checkMonthChangedInStartWeek == checkMonthChangedInEndWeek) {
1265 let firstDayInWeek = moment(result['approvedDate'], 'DD/MM/YYYY').startOf(format).format('D');
1266 let endDayInWeek = moment(result['approvedDate'], 'DD/MM/YYYY').endOf(format).format('D');
1267 return firstDayInWeek + ' - ' + endDayInWeek + ' ' + moment(result['approvedDate'], 'DD/MM/YYYY').startOf(format).format('MM/YYYY');
1268 } else {
1269 return moment(result['approvedDate'], 'DD/MM/YYYY').startOf(format).format('MM/DD') + ' - ' + moment(result['approvedDate'], 'MM/DD/YYYY').endOf(format).format('MM/DD YYYY');
1270 }
1271 } else if (format === 'day') {
1272 return moment(result['approvedDate'], 'DD/MM/YYYY').format('MM/DD/YYYY');
1273 } else {
1274 return moment(result['approvedDate'], 'DD/MM/YYYY').startOf(format).format('MM/YYYY');
1275 }
1276
1277 });
1278 var sumApprovedAmount = [];
1279 var amountOverTime = 0;
1280 Object.keys(datesGroupedBy).forEach(date => {
1281 var data = { date: date };
1282 const sum = (arr, key) => _.reduce(arr, (rst, it) => {
1283 return rst + it[key];
1284 }, 0);
1285 data['approvedAmount'] = sum(datesGroupedBy[date], 'approvedAmount');
1286 data['Amount of Approved'] = datesGroupedBy[date].length
1287 sumApprovedAmount.push(data);
1288 })
1289
1290 if (sumApprovedAmount.length > 13) {
1291 sumApprovedAmount = sumApprovedAmount.slice(sumApprovedAmount.length - 13);
1292 }
1293
1294 sumApprovedAmount = sumApprovedAmount.map(obj => {
1295 amountOverTime += obj['approvedAmount'];
1296 obj['Total Approved'] = amountOverTime;
1297 return obj;
1298 })
1299 return sumApprovedAmount;
1300
1301}
1302
1303//-------------------------------------- utils methods --------------------------------------//
1304
1305
1306/**
1307 * @name calclSavingNavigatorApprovedAmount
1308 * @params :: arrOfPaps - array of paps
1309 * @description :: helper method for getSavingStatistics,
1310 * sum the approved amount of paps
1311 * @return :: json that contains totalSum field and totalElements field (the number of elements we sum)
1312 */
1313function calclSavingNavigatorApprovedAmount(arrOfPaps) {
1314 var totalSum = 0;
1315 var totalNumberOfElements = 0;
1316
1317 arrOfPaps.forEach(pap => {
1318 //checks if the field is undefined
1319 if (pap && pap.currentApplication && pap.currentApplication.approvedAmount > 0) {
1320 totalSum += pap.currentApplication.approvedAmount;
1321 totalNumberOfElements++;
1322 } else if (pap && pap.grantTotal > 0) {
1323 totalSum += pap.grantTotal;
1324 totalNumberOfElements++;
1325 } else if (pap && pap.assistanceProgram && pap.assistanceProgram.fundsMax > 0) {
1326 totalSum += pap.assistanceProgram.fundsMax;
1327 totalNumberOfElements++;
1328 }
1329 });
1330
1331 return { totalSum: totalSum, totalElements: totalNumberOfElements };
1332}
1333/**
1334 * @name calclSavingNavigatorTotalAmount
1335 * @params :: arrOfPaps - array of paps
1336 * @description :: helper method for getSavingStatistics,
1337 * sum the total amount of paps
1338 * @return :: json that contains totalSum field and totalElements field (the number of elements we sum)
1339 */
1340function calclSavingNavigatorTotalAmount(arrOfPaps) {
1341 var totalSum = 0;
1342 var totalNumberOfElements = 0;
1343
1344 arrOfPaps.forEach(pap => {
1345 //checks if the field is undefined
1346 if (pap && pap.grantTotal > 0) {
1347 totalSum += pap.grantTotal;
1348 totalNumberOfElements++;
1349 } else if (pap && pap.assistanceProgram && pap.assistanceProgram.fundsMax > 0) {
1350 totalSum += pap.assistanceProgram.fundsMax;
1351 totalNumberOfElements++;
1352 }
1353 });
1354 return { totalSum: totalSum, totalElements: totalNumberOfElements };
1355}
1356
1357//returns the tupNumber of key,values in json
1358function getTop(json, topNumber) {
1359 var arrayObjects = [];
1360
1361 // Push each JSON Object entry in array by [key, value]
1362 for (var i in json) {
1363 json[i] = (typeof json[i] !== "number") ? json[i].value : json[i];
1364 arrayObjects.push({ name: jsUcfirst(i), Amount: json[i] });
1365 }
1366 // sort the array in decending order
1367 arrayObjects.sort((a, b) => parseFloat(a.Amount) - parseFloat(b.Amount));
1368 arrayObjects = arrayObjects.slice(arrayObjects.length - topNumber, arrayObjects.length)
1369 return arrayObjects;
1370}
1371
1372/**
1373 * @name createArrayByField
1374 * @params :: arrOfItems - array, field - the field of each item in arrOfItems that we want to select from the item(for now you can enter only one field level)
1375 * @description :: helper for increaseRevenue, create an array groupBy field , take the keys of the array and removes the null values from the array
1376 * @return :: arrOfKeysWithoutNull - array without null value
1377 */
1378function createArrayByField(arrOfItems, field) {
1379 let arrOfItemsByField = _.groupBy(arrOfItems, function (item) {
1380 return item[`${field}`];
1381 });
1382 let arrOfKeys = Object.keys(arrOfItemsByField);
1383 //remove null items
1384 let arrOfKeysWithoutNull = arrOfKeys.filter(item => item !== "null");
1385 return arrOfKeysWithoutNull;
1386}
1387
1388/**
1389 * @name jsUcfirst
1390 * @params :: string - the string to upper case of the first letter
1391 * @description :: the function upper case the first letter and concat it to the rest of the string
1392 * @return :: string with the first letter uppercase
1393 */
1394function jsUcfirst(string) {
1395 return string.charAt(0).toUpperCase() + string.slice(1);
1396}
1397//upper case each string with length above 3
1398function titualCase(string) {
1399 var words = string.split(' ');
1400 words = words.map(word => { if (word.length > 3) { return jsUcfirst(word) } else { return word } });
1401 return words.join(' ');
1402}
1403
1404function calculateActivePatientAgeDistribution(patientsArr) {
1405 var minAge = 30;
1406 var maxAge = 80;
1407 var data = {
1408 10: { 30: [], 40: [], 50: [], 60: [], 70: [] }
1409 }
1410 var outOfRangeData = {
1411 10: { '29': [], '81': [] },
1412 }
1413 patientsArr.forEach(patient => {
1414 if (patient.age < 30) {
1415 outOfRangeData[10][29].push(patient);
1416 } else if (patient.age > 80) {
1417 outOfRangeData[10][81].push(patient);
1418 } else {
1419 var rangeGapBy10 = Object.keys(data[10]);
1420 for (var index = 0; index < rangeGapBy10.length; index++) {
1421 if ((index + 1) == rangeGapBy10.length) {
1422 data[10][rangeGapBy10[rangeGapBy10.length - 1]].push(patient);
1423 } else if ((patient.age > rangeGapBy10[index]) && (patient.age <= rangeGapBy10[index + 1])) {
1424 data[10][rangeGapBy10[index]].push(patient);
1425 break;
1426 }
1427 }
1428 }
1429 })
1430
1431 var gaps = Object.keys(data[10]);
1432 var dataOrganized = {}
1433 dataOrganized['29'] = outOfRangeData[10][29].length;
1434 dataOrganized['81'] = outOfRangeData[10][81].length;
1435 gaps.forEach((gap, index) => {
1436 if (index === gaps.length - 1) {
1437 dataOrganized[`${Number(gap) + 1}-80`] = data[10][gap].length;
1438 } else {
1439 dataOrganized[`${Number(gap) + 1} - ${gaps[index + 1]}`] = data[10][gap].length;
1440 }
1441 });
1442 return dataOrganized;
1443}
1444
1445module.exports = {
1446 findAllPatientsData,
1447 dateValidator,
1448 assigneeValidator,
1449 findTop5DrugsManufacturesAndFoundations,
1450 sumDrugApprovedAmount,
1451 findApprovedAssistanceOverTime,
1452 getTopDiagnoses,
1453 getPayerMix,
1454 getSavingStatistics,
1455 getRevenueIncreaseAndCommunityBenefits
1456};