From c9fbef6ad9e298dcb8df094dc0c55eecc7a479c2 Mon Sep 17 00:00:00 2001 From: napakalas Date: Tue, 20 Jan 2026 16:27:53 +1300 Subject: [PATCH 1/6] Expose node-mappings under pathways (#42). --- mapserver/competency/flatmap.py | 1 + 1 file changed, 1 insertion(+) 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'] From bcc441cf03c5b4a9bf9beb9871bdebe51f470819 Mon Sep 17 00:00:00 2001 From: napakalas Date: Tue, 20 Jan 2026 16:29:16 +1300 Subject: [PATCH 2/6] Add Query 27 to retrieve node mappings from SCKAN to map nodes (#42). --- mapserver/competency/queries.d/query_27.yaml | 144 +++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 mapserver/competency/queries.d/query_27.yaml diff --git a/mapserver/competency/queries.d/query_27.yaml b/mapserver/competency/queries.d/query_27.yaml new file mode 100644 index 0000000..6eb5b8d --- /dev/null +++ b/mapserver/competency/queries.d/query_27.yaml @@ -0,0 +1,144 @@ +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_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, + sl.sckan_label, + COALESCE(nm.source_id, '') AS source_id, + COALESCE(nm.node_id, '') AS node_id, + COALESCE(ml.node_label, '') 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_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 From 6edd11f743c4fec949d7c7b4ff04782a862cb2e8 Mon Sep 17 00:00:00 2001 From: napakalas Date: Tue, 20 Jan 2026 16:32:21 +1300 Subject: [PATCH 3/6] Add tests for Query 27 (#42). --- tests/test_query_27.py | 70 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/test_query_27.py diff --git a/tests/test_query_27.py b/tests/test_query_27.py new file mode 100644 index 0000000..61a8489 --- /dev/null +++ b/tests/test_query_27.py @@ -0,0 +1,70 @@ +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}]} + print('. query:', query) + 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}]} + print('. query:', query) + 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}]} + print('. query:', query) + 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 + } + ) +#=============================================================================== From b2da68941daa83d1284e89e3a693d50bb071cf63 Mon Sep 17 00:00:00 2001 From: napakalas Date: Tue, 20 Jan 2026 17:12:47 +1300 Subject: [PATCH 4/6] Tidy up, remove unnecessary print (#42). --- tests/test_query_27.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_query_27.py b/tests/test_query_27.py index 61a8489..9cf5d8c 100644 --- a/tests/test_query_27.py +++ b/tests/test_query_27.py @@ -25,7 +25,6 @@ def test_human_male_map(): query = {**base_query, 'parameters': base_query['parameters'] + [{'column': 'source_id', 'value': MALE_UUID}]} - print('. query:', query) response = cq_request(query) assert_valid_query_response( @@ -40,7 +39,6 @@ def test_human_male_map(): def test_human_female_map(): query = {**base_query, 'parameters': base_query['parameters'] + [{'column': 'source_id', 'value': FEMALE_UUID}]} - print('. query:', query) response = cq_request(query) assert_valid_query_response( @@ -55,7 +53,6 @@ def test_human_female_map(): def test_human_rat_map(): query = {**base_query, 'parameters': base_query['parameters'] + [{'column': 'source_id', 'value': RAT_UUID}]} - print('. query:', query) response = cq_request(query) assert_valid_query_response( From 119833bd59949e9effa50a2fa625e5f2b6454910 Mon Sep 17 00:00:00 2001 From: napakalas Date: Wed, 21 Jan 2026 10:42:54 +1300 Subject: [PATCH 5/6] Ensure tests always use the latest UUID (#42). --- tests/utility.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) 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( From cfd1e3928d550075154fd441919570d58bc2731b Mon Sep 17 00:00:00 2001 From: napakalas Date: Wed, 21 Jan 2026 16:14:57 +1300 Subject: [PATCH 6/6] Ensure labels start with an uppercase letter (#42). --- mapserver/competency/queries.d/query_27.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mapserver/competency/queries.d/query_27.yaml b/mapserver/competency/queries.d/query_27.yaml index 6eb5b8d..bc517d2 100644 --- a/mapserver/competency/queries.d/query_27.yaml +++ b/mapserver/competency/queries.d/query_27.yaml @@ -41,7 +41,7 @@ queries: SELECT nm.sckan_id, nm.sckan_node_id, - string_agg(pt.label, ', ' ORDER BY seq.pos) AS sckan_label + 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 @@ -93,10 +93,12 @@ queries: nm.sckan_id, nm.path_id, nm.sckan_node_id, - sl.sckan_label, + 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(ml.node_label, '') AS node_label + 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 @@ -130,7 +132,7 @@ queries: - key: sckan_node_id label: SKCAN Node ID type: string - - key: sckan_label + - key: sckan_node_label label: SKCAN Node Label type: string - key: source_id