Repository: Clinical-Genomics/patientMatcher

Issue:
Introduce the possibility to choose the external node to send a request to
Maybe it's interesting for final users. I am aware of at least another MME service which allows users to do that (RD Connect)

Discussion:

Patch:
diff --git a/README.md b/README.md
index 4fb74a3..bb2fa19 100644
--- a/README.md
+++ b/README.md
@@ -197,7 +197,7 @@ curl -X GET \
   -H 'X-Auth-Token: custom_token' \
   localhost:9020/nodes
 ```
-The response will return a list like this : [ { 'id' : node_1_id, 'description' : node1_description}, .. ] or an empty list if the server is not connected to external nodes.
+The response will return a list like this : [ { 'id' : node_1_id, 'description' : node1_description }, .. ] or an empty list if the server is not connected to external nodes.
 
 &nbsp;&nbsp;
 
@@ -228,6 +228,14 @@ curl -X POST \
   -H 'X-Auth-Token: custom_token' \
   localhost:9020/match/external/patient_id
 ```
+It is possible to search for matching patients on a specific node. To do so specify the node id in the request args. Example:
+```bash
+curl -X POST \
+  -H 'X-Auth-Token: custom_token' \
+  localhost:9020/match/external/patient_id?node=specific_node_id
+```
+Read [here](#node_list) how to get a list with the ID of the connected nodes.
+
 &nbsp;&nbsp;
 
 <a name="patient_matches"></a>
diff --git a/patientMatcher/match/handler.py b/patientMatcher/match/handler.py
index 7176e9b..3883429 100644
--- a/patientMatcher/match/handler.py
+++ b/patientMatcher/match/handler.py
@@ -119,17 +119,20 @@ def internal_matcher(database, patient_obj, max_pheno_score, max_geno_score, max
     return internal_match
 
 
-def external_matcher(database, patient):
+def external_matcher(database, patient, node=None):
     """Handles a query patient matching against all connected MME nodes
 
     Args:
         database(pymongo.database.Database)
         patient(dict) : a MME patient entity
+        node(str): id of the node to search in
 
     Returns:
         external_match(dict): a matching object containing a list of results in 'results' field
     """
-
+    query = {}
+    if node:
+        query['_id'] = node
     connected_nodes = list(database['nodes'].find()) #get all connected nodes
     if len(connected_nodes) == 0:
         LOG.error("Could't find any connected MME nodes. Aborting external matching.")
@@ -146,16 +149,18 @@ def external_matcher(database, patient):
         'data' : data, # description of the patient submitted
         'results' : [],
         'errors' : [],
-        'match_type' : 'external'
+        'match_type' : 'external',
+        'searched_nodes' : []
     }
 
-    LOG.info("Matching patient against {} nodes..".format(len(connected_nodes)))
+    LOG.info("Matching patient against {} node(s)..".format(len(connected_nodes)))
     for node in connected_nodes:
 
         server_name = node['_id']
         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))
diff --git a/patientMatcher/server/controllers.py b/patientMatcher/server/controllers.py
index 93dc9fe..1fa8da8 100644
--- a/patientMatcher/server/controllers.py
+++ b/patientMatcher/server/controllers.py
@@ -36,15 +36,14 @@ def patient(database, patient_id):
     return query_patient
 
 
-def match_external(database, query_patient):
+def match_external(database, query_patient, node=None):
     """Trigger an external patient matching for a given patient object"""
     # trigger the matching and save the matching id to variable
-    matching_obj = external_matcher(database, query_patient)
+    matching_obj = external_matcher(database, query_patient, node)
     # save matching object to database
 
     if matching_obj:
         database['matches'].insert_one(matching_obj)
-
     return matching_obj
 
 
diff --git a/patientMatcher/server/views.py b/patientMatcher/server/views.py
index 4a28406..388c57d 100644
--- a/patientMatcher/server/views.py
+++ b/patientMatcher/server/views.py
@@ -143,7 +143,17 @@ def match_external(patient_id):
         resp.status_code = 200
         return resp
 
-    matching_obj = controllers.match_external(current_app.db, query_patient)
+    node = request.args.get('node')
+
+    # if search should be performed on a specific node, make sure node is in database
+    if node and not current_app.db['nodes'].find({'_id':node}).count():
+        LOG.info('ERROR, theres no node with id "{}" in database'.format(request.args['node']))
+        message = 'ERROR. Could not find any connected node with id {} in database'.format(request.args['node'])
+        resp = jsonify(message)
+        resp.status_code = 200
+        return resp
+
+    matching_obj = controllers.match_external(current_app.db, query_patient, node)
 
     if not matching_obj:
         message = "Could not find any other node connected to this MatchMaker server"


Test patch:
diff --git a/tests/server/test_server_responses.py b/tests/server/test_server_responses.py
index 745b1d6..57f1210 100644
--- a/tests/server/test_server_responses.py
+++ b/tests/server/test_server_responses.py
@@ -297,8 +297,16 @@ def test_match_external(test_client, test_node, database, json_patients):
     data = json.loads(response.data)
     assert data == 'Could not find any other node connected to this MatchMaker server'
 
+    # Try to send a request for a match on a node that does not exist
+    response = app.test_client().post(''.join(['/match/external/', inserted_id, '?node=meh']), headers = auth_headers(ok_token))
+    assert response.status_code == 200
+    data = json.loads(response.data)
+    # And check that node not found is in response message
+    assert data == 'ERROR. Could not find any connected node with id meh in database'
+
     # insert a connected node
     add_node(mongo_db=app.db, obj=test_node, is_client=False) # required for external matches
+    # send a request to match patients against all nodes
     response = app.test_client().post(''.join(['/match/external/', inserted_id]), headers = auth_headers(ok_token))
 
     # Response should be valid
@@ -306,6 +314,14 @@ def test_match_external(test_client, test_node, database, json_patients):
     # And a new match should be created in matches collection
     assert database['matches'].find().count() == 1
 
+    # send a request to match patients against the specific existing node:
+    response = app.test_client().post(''.join(['/match/external/', inserted_id, '?node=', test_node['_id']]), headers = auth_headers(ok_token))
+    # Response should be valid
+    assert response.status_code == 200
+
+    # And a new match should be created in matches collection. So total matches are 2
+    assert database['matches'].find().count() == 2
+
 
 def unauth_headers():
     head = {