diff --git a/mapserver/competency/flatmap.py b/mapserver/competency/flatmap.py index b2fa7e5..98b296e 100644 --- a/mapserver/competency/flatmap.py +++ b/mapserver/competency/flatmap.py @@ -109,6 +109,7 @@ def anatomical_map_knowledge(map_uuid: str, competency_db: CompetencyKnowledge) 'nerves': path_knowledge.get('node-nerves', []), 'phenotypes': path_phenotypes.get(path_id, []), 'references': path_evidence.get(path_id, []), + 'node-mappings': path_knowledge.get('node-mappings', []), } if 'alert' in properties: knowledge_terms[path_id]['alert'] = properties['alert'] diff --git a/mapserver/competency/queries.d/query_27.yaml b/mapserver/competency/queries.d/query_27.yaml new file mode 100644 index 0000000..bc517d2 --- /dev/null +++ b/mapserver/competency/queries.d/query_27.yaml @@ -0,0 +1,146 @@ +queries: + - id: 27 + label: Node mapping from sckan to map + sql: > + WITH node_mappings AS ( + SELECT DISTINCT + sckan.sckan_id, + sckan.path_id, + sckan.sckan_node_id, + map.source_id, + map.node_id + FROM ( + SELECT + source_id AS sckan_id, + path_id, + node_id AS sckan_node_id + FROM path_nodes + WHERE source_id IN ( + SELECT sckan_id + FROM path_node_mappings + WHERE %CONDITION_1% + ) + AND %CONDITION_0% + ) AS sckan + LEFT JOIN ( + SELECT + source_id, + path_id, + node_id, + sckan_id, + sckan_node_id + FROM path_node_mappings + WHERE %CONDITION_1% + AND %CONDITION_0% + ) AS map + ON sckan.path_id = map.path_id + AND sckan.sckan_node_id = map.sckan_node_id + ), + + sckan_labels AS ( + SELECT + nm.sckan_id, + nm.sckan_node_id, + string_agg(pt.label, ', ' ORDER BY seq.pos) AS sckan_node_label + FROM node_mappings nm + JOIN path_node_features pnf + ON nm.sckan_id = pnf.source_id + AND nm.path_id = pnf.path_id + AND nm.sckan_node_id = pnf.node_id + JOIN feature_terms pt + ON pnf.source_id = pt.source_id + AND pnf.feature_id = pt.term_id + CROSS JOIN LATERAL ( + SELECT pos + FROM ( + SELECT pnf.node_id::jsonb->>0 AS value, 1 AS pos + UNION ALL + SELECT value, ordinality + 1 AS pos + FROM jsonb_array_elements_text(pnf.node_id::jsonb->1) WITH ORDINALITY + ) s + WHERE s.value = pt.term_id + ) AS seq + GROUP BY nm.sckan_id, nm.sckan_node_id + ), + + map_labels AS ( + SELECT + nm.source_id, + nm.node_id, + string_agg(pt.label, ', ' ORDER BY seq.pos) AS node_label + FROM node_mappings nm + JOIN path_node_features pnf + ON nm.source_id = pnf.source_id + AND nm.path_id = pnf.path_id + AND nm.node_id = pnf.node_id + JOIN feature_terms pt + ON pnf.source_id = pt.source_id + AND pnf.feature_id = pt.term_id + CROSS JOIN LATERAL ( + SELECT pos + FROM ( + SELECT pnf.node_id::jsonb->>0 AS value, 1 AS pos + UNION ALL + SELECT value, ordinality + 1 AS pos + FROM jsonb_array_elements_text(pnf.node_id::jsonb->1) WITH ORDINALITY + ) s + WHERE s.value = pt.term_id + ) AS seq + GROUP BY nm.source_id, nm.node_id + ) + + SELECT + nm.sckan_id, + nm.path_id, + nm.sckan_node_id, + UPPER(SUBSTRING(sl.sckan_node_label, 1, 1)) || + SUBSTRING(sl.sckan_node_label, 2) AS sckan_node_label, + COALESCE(nm.source_id, '') AS source_id, + COALESCE(nm.node_id, '') AS node_id, + COALESCE(UPPER(SUBSTRING(ml.node_label, 1, 1)) || + SUBSTRING(ml.node_label, 2), '') AS node_label + FROM node_mappings nm + NATURAL JOIN sckan_labels sl + LEFT JOIN map_labels ml + ON nm.source_id = ml.source_id + AND nm.node_id = ml.node_id; + parameters: + - id: path_id + column: path_id + label: Neuron population + type: string + multiple: true + condition: CONDITION_0 + - id: source_id + column: source_id + label: Knowledge source + type: string + multiple: true + condition: CONDITION_1 + default_msg: the latest source is used + default_sql: > + select source_id from knowledge_sources where source_id like 'sckan%' + order by source_id desc limit 1 + order: '' + results: + - key: sckan_id + label: SKCAN Knowledge source + type: string + - key: path_id + label: Neuron population + type: string + - key: sckan_node_id + label: SKCAN Node ID + type: string + - key: sckan_node_label + label: SKCAN Node Label + type: string + - key: source_id + label: Knowledge source + type: string + - key: node_id + label: Map Node ID + type: string + - key: node_label + label: Map Node Label + type: string diff --git a/tests/test_query_27.py b/tests/test_query_27.py new file mode 100644 index 0000000..9cf5d8c --- /dev/null +++ b/tests/test_query_27.py @@ -0,0 +1,67 @@ +import pytest +from utility import cq_request, assert_valid_query_response, MALE_UUID, FEMALE_UUID, RAT_UUID + +base_query = { + 'query_id': '27', + 'parameters': [ + {'column': 'path_id','value': 'ilxtr:neuron-type-bolew-unbranched-15'} + ] +} + +expected_sckan_node_ids = [ + '["ILX:0738293", []]', + '["ILX:0738305", ["UBERON:0001532"]]', + '["ILX:0793621", []]', + '["UBERON:0001989", []]', + '["UBERON:0003708", []]' +] +expected_node_ids = [ + '["ILX:0738293", []]', + '["ILX:0738305", []]', + '["ILX:0793621", []]', + '["UBERON:0001989", []]', + '["UBERON:0003708", []]' +] + +def test_human_male_map(): + query = {**base_query, 'parameters': base_query['parameters'] + [{'column': 'source_id', 'value': MALE_UUID}]} + response = cq_request(query) + + assert_valid_query_response( + response, + expected_num_keys=7, + expected_num_values=5, + expected_column_values={ + 'sckan_node_id': expected_sckan_node_ids, + 'node_id': expected_node_ids + } + ) + +def test_human_female_map(): + query = {**base_query, 'parameters': base_query['parameters'] + [{'column': 'source_id', 'value': FEMALE_UUID}]} + response = cq_request(query) + + assert_valid_query_response( + response, + expected_num_keys=7, + expected_num_values=5, + expected_column_values={ + 'sckan_node_id': expected_sckan_node_ids, + 'node_id': expected_node_ids + } + ) + +def test_human_rat_map(): + query = {**base_query, 'parameters': base_query['parameters'] + [{'column': 'source_id', 'value': RAT_UUID}]} + response = cq_request(query) + + assert_valid_query_response( + response, + expected_num_keys=7, + expected_num_values=5, + expected_column_values={ + 'sckan_node_id': expected_sckan_node_ids, + 'node_id': expected_node_ids + } + ) +#=============================================================================== diff --git a/tests/utility.py b/tests/utility.py index 60b2e4a..1ed3186 100644 --- a/tests/utility.py +++ b/tests/utility.py @@ -1,15 +1,28 @@ import requests +from tools.portal_maps import ENDPOINTS, latest_maps -END_POINT = 'https://mapcore-demo.org/devel/flatmap/v4/competency/query' +END_POINT = ENDPOINTS['development'] +CQ_END_POINT = f'{END_POINT}/competency/query' HEADERS = {'Content-Type': 'application/json'} -MALE_UUID = '2b76d336-5c56-55e3-ab1e-795d6c63f9c1' -FEMALE_UUID = '91359a0f-9e32-5309-b365-145d9956817d' -RAT_UUID = 'fb6d0345-cb70-5c7e-893c-d744a6313c95' +# latest flatmap UUIDs for testing obtained dynamically +maps = latest_maps(END_POINT) +MALE_UUID = ( + maps.get(('NCBITaxon:9606', 'PATO:0000384'), {}).get('uuid') + or "2b76d336-5c56-55e3-ab1e-795d6c63f9c1" +) +FEMALE_UUID = ( + maps.get(('NCBITaxon:9606', 'PATO:0000383'), {}).get('uuid') + or "91359a0f-9e32-5309-b365-145d9956817d" +) +RAT_UUID = ( + maps.get(('NCBITaxon:10114', None), {}).get('uuid') + or "fb6d0345-cb70-5c7e-893c-d744a6313c95" +) SCKAN_VERSION = 'sckan-2024-09-21' def cq_request(query: dict): - response = requests.post(END_POINT, json=query, headers=HEADERS) + response = requests.post(CQ_END_POINT, json=query, headers=HEADERS) return response def assert_valid_query_response(