@@ -116,22 +116,28 @@ for (s in similar$similar_concepts) {
116116Search 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.
120121results <- 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")
207312Verify 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.
211317condition_codes <- c(" E11.9" , " I10" , " J44.9" )
212318
213319for (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) {
225331Explore 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)`
229336descendants <- 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 ))
235343message(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
301411tryCatch({
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
337459Run an example:
338460
0 commit comments