Skip to content

Commit 4800128

Browse files
author
alex-omophub
committed
Enhance README with detailed examples for bulk search and tibble output
- Updated the README to clarify the output structure for bulk lexical and semantic searches, emphasizing the return types and iteration methods. - Added a new section on tibble output for batch resolution, demonstrating how to use `as_tibble = TRUE` for streamlined data manipulation with `dplyr`. - Included standalone wrapper functions for improved usability in pipe-friendly workflows and clarified the distinction between using the client methods and raw FHIR responses.
1 parent a2f6b69 commit 4800128

1 file changed

Lines changed: 138 additions & 16 deletions

File tree

README.md

Lines changed: 138 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,28 @@ for (s in similar$similar_concepts) {
116116
Search for multiple terms in a single API call:
117117

118118
```r
119-
# Bulk lexical search (up to 50 queries)
119+
# Bulk lexical search (up to 50 queries) - returns a flat list of
120+
# per-query result objects; iterate it directly.
120121
results <- client$search$bulk_basic(list(
121122
list(search_id = "q1", query = "diabetes mellitus"),
122123
list(search_id = "q2", query = "hypertension"),
123124
list(search_id = "q3", query = "aspirin")
124125
), defaults = list(vocabulary_ids = list("SNOMED"), page_size = 5))
125126

126-
for (item in results$results) {
127+
for (item in results) {
127128
cat(sprintf("%s: %d results\n", item$search_id, length(item$results)))
128129
}
129130

130-
# Bulk semantic search (up to 25 queries)
131-
results <- client$search$bulk_semantic(list(
131+
# Bulk semantic search (up to 25 queries) - returns a dict with
132+
# `results`, `total_searches`, `completed_count`, and `total_duration`.
133+
response <- client$search$bulk_semantic(list(
132134
list(search_id = "s1", query = "heart failure treatment options"),
133135
list(search_id = "s2", query = "type 2 diabetes medication")
134136
), defaults = list(threshold = 0.5, page_size = 10))
137+
138+
for (item in response$results) {
139+
cat(sprintf("%s: %d results\n", item$search_id, length(item$results)))
140+
}
135141
```
136142

137143
## FHIR-to-OMOP Resolution
@@ -176,6 +182,105 @@ result$best_match$resolution$source_concept$vocabulary_id
176182
# [1] "SNOMED"
177183
```
178184

185+
### Tibble Output for Batch Resolution
186+
187+
Pass `as_tibble = TRUE` to get a flat [`tibble`](https://tibble.tidyverse.org/) with one row per input coding - ready to pipe into `dplyr` / `tidyr`:
188+
189+
```r
190+
library(dplyr)
191+
192+
tbl <- client$fhir$resolve_batch(
193+
list(
194+
list(system = "http://hl7.org/fhir/sid/icd-10-cm", code = "E11.9"),
195+
list(system = "http://hl7.org/fhir/sid/icd-10-cm", code = "I10"),
196+
list(system = "http://hl7.org/fhir/sid/icd-10-cm", code = "J45.909")
197+
),
198+
as_tibble = TRUE
199+
)
200+
201+
tbl |>
202+
filter(status == "resolved") |>
203+
select(source_code, standard_concept_name, target_table)
204+
#> # A tibble: 3 x 3
205+
#> source_code standard_concept_name target_table
206+
#> <chr> <chr> <chr>
207+
#> 1 E11.9 Type 2 diabetes mellitus condition_occurrence
208+
#> 2 I10 Essential hypertension condition_occurrence
209+
#> 3 J45.909 Asthma condition_occurrence
210+
```
211+
212+
The tibble columns are `source_system`, `source_code`, `source_concept_id`, `source_concept_name`, `standard_concept_id`, `standard_concept_name`, `standard_vocabulary_id`, `domain_id`, `target_table`, `mapping_type`, `similarity_score`, `status`, and `status_detail`. Failed rows stay in-place with `status = "failed"` and the API error text in `status_detail`. The batch summary (`total` / `resolved` / `failed`) is attached as `attr(tbl, "summary")`.
213+
214+
Default `as_tibble = FALSE` still returns the legacy `list(results, summary)` shape.
215+
216+
### Standalone Wrapper Functions
217+
218+
The R6 interface is always available:
219+
220+
```r
221+
client$fhir$resolve(system = "http://snomed.info/sct", code = "44054006")
222+
```
223+
224+
For pipe-friendly workflows, three standalone wrappers forward to the same R6 methods and take the client as their first argument:
225+
226+
```r
227+
# Equivalent to client$fhir$resolve()
228+
client |>
229+
fhir_resolve(
230+
system = "http://snomed.info/sct",
231+
code = "44054006",
232+
resource_type = "Condition"
233+
)
234+
235+
# Tibble-shaped batch in a pipe
236+
tbl <- client |>
237+
fhir_resolve_batch(
238+
codings = list(
239+
list(system = "http://snomed.info/sct", code = "44054006"),
240+
list(system = "http://loinc.org", code = "2339-0")
241+
),
242+
as_tibble = TRUE
243+
)
244+
245+
client |>
246+
fhir_resolve_codeable_concept(
247+
coding = list(
248+
list(system = "http://snomed.info/sct", code = "44054006"),
249+
list(system = "http://hl7.org/fhir/sid/icd-10-cm", code = "E11.9")
250+
),
251+
resource_type = "Condition"
252+
)
253+
```
254+
255+
Both forms are fully supported - pick whichever reads better for the surrounding code.
256+
257+
### FHIR Client Interop
258+
259+
When you need raw FHIR `Parameters` / `Bundle` responses instead of the Concept Resolver envelope, `omophub_fhir_url()` returns the OMOPHub FHIR Terminology Service base URL for a given FHIR version (`"r4"` default, plus `"r4b"`, `"r5"`, `"r6"`). Use it with [`httr2`](https://httr2.r-lib.org/) or [`fhircrackr`](https://cran.r-project.org/package=fhircrackr) to talk directly to OMOPHub's FHIR endpoint.
260+
261+
```r
262+
library(httr2)
263+
264+
# Call CodeSystem/$lookup directly against OMOPHub's FHIR endpoint
265+
resp <- request(omophub_fhir_url()) |>
266+
req_url_path_append("CodeSystem/$lookup") |>
267+
req_url_query(
268+
system = "http://snomed.info/sct",
269+
code = "44054006"
270+
) |>
271+
req_headers(Authorization = paste("Bearer", Sys.getenv("OMOPHUB_API_KEY"))) |>
272+
req_perform()
273+
274+
params <- resp_body_json(resp)
275+
# Raw FHIR Parameters resource with the concept display and designations.
276+
277+
# R5 / R6 endpoints work the same way
278+
omophub_fhir_url("r5")
279+
#> "https://fhir.omophub.com/fhir/r5"
280+
```
281+
282+
**When to use which**: Use `client$fhir$resolve()` (or `fhir_resolve()`) when you want OMOP-enriched answers (standard concept, CDM target table, mapping quality). Use `omophub_fhir_url()` + `httr2` when you need raw FHIR responses for FHIR-native tooling.
283+
179284
## Use Cases
180285

181286
### ETL & Data Pipelines
@@ -207,14 +312,15 @@ standard_id <- validate_and_map("ICD10CM", "E11.9")
207312
Verify codes exist and are valid:
208313

209314
```r
210-
# Check if condition codes are valid
315+
# Check if condition codes are valid. HTTP 404 responses come through
316+
# as httr2's `httr2_http_404` condition class.
211317
condition_codes <- c("E11.9", "I10", "J44.9")
212318

213319
for (code in condition_codes) {
214320
tryCatch({
215321
concept <- client$concepts$get_by_code("ICD10CM", code)
216322
message(sprintf("OK %s: %s", code, concept$concept_name))
217-
}, omophub_api_error = function(e) {
323+
}, httr2_http_404 = function(e) {
218324
message(sprintf("ERROR %s: Invalid code!", code))
219325
})
220326
}
@@ -225,13 +331,15 @@ for (code in condition_codes) {
225331
Explore hierarchies to build comprehensive concept sets:
226332

227333
```r
228-
# Get all descendants for phenotype definition
334+
# Get all descendants for phenotype definition. `descendants()` returns
335+
# `list(data = list(descendants = [...], concept_id, concept_name, ...), meta)`
229336
descendants <- client$hierarchy$descendants(
230337
201826, # Type 2 diabetes mellitus
231338
max_levels = 5
232339
)
233340

234-
concept_set <- sapply(descendants$concepts, function(x) x$concept_id)
341+
descendant_list <- descendants$data$descendants
342+
concept_set <- vapply(descendant_list, function(x) x$concept_id, integer(1))
235343
message(sprintf("Found %d concepts for T2DM phenotype", length(concept_set)))
236344
```
237345

@@ -297,20 +405,32 @@ client <- OMOPHubClient$new(
297405

298406
## Error Handling
299407

408+
HTTP errors are raised as [httr2 condition classes](https://httr2.r-lib.org/reference/req_error.html) (`httr2_http_404`, `httr2_http_401`, `httr2_http_429`, `httr2_http_403`, etc.). Pre-request input-validation errors use the SDK's `omophub_validation_error` class.
409+
300410
```r
301411
tryCatch({
302412
result <- client$concepts$get(999999999)
303-
}, omophub_not_found_error = function(e) {
304-
message("Concept not found: ", conditionMessage(e))
305-
}, omophub_auth_error = function(e) {
306-
message("Check your API key")
307-
}, omophub_rate_limit_error = function(e) {
308-
message("Rate limited, please wait")
309-
}, omophub_api_error = function(e) {
310-
message("API error: ", conditionMessage(e))
413+
}, httr2_http_404 = function(e) {
414+
message("Concept not found: ", conditionMessage(e)[[1]])
415+
}, httr2_http_401 = function(e) {
416+
message("Unauthorized - check your API key")
417+
}, httr2_http_403 = function(e) {
418+
message("Forbidden - API key lacks permission or vocabulary restricted")
419+
}, httr2_http_429 = function(e) {
420+
# The SDK already auto-retries 429 via httr2::req_retry() with
421+
# exponential backoff; handle here only for custom logging.
422+
message("Rate limited (", e$retry_after %||% "?", "s)")
423+
}, httr2_http = function(e) {
424+
# Generic HTTP error fallback (any 4xx/5xx not caught above)
425+
message("HTTP error: ", conditionMessage(e)[[1]])
426+
}, omophub_validation_error = function(e) {
427+
# Pre-request validation (bad concept_id type, empty query, etc.)
428+
message("Validation error: ", conditionMessage(e)[[1]])
311429
})
312430
```
313431

432+
See [`inst/examples/error_handling.R`](inst/examples/error_handling.R) for the full set of patterns including graceful degradation and batch error collection.
433+
314434
## Compared to Alternatives
315435

316436
| Feature | OMOPHub SDK | ATHENA Download | OHDSI WebAPI |
@@ -333,6 +453,8 @@ The package includes comprehensive examples in `inst/examples/`:
333453
| `navigate_hierarchy.R` | Hierarchy navigation - ancestors, descendants |
334454
| `map_between_vocabularies.R` | Cross-vocabulary mapping |
335455
| `error_handling.R` | Error handling patterns |
456+
| `fhir_resolver.R` | FHIR Concept Resolver - single / batch / CodeableConcept, quality, recommendations |
457+
| `fhir_interop.R` | 1.7.0 interop - tibble batch output, standalone wrappers, `omophub_fhir_url()` |
336458

337459
Run an example:
338460

0 commit comments

Comments
 (0)