Repository: Clinical-Genomics/patientMatcher

Issue:
Group match results by searched node
Not really a bug here, but it would add more useful info to the match objects.

Match objects saved to database now look like this:
```bash
matches = [
        {    # External match where test_patient is the query and with results
            '_id' : 'match_1',
            'has_matches' : True,
            'data' : {
                'patient' : {
                    'id' : 'P0000079',
                    'contact' : {
                        'href' : 'mailto:test_contact@email.com'
                    }
                }
            },
            **'results' : [
                {'patient' : { 'patient_data' : 'test_stuff'}},
                {'patient' : { 'patient_data2' : 'test_stuff2'}},
            ],**
            **'searched_nodes' : [{ 'id': 'node1_id' , 'label': 'node1_label'}, { 'id': 'node2_id' , 'label': 'node2_label'}]**
            'match_type' : 'external'
        },
       ...
     ]
```

It would be nice to save matches like this instead:

```bash
matches = [
        {    # External match where test_patient is the query and with results
            '_id' : 'match_1',
            'has_matches' : True,
            'data' : {
                'patient' : {
                    'id' : 'P0000079',
                    'contact' : {
                        'href' : 'mailto:test_contact@email.com'
                    }
                }
            },
            **'results' : [ {
                   'searched_node' : {{ 'id': 'node1_id' , 'label': 'node1_label'}},
                   'patients' : [{'patient' : { 'patient_data' : 'test_stuff'}}, {'patient' : { 'patient_data2' : 'test_stuff2'}}]
             },
             {
                   'searched_node' : {{ 'id': 'node2_id' , 'label': 'node2_label'}},
                   'patients' : [{'patient' : { 'patient_data3' : 'test_stuff'}} ]
            },
          ],**# end of results
            'match_type' : 'external'
        },
       ...
     ]
```





Discussion:

Patch:
diff --git a/patientMatcher/match/handler.py b/patientMatcher/match/handler.py
index ca7f629..f87a12a 100644
--- a/patientMatcher/match/handler.py
+++ b/patientMatcher/match/handler.py
@@ -28,7 +28,7 @@ def patient_matches(database, patient_id, type=None, with_results=True):
     query = {
         '$or' : [
             {'data.patient.id' : patient_id }, # collect matches triggered by patient
-            {'results.patient.id' : patient_id} # and matches where patient is among results
+            {'results.patients.patient.id' : patient_id} # and matches where patient is among results
         ]}
     if type:
         query['match_type'] = type
@@ -111,7 +111,10 @@ def internal_matcher(database, patient_obj, max_pheno_score, max_geno_score, max
         'created' : datetime.datetime.now(),
         'has_matches' : has_matches,
         'data' : {'patient' : json_pat}, # description of the patient submitted
-        'results' : sorted_matches[:max_results],
+        'results' : [{
+            'node' : { 'id': 'patientMatcher', 'label': 'patientMatcher server'},
+            'patients' : sorted_matches[:max_results]
+        }],
         'match_type' : 'internal'
     }
 
@@ -150,7 +153,6 @@ def external_matcher(database, patient, node=None):
         'results' : [],
         'errors' : [],
         'match_type' : 'external',
-        'searched_nodes' : []
     }
 
     LOG.info("Matching patient against {} node(s)..".format(len(connected_nodes)))
@@ -160,7 +162,6 @@ def external_matcher(database, patient, node=None):
         node_url = node['matching_url']
         token = node['auth_token']
         request_content_type = node['accepted_content']
-        external_match['searched_nodes'].append( { 'id': node['_id'], 'label' : node['label'] } )
 
         headers = {'Content-Type': request_content_type, 'Accept': 'application/vnd.ga4gh.matchmaker.v1.0+json', "X-Auth-Token": token}
         LOG.info('sending HTTP request to server: "{}"'.format(server_name))
@@ -178,14 +179,26 @@ def external_matcher(database, patient, node=None):
         except Exception as json_exp:
             error = json_exp
             LOG.error('Server returned error:{}'.format(error))
-            external_match['errors'].append(str(type(error)))
+
+            error_obj = {
+                'node' : { 'id': node['_id'], 'label' : node['label'] },
+                'error' : str(error)
+            }
+            external_match['errors'].append(error_obj)
 
         if json_response:
             LOG.info('server returns the following response: {}'.format(json_response))
+
+            result_obj = {
+                'node' : { 'id': node['_id'], 'label' : node['label'] },
+                'patients' : []
+            }
             results = json_response['results']
             if len(results):
                 external_match['has_matches'] = True
                 for result in results:
-                    external_match['results'].append(result)
+                    result_obj['patients'].append(result)
+
+                external_match['results'].append(result_obj)
 
     return external_match
diff --git a/patientMatcher/server/views.py b/patientMatcher/server/views.py
index 8d9c752..7388bee 100644
--- a/patientMatcher/server/views.py
+++ b/patientMatcher/server/views.py
@@ -198,7 +198,7 @@ def match_internal():
     match_obj = internal_matcher(current_app.db, query_patient, max_pheno_score, max_geno_score, max_results)
     # save matching object to database
     current_app.db['matches'].insert_one(match_obj)
-    matches = match_obj['results']
+    matches = match_obj['results'][0]['patients'] #results[0] because there is just one node (internal match)
 
     # if notifications are on and there are matching results
     if current_app.config.get('MAIL_SERVER') and len(matches):
diff --git a/patientMatcher/utils/notify.py b/patientMatcher/utils/notify.py
index 8768082..7576da7 100644
--- a/patientMatcher/utils/notify.py
+++ b/patientMatcher/utils/notify.py
@@ -33,9 +33,8 @@ def notify_match_internal(database, match_obj, admin_email, mail):
         #If patient used for the search is on patientMatcher database, notify querier as well:
         patient_id = match_obj['data']['patient']['id']
         patient_label = match_obj['data']['patient'].get('label')
