Skip to content

Commit 2dd7917

Browse files
author
alex-omophub
committed
Extending mapping method with source_codes option
1 parent e9d2ddc commit 2dd7917

2 files changed

Lines changed: 92 additions & 12 deletions

File tree

R/mappings.R

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,53 @@ MappingsResource <- R6::R6Class(
5353
#' @description
5454
#' Map concepts to a target vocabulary.
5555
#'
56-
#' @param source_concepts Vector of OMOP concept IDs to map.
57-
#' @param target_vocabulary Target vocabulary ID (e.g., "ICD10CM", "SNOMED").
58-
#' @param mapping_type Mapping type (direct, equivalent, broader, narrower).
56+
#' @param target_vocabulary Target vocabulary ID (e.g., "ICD10CM", "SNOMED", "RxNorm").
57+
#' @param source_concepts Vector of OMOP concept IDs to map. Use this OR source_codes, not both.
58+
#' @param source_codes List of vocabulary/code pairs to map. Each element should be a list
59+
#' with `vocabulary_id` and `concept_code`. Use this OR source_concepts, not both.
60+
#' @param mapping_type Mapping type filter (direct, equivalent, broader, narrower).
5961
#' @param include_invalid Include invalid mappings. Default `FALSE`.
6062
#' @param vocab_release Specific vocabulary release version (e.g., "2025.1"). Default `NULL`.
6163
#'
6264
#' @returns Mapping results with summary.
63-
map = function(source_concepts,
64-
target_vocabulary,
65+
map = function(target_vocabulary,
66+
source_concepts = NULL,
67+
source_codes = NULL,
6568
mapping_type = NULL,
6669
include_invalid = FALSE,
6770
vocab_release = NULL) {
68-
checkmate::assert_integerish(source_concepts, min.len = 1)
6971
checkmate::assert_string(target_vocabulary, min.chars = 1)
7072

71-
body <- list(
72-
source_concepts = as.integer(source_concepts),
73-
target_vocabulary = target_vocabulary
74-
)
73+
# Validate: exactly one of source_concepts or source_codes required
74+
has_concepts <- !is.null(source_concepts) && length(source_concepts) > 0
75+
has_codes <- !is.null(source_codes) && length(source_codes) > 0
76+
77+
if (!has_concepts && !has_codes) {
78+
abort_validation("Either source_concepts or source_codes is required")
79+
}
80+
if (has_concepts && has_codes) {
81+
abort_validation("Cannot use both source_concepts and source_codes")
82+
}
83+
84+
body <- list(target_vocabulary = target_vocabulary)
85+
86+
if (has_concepts) {
87+
checkmate::assert_integerish(source_concepts, min.len = 1)
88+
body$source_concepts <- as.integer(source_concepts)
89+
}
90+
91+
if (has_codes) {
92+
checkmate::assert_list(source_codes, min.len = 1)
93+
# Validate each code entry has required fields
94+
for (i in seq_along(source_codes)) {
95+
if (!all(c("vocabulary_id", "concept_code") %in% names(source_codes[[i]]))) {
96+
abort_validation(
97+
sprintf("source_codes[%d] must have 'vocabulary_id' and 'concept_code'", i)
98+
)
99+
}
100+
}
101+
body$source_codes <- source_codes
102+
}
75103

76104
if (!is.null(mapping_type)) {
77105
body$mapping_type <- mapping_type

tests/testthat/test-mappings-integration.R

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,63 @@ test_that("map concepts batch works", {
4242
client <- integration_client()
4343

4444
result <- client$mappings$map(
45-
source_concepts = c(DIABETES_CONCEPT_ID, MI_CONCEPT_ID),
46-
target_vocabulary = "ICD10CM"
45+
target_vocabulary = "ICD10CM",
46+
source_concepts = c(DIABETES_CONCEPT_ID, MI_CONCEPT_ID)
4747
)
4848

4949
# Should get some result structure
5050
mappings <- extract_data(result, "mappings")
5151
expect_true(is.list(mappings))
5252
})
53+
54+
# ==============================================================================
55+
# map() with source_codes integration tests
56+
# ==============================================================================
57+
58+
test_that("map concepts with source_codes works", {
59+
skip_if_no_integration_key()
60+
client <- integration_client()
61+
62+
result <- client$mappings$map(
63+
target_vocabulary = "RxNorm",
64+
source_codes = list(
65+
list(vocabulary_id = "SNOMED", concept_code = "387517004"),
66+
list(vocabulary_id = "SNOMED", concept_code = "108774000")
67+
)
68+
)
69+
70+
mappings <- extract_data(result, "mappings")
71+
expect_true(is.list(mappings))
72+
})
73+
74+
test_that("map concepts with source_codes and mapping_type works", {
75+
skip_if_no_integration_key()
76+
client <- integration_client()
77+
78+
result <- client$mappings$map(
79+
target_vocabulary = "RxNorm",
80+
source_codes = list(
81+
list(vocabulary_id = "SNOMED", concept_code = "387517004")
82+
),
83+
mapping_type = "equivalent"
84+
)
85+
86+
mappings <- extract_data(result, "mappings")
87+
expect_true(is.list(mappings))
88+
})
89+
90+
test_that("map concepts with invalid source_codes returns empty", {
91+
skip_if_no_integration_key()
92+
client <- integration_client()
93+
94+
result <- client$mappings$map(
95+
target_vocabulary = "RxNorm",
96+
source_codes = list(
97+
list(vocabulary_id = "SNOMED", concept_code = "INVALID_CODE_12345")
98+
)
99+
)
100+
101+
mappings <- extract_data(result, "mappings")
102+
expect_true(is.list(mappings))
103+
expect_equal(length(mappings), 0)
104+
})

0 commit comments

Comments
 (0)