From 1c58380cf4214fb100ff11de0a9ff409fdb196d4 Mon Sep 17 00:00:00 2001 From: reagan-meant Date: Tue, 12 Sep 2023 12:33:31 +0300 Subject: [PATCH 1/6] Add endpoint to check potential matches --- server/lib/esMatching.js | 7 +- server/lib/routes/match.js | 190 +++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) diff --git a/server/lib/esMatching.js b/server/lib/esMatching.js index c919603e..d369ad14 100644 --- a/server/lib/esMatching.js +++ b/server/lib/esMatching.js @@ -189,6 +189,7 @@ const buildQuery = (sourceResource, decisionRule) => { const getESDocument = (query, callback) => { let error = false; let documents = []; + let callbackCalled = false; // Flag to track if callback has been called if(!query) { query = {}; } @@ -226,6 +227,7 @@ const getESDocument = (query, callback) => { scroll_id: scroll_id }; } + callbackCalled = true; // Set the flag after calling the callback return callback(null); }).catch((err) => { if(err.response && err.response.status === 429) { @@ -238,7 +240,10 @@ const getESDocument = (query, callback) => { error = err; logger.error(err); scroll_id = null; - return callback(null); + if (!callbackCalled) { + callbackCalled = true; + return callback(null); + } } }); }, diff --git a/server/lib/routes/match.js b/server/lib/routes/match.js index 781e3187..5e400f1f 100644 --- a/server/lib/routes/match.js +++ b/server/lib/routes/match.js @@ -1080,6 +1080,196 @@ router.get(`/count-new-auto-matches`, (req, res) => { return res.status(200).json({total: autoCount.total}); }); }); +router.post('/potential-matches', (req, res) => { + logger.info("Received a request to get potential matches"); + let matchResults = []; + const patientJson = req.body; + + generateScoreMatrix({patient: patientJson, level: 'childMatches'}, () => { + return res.status(200).send(matchResults); + }); + + function matrixExist(sourceID) { + let found = false; + for(let matrix of matchResults) { + if(matrix.source_id === sourceID) { + found = true; + break; + } + if(found) { + break; + } + } + return found; + } + + function generateScoreMatrix({patient, level}, callback) { + let matchingTool; + if (config.get("matching:tool") === "mediator") { + matchingTool = medMatching; + } else if (config.get("matching:tool") === "elasticsearch") { + matchingTool = esMatching; + } + + matchingTool.performMatch({ + sourceResource: patient, + ignoreList: [], + }, ({ + error, + FHIRAutoMatched, + FHIRPotentialMatches, + FHIRConflictsMatches, + ESMatches + }) => { + + let link = patient.link && patient.link.length > 0 ? patient.link[0].other.reference : null; + let goldenLink = null; + + if (link) { + goldenLink = link.split('/')[1]; + } else { + + } + + const validSystem = generalMixin.getClientIdentifier(patient); + let name = patient.name.find((name) => { + return name.use === 'official'; + }); + let given = ''; + if(name && name.given) { + given = name.given.join(' '); + } + let clientUserId; + if (patient.meta && patient.meta.tag) { + for (let tag of patient.meta.tag) { + if ( + tag.system === "http://openclientregistry.org/fhir/clientid" + ) { + clientUserId = tag.code; + } + } + } + let systemName = generalMixin.getClientDisplayName(clientUserId); + let phone; + if(patient.telecom) { + for(let telecom of patient.telecom) { + if(telecom.system === 'phone') { + phone = telecom.value; + } + } + } + + let primaryPatient = { + id: patient.id, + gender: patient.gender, + given, + family: name.family, + birthDate: patient.birthDate, + phone, + uid: goldenLink, + ouid: goldenLink, + source_id: validSystem && validSystem.value ? validSystem.value : null, + source: systemName ? systemName: null, + scores: {} + }; + + if (patient.extension) { + for (let id of patient.extension) { + let propertyName = "extension_" + id.url; + primaryPatient[propertyName]= ( id.valueString ? id.valueString : id.valueDate ); + + } + } + + if(patient.identifier) { + for(let identifier of patient.identifier) { + let propertyName = "identifier_" + identifier.system; + primaryPatient[propertyName]= identifier.value; + } + } + + populateScores(primaryPatient, ESMatches, FHIRPotentialMatches, FHIRAutoMatched, FHIRConflictsMatches); + matchResults.push(primaryPatient); + if(level != 'childMatches' && !config.get('matching:resolvePotentialOfPotentials')) { + return callback(); + } + async.series({ + auto: (callback) => { + async.eachSeries(FHIRAutoMatched.entry, (autoMatched, nxtAutoMatched) => { + const validSystem = generalMixin.getClientIdentifier(autoMatched.resource); + if(matrixExist(validSystem.value)) { + return nxtAutoMatched(); + } + generateScoreMatrix({patient: autoMatched.resource, level: 'grandChildMatches'}, () => { + return nxtAutoMatched(); + }); + }, () => { + return callback(null); + }); + }, + potential: (callback) => { + async.eachSeries(FHIRPotentialMatches.entry, (potentialMatch, nxtPotMatch) => { + const validSystem = generalMixin.getClientIdentifier(potentialMatch.resource); + if(matrixExist(validSystem.value)) { + return nxtPotMatch(); + } + generateScoreMatrix({patient: potentialMatch.resource, level: 'grandChildMatches'}, () => { + return nxtPotMatch(); + }); + }, () => { + return callback(null); + }); + }, + conflicts: (callback) => { + async.eachSeries(FHIRConflictsMatches.entry, (conflictMatch, nxtConflictMatch) => { + const validSystem = generalMixin.getClientIdentifier(conflictMatch.resource); + if(matrixExist(validSystem.value)) { + return nxtConflictMatch(); + } + generateScoreMatrix({patient: conflictMatch.resource, level: 'grandChildMatches'}, () => { + return nxtConflictMatch(); + }); + }, () => { + return callback(null); + }); + } + }, () => { + return callback(); + }); + }); + } + function populateScores(patient, ESMatches, FHIRPotentialMatches, FHIRAutoMatched, FHIRConflictsMatches) { + for(let esmatch of ESMatches) { + for(let autoMatch of esmatch.autoMatchResults) { + let patResource = FHIRAutoMatched.entry.find((entry) => { + return entry.resource.id === autoMatch['_id']; + }); + const validSystem = generalMixin.getClientIdentifier(patResource.resource); + patient.scores[validSystem.value] = autoMatch['_score']; + } + for(let potMatch of esmatch.potentialMatchResults) { + let patResource = FHIRPotentialMatches.entry.find((entry) => { + return entry.resource.id === potMatch['_id']; + }); + if(!patResource) { + continue; + } + const validSystem = generalMixin.getClientIdentifier(patResource.resource); + patient.scores[validSystem.value] = potMatch['_score']; + } + for(let conflMatch of esmatch.conflictsMatchResults) { + let patResource = FHIRConflictsMatches.entry.find((entry) => { + return entry.resource.id === conflMatch['_id']; + }); + if(!patResource) { + continue; + } + const validSystem = generalMixin.getClientIdentifier(patResource.resource); + patient.scores[validSystem.value] = conflMatch['_score']; + } + } + } +}); router.get('/potential-matches/:id', (req, res) => { logger.info("Received a request to get potential matches"); From 19b3ac9cb60523ff07356956416c19e58a273eca Mon Sep 17 00:00:00 2001 From: reagan-meant Date: Fri, 15 Sep 2023 10:34:24 +0300 Subject: [PATCH 2/6] Return arrays of different result types --- server/lib/routes/match.js | 46 ++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/server/lib/routes/match.js b/server/lib/routes/match.js index 5e400f1f..dbf65873 100644 --- a/server/lib/routes/match.js +++ b/server/lib/routes/match.js @@ -1082,28 +1082,36 @@ router.get(`/count-new-auto-matches`, (req, res) => { }); router.post('/potential-matches', (req, res) => { logger.info("Received a request to get potential matches"); - let matchResults = []; + let matchResults = { + parent: [], + auto: [], + potential: [], + conflict: [] + }; const patientJson = req.body; - generateScoreMatrix({patient: patientJson, level: 'childMatches'}, () => { + generateScoreMatrix({patient: patientJson, level: 'childMatches',type: 'parent'}, () => { return res.status(200).send(matchResults); }); function matrixExist(sourceID) { let found = false; - for(let matrix of matchResults) { - if(matrix.source_id === sourceID) { - found = true; - break; + for (let key in matchResults) { + let matrixArray = matchResults[key]; + for (let matrix of matrixArray) { + if (matrix.source_id === sourceID) { + found = true; + break; + } } - if(found) { - break; + if (found) { + break; } - } + } return found; } - function generateScoreMatrix({patient, level}, callback) { + function generateScoreMatrix({patient, level, type}, callback) { let matchingTool; if (config.get("matching:tool") === "mediator") { matchingTool = medMatching; @@ -1189,7 +1197,17 @@ router.post('/potential-matches', (req, res) => { } populateScores(primaryPatient, ESMatches, FHIRPotentialMatches, FHIRAutoMatched, FHIRConflictsMatches); - matchResults.push(primaryPatient); + if (type == 'parent'){ + matchResults.parent.push(primaryPatient); + } else if (type == 'auto') { + matchResults.auto.push(primaryPatient); + } else if (type == 'potential') { + matchResults.potential.push(primaryPatient); + } else if (type == 'conflict') { + matchResults.conflict.push(primaryPatient); + } else { + // Handle cases where 'type' doesn't match any of the known values. + } if(level != 'childMatches' && !config.get('matching:resolvePotentialOfPotentials')) { return callback(); } @@ -1200,7 +1218,7 @@ router.post('/potential-matches', (req, res) => { if(matrixExist(validSystem.value)) { return nxtAutoMatched(); } - generateScoreMatrix({patient: autoMatched.resource, level: 'grandChildMatches'}, () => { + generateScoreMatrix({patient: autoMatched.resource, level: 'grandChildMatches', type: 'auto'}, () => { return nxtAutoMatched(); }); }, () => { @@ -1213,7 +1231,7 @@ router.post('/potential-matches', (req, res) => { if(matrixExist(validSystem.value)) { return nxtPotMatch(); } - generateScoreMatrix({patient: potentialMatch.resource, level: 'grandChildMatches'}, () => { + generateScoreMatrix({patient: potentialMatch.resource, level: 'grandChildMatches', type: 'potential'}, () => { return nxtPotMatch(); }); }, () => { @@ -1226,7 +1244,7 @@ router.post('/potential-matches', (req, res) => { if(matrixExist(validSystem.value)) { return nxtConflictMatch(); } - generateScoreMatrix({patient: conflictMatch.resource, level: 'grandChildMatches'}, () => { + generateScoreMatrix({patient: conflictMatch.resource, level: 'grandChildMatches', type: 'conflict'}, () => { return nxtConflictMatch(); }); }, () => { From 6633a48815fbfe423e8488375eedc40ac7d3bc53 Mon Sep 17 00:00:00 2001 From: diakite aziz Date: Fri, 20 Oct 2023 12:13:54 +0000 Subject: [PATCH 3/6] Rename route --- server/lib/routes/match.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/lib/routes/match.js b/server/lib/routes/match.js index dbf65873..77fefb67 100644 --- a/server/lib/routes/match.js +++ b/server/lib/routes/match.js @@ -1080,8 +1080,9 @@ router.get(`/count-new-auto-matches`, (req, res) => { return res.status(200).json({total: autoCount.total}); }); }); -router.post('/potential-matches', (req, res) => { - logger.info("Received a request to get potential matches"); + +router.post('/general-matches', (req, res) => { + logger.info("Received a request to get all matches"); let matchResults = { parent: [], auto: [], From df00a4fe11fb2363c1175c7f337d7e168dc2b33b Mon Sep 17 00:00:00 2001 From: MAKOBA REAGAN PATRICK Date: Sat, 21 Oct 2023 22:55:49 +0000 Subject: [PATCH 4/6] Minor fixes --- server/lib/esMatching.js | 7 +------ server/lib/routes/match.js | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/server/lib/esMatching.js b/server/lib/esMatching.js index d369ad14..c919603e 100644 --- a/server/lib/esMatching.js +++ b/server/lib/esMatching.js @@ -189,7 +189,6 @@ const buildQuery = (sourceResource, decisionRule) => { const getESDocument = (query, callback) => { let error = false; let documents = []; - let callbackCalled = false; // Flag to track if callback has been called if(!query) { query = {}; } @@ -227,7 +226,6 @@ const getESDocument = (query, callback) => { scroll_id: scroll_id }; } - callbackCalled = true; // Set the flag after calling the callback return callback(null); }).catch((err) => { if(err.response && err.response.status === 429) { @@ -240,10 +238,7 @@ const getESDocument = (query, callback) => { error = err; logger.error(err); scroll_id = null; - if (!callbackCalled) { - callbackCalled = true; - return callback(null); - } + return callback(null); } }); }, diff --git a/server/lib/routes/match.js b/server/lib/routes/match.js index 77fefb67..feef7a00 100644 --- a/server/lib/routes/match.js +++ b/server/lib/routes/match.js @@ -1081,7 +1081,7 @@ router.get(`/count-new-auto-matches`, (req, res) => { }); }); -router.post('/general-matches', (req, res) => { +router.post('/matches', (req, res) => { logger.info("Received a request to get all matches"); let matchResults = { parent: [], From 0093aab70f791212c0ce303b2b4ba0c9b82d05cb Mon Sep 17 00:00:00 2001 From: reagan-meant Date: Sun, 25 Feb 2024 22:48:18 +0300 Subject: [PATCH 5/6] Append all phone numbers --- server/lib/routes/match.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/server/lib/routes/match.js b/server/lib/routes/match.js index feef7a00..0982c3f5 100644 --- a/server/lib/routes/match.js +++ b/server/lib/routes/match.js @@ -1159,11 +1159,14 @@ router.post('/matches', (req, res) => { } } let systemName = generalMixin.getClientDisplayName(clientUserId); - let phone; + let phone = ''; if(patient.telecom) { for(let telecom of patient.telecom) { if(telecom.system === 'phone') { - phone = telecom.value; + if (phone) { + phone += ', '; + } + phone += telecom.value; } } } @@ -1359,11 +1362,14 @@ router.get('/potential-matches/:id', (req, res) => { } } let systemName = generalMixin.getClientDisplayName(clientUserId); - let phone; + let phone = ''; if(patient.telecom) { for(let telecom of patient.telecom) { if(telecom.system === 'phone') { - phone = telecom.value; + if (phone) { + phone += ', '; + } + phone += telecom.value; } } } From 5da6995f758dbc409e2bd83a5e7892b5eb6dc730 Mon Sep 17 00:00:00 2001 From: Piotr Mankowski Date: Thu, 7 Mar 2024 22:09:17 +0000 Subject: [PATCH 6/6] Exclude undefined values for phone --- server/lib/routes/match.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/lib/routes/match.js b/server/lib/routes/match.js index 0982c3f5..5426169e 100644 --- a/server/lib/routes/match.js +++ b/server/lib/routes/match.js @@ -1162,7 +1162,7 @@ router.post('/matches', (req, res) => { let phone = ''; if(patient.telecom) { for(let telecom of patient.telecom) { - if(telecom.system === 'phone') { + if(telecom.system === 'phone' && telecom.value !== '' && telecom.value !== undefined) { if (phone) { phone += ', '; } @@ -1365,7 +1365,7 @@ router.get('/potential-matches/:id', (req, res) => { let phone = ''; if(patient.telecom) { for(let telecom of patient.telecom) { - if(telecom.system === 'phone') { + if(telecom.system === 'phone' && telecom.value !== '' && telecom.value !== undefined) { if (phone) { phone += ', '; }