-        results = match_obj['results']
         recipient = match_obj['data']['patient']['contact']['href'][7:]
-        email_body = active_match_email_body(patient_id, results, patient_label, external_match=False)
+        email_body = active_match_email_body(patient_id, patient_label, external_match=False)
         LOG.info('Sending an internal match notification for query patient with ID:{0}. Patient contact: {1}'.format(patient_id, recipient))
 
         kwargs = dict(subject=email_subject, html=email_body, sender=sender, recipients=[recipient])
@@ -46,9 +45,9 @@ def notify_match_internal(database, match_obj, admin_email, mail):
         except Exception as err:
             LOG.error('An error occurred while sending an internal match notification: {}'.format(err))
 
-
     # Loop over the result patients and notify their contact about the matching with query patient
-    for result in match_obj['results']:
+
+    for result in match_obj['results'][0]['patients']: #this list has only one element since there is only one internal node
 
         patient_id = result['patient']['id']
 
@@ -82,10 +81,9 @@ def notify_match_external(match_obj, admin_email, mail):
     sender = admin_email
     patient_id = match_obj['data']['patient']['id']
     patient_label = match_obj['data']['patient'].get('label')
-    results = match_obj['results']
     recipient = match_obj['data']['patient']['contact']['href'][7:]
     email_subject = 'MatchMaker Exchange: new patient match available.'
-    email_body = active_match_email_body(patient_id, results, patient_label, external_match=True)
+    email_body = active_match_email_body(patient_id, patient_label, external_match=True)
     LOG.info('Sending an external match notification for query patient with ID {0}. Patient contact: {1}'.format(patient_id, recipient))
 
     kwargs = dict(subject=email_subject, html=email_body, sender=sender, recipients=[recipient])
@@ -98,7 +96,7 @@ def notify_match_external(match_obj, admin_email, mail):
 
 
 
-def active_match_email_body(patient_id, results, patient_label=None, external_match=False):
+def active_match_email_body(patient_id, patient_label=None, external_match=False):
     """Returns the body message of the notification email when the patient was used as query patient
 
     Args:
@@ -119,13 +117,13 @@ def active_match_email_body(patient_id, results, patient_label=None, external_ma
         ***This is an automated message, please do not reply to this email.***<br><br>
         <strong>MatchMaker Exchange patient matching notification:</strong><br><br>
         Patient with ID <strong>{0}</strong>, label <strong>{1}</strong> was recently used in a search {2}.
-        This search returned <strong>{3} potential matche(s)</strong>.<br><br>
+        This search returned potential matche(s)</strong>.<br><br>
         For security reasons match results and patient contacts are not disclosed in this email.<br>
         Please contact the service provider or connect to the portal you used to submit the data to review these results.
         <br><br>
         Kind regards,<br>
         The PatienMatcher team
-    """.format(patient_id, patient_label, search_type, len(results))
+    """.format(patient_id, patient_label, search_type)
 
     return html
 


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index ed372dc..f01b0f4 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -87,8 +87,19 @@ def match_objs():
                 }
             },
             'results' : [
-                {'patient' : { 'patient_data' : 'test_stuff'}},
-                {'patient' : { 'patient_data2' : 'test_stuff2'}},
+                {
+                    'node' : {'id' : 'test_node1', 'label': 'Test Node 1'},
+                    'patients' : [
+                        {'patient' : { 'patient_data1' : 'test_stuff1'}},
+                        {'patient' : { 'patient_data2' : 'test_stuff2'}}
+                    ]
+                },
+                {
+                    'node' : {'id' : 'test_node2', 'label': 'Test Node 2'},
+                    'patients' : [
+                        {'patient' : { 'patient_data3' : 'test_stuff3'}}
+                    ]
+                }
             ],
             'match_type' : 'external'
         },
@@ -103,7 +114,14 @@ def match_objs():
                     'href' : 'mailto:test_contact@email.com'
                 }
             },
-            'results' : [],
+            'results' : [
+                {
+                    'node': {'id' : 'patientMatcher', 'label' : 'patientMatcher server'},
+                    'patients' : [
+                        {'patient' : { 'int_pat1' : 'test_stuff'}}
+                    ]
+                }
+            ],
             'match_type' : 'internal'
         },
         {    #  Internal match where test_patient is among results
@@ -118,12 +136,17 @@ def match_objs():
                 }
             },
             'results' : [
-                {'patient' : {
-                    'id' : 'P0000079',
-                    'contact' : {
-                        'href' : 'mailto:test_contact2@email.com'
-                    }
-                }},
+                {
+                    'node' : {'id' : 'test_node1', 'label': 'Test Node 1'},
+                    'patients' : [
+                        {'patient' : {
+                            'id' : 'P0000079',
+                            'contact' : {
+                                'href' : 'mailto:test_contact2@email.com'
+                            }
+                        }}
+                    ]
+                }
             ],
             'match_type' : 'internal'
         },
diff --git a/tests/match/test_matching_handler.py b/tests/match/test_matching_handler.py
index 1c06073..e5d307e 100644
--- a/tests/match/test_matching_handler.py
+++ b/tests/match/test_matching_handler.py
@@ -18,7 +18,7 @@ def test_internal_matching(demo_data_path, database, json_patients):
     assert a_patient
 
     match_obj = dbmatcher(database, a_patient, 0.5, 0.5)
-    matches = match_obj['results']
+    matches = match_obj['results'][0]['patients']
     assert len(matches) > 0
 
     higest_scored_patient = matches[0]