@@ -43,30 +43,44 @@ def _extract_coding(
4343
4444
4545# Keys the FHIR Concept Resolver accepts on a single coding item. Any
46- # other keys in a dict input (e.g. ``userSelected``, ``extension``,
47- # ``version`` from ``fhir.resources.Coding.model_dump()``) are dropped
48- # so the server never sees FHIR metadata it does not understand.
46+ # other keys in a dict input (e.g. ``extension``, ``version`` from
47+ # ``fhir.resources.Coding.model_dump()``) are dropped so the server never
48+ # sees FHIR metadata it does not understand. FHIR's camelCase
49+ # ``userSelected`` is mapped to the API's snake_case ``user_selected`` in
50+ # :func:`_coding_to_dict`.
4951_ALLOWED_CODING_KEYS : tuple [str , ...] = (
5052 "system" ,
5153 "code" ,
5254 "display" ,
5355 "vocabulary_id" ,
56+ "user_selected" ,
5457)
5558
5659
5760def _coding_to_dict (coding_input : object ) -> dict [str , Any ]:
5861 """Convert any Coding-like input to a wire-format dict.
5962
6063 Preserves only the keys the resolver endpoint understands
61- (:data:`_ALLOWED_CODING_KEYS`). Skips keys whose values are ``None``
62- so the request payload stays tight.
64+ (:data:`_ALLOWED_CODING_KEYS`). FHIR's camelCase ``userSelected`` is
65+ accepted and mapped to ``user_selected``. Skips keys whose values are
66+ ``None`` so the request payload stays tight.
6367 """
6468 if isinstance (coding_input , dict ):
6569 src : dict [str , Any ] = {
6670 key : coding_input .get (key ) for key in _ALLOWED_CODING_KEYS
6771 }
72+ if (
73+ src .get ("user_selected" ) is None
74+ and coding_input .get ("userSelected" ) is not None
75+ ):
76+ src ["user_selected" ] = coding_input .get ("userSelected" )
6877 else :
6978 src = {key : getattr (coding_input , key , None ) for key in _ALLOWED_CODING_KEYS }
79+ if (
80+ src .get ("user_selected" ) is None
81+ and getattr (coding_input , "userSelected" , None ) is not None
82+ ):
83+ src ["user_selected" ] = getattr (coding_input , "userSelected" , None )
7084 return {k : v for k , v in src .items () if v is not None }
7185
7286
@@ -130,6 +144,7 @@ def _build_resolve_body(
130144 include_recommendations : bool = False ,
131145 recommendations_limit : int = 5 ,
132146 include_quality : bool = False ,
147+ on_unmapped : str | None = None ,
133148) -> dict [str , Any ]:
134149 body : dict [str , Any ] = {}
135150 if system is not None :
@@ -147,6 +162,8 @@ def _build_resolve_body(
147162 body ["recommendations_limit" ] = recommendations_limit
148163 if include_quality :
149164 body ["include_quality" ] = True
165+ if on_unmapped is not None :
166+ body ["on_unmapped" ] = on_unmapped
150167 return body
151168
152169
@@ -182,6 +199,7 @@ def resolve(
182199 include_recommendations : bool = False ,
183200 recommendations_limit : int = 5 ,
184201 include_quality : bool = False ,
202+ on_unmapped : str | None = None ,
185203 ) -> FhirResolveResult :
186204 """Resolve a single FHIR Coding to an OMOP standard concept.
187205
@@ -204,6 +222,8 @@ def resolve(
204222 include_recommendations: Include Phoebe recommendations
205223 recommendations_limit: Max recommendations to return (1-20)
206224 include_quality: Include mapping quality signal
225+ on_unmapped: ``"error"`` (default) raises 404 when nothing
226+ resolves; ``"sentinel"`` returns a ``concept_id`` 0 record
207227
208228 Returns:
209229 Resolution result with source concept, standard concept,
@@ -221,6 +241,7 @@ def resolve(
221241 include_recommendations = include_recommendations ,
222242 recommendations_limit = recommendations_limit ,
223243 include_quality = include_quality ,
244+ on_unmapped = on_unmapped ,
224245 )
225246 return self ._request .post ("/fhir/resolve" , json_data = body )
226247
@@ -232,6 +253,7 @@ def resolve_batch(
232253 include_recommendations : bool = False ,
233254 recommendations_limit : int = 5 ,
234255 include_quality : bool = False ,
256+ on_unmapped : str | None = None ,
235257 ) -> FhirBatchResult :
236258 """Batch-resolve up to 100 FHIR Codings.
237259
@@ -260,6 +282,8 @@ def resolve_batch(
260282 body ["recommendations_limit" ] = recommendations_limit
261283 if include_quality :
262284 body ["include_quality" ] = True
285+ if on_unmapped is not None :
286+ body ["on_unmapped" ] = on_unmapped
263287 return self ._request .post ("/fhir/resolve/batch" , json_data = body )
264288
265289 def resolve_codeable_concept (
@@ -271,6 +295,7 @@ def resolve_codeable_concept(
271295 include_recommendations : bool = False ,
272296 recommendations_limit : int = 5 ,
273297 include_quality : bool = False ,
298+ on_unmapped : str | None = None ,
274299 ) -> FhirCodeableConceptResult :
275300 """Resolve a FHIR CodeableConcept with vocabulary preference.
276301
@@ -309,6 +334,8 @@ def resolve_codeable_concept(
309334 body ["recommendations_limit" ] = recommendations_limit
310335 if include_quality :
311336 body ["include_quality" ] = True
337+ if on_unmapped is not None :
338+ body ["on_unmapped" ] = on_unmapped
312339 return self ._request .post ("/fhir/resolve/codeable-concept" , json_data = body )
313340
314341
@@ -333,6 +360,7 @@ async def resolve(
333360 include_recommendations : bool = False ,
334361 recommendations_limit : int = 5 ,
335362 include_quality : bool = False ,
363+ on_unmapped : str | None = None ,
336364 ) -> FhirResolveResult :
337365 """Resolve a single FHIR Coding to an OMOP standard concept.
338366
@@ -350,6 +378,7 @@ async def resolve(
350378 include_recommendations = include_recommendations ,
351379 recommendations_limit = recommendations_limit ,
352380 include_quality = include_quality ,
381+ on_unmapped = on_unmapped ,
353382 )
354383 return await self ._request .post ("/fhir/resolve" , json_data = body )
355384
@@ -361,6 +390,7 @@ async def resolve_batch(
361390 include_recommendations : bool = False ,
362391 recommendations_limit : int = 5 ,
363392 include_quality : bool = False ,
393+ on_unmapped : str | None = None ,
364394 ) -> FhirBatchResult :
365395 """Batch-resolve up to 100 FHIR Codings.
366396
@@ -374,6 +404,8 @@ async def resolve_batch(
374404 body ["recommendations_limit" ] = recommendations_limit
375405 if include_quality :
376406 body ["include_quality" ] = True
407+ if on_unmapped is not None :
408+ body ["on_unmapped" ] = on_unmapped
377409 return await self ._request .post ("/fhir/resolve/batch" , json_data = body )
378410
379411 async def resolve_codeable_concept (
@@ -385,6 +417,7 @@ async def resolve_codeable_concept(
385417 include_recommendations : bool = False ,
386418 recommendations_limit : int = 5 ,
387419 include_quality : bool = False ,
420+ on_unmapped : str | None = None ,
388421 ) -> FhirCodeableConceptResult :
389422 """Resolve a FHIR CodeableConcept with vocabulary preference.
390423
@@ -402,6 +435,8 @@ async def resolve_codeable_concept(
402435 body ["recommendations_limit" ] = recommendations_limit
403436 if include_quality :
404437 body ["include_quality" ] = True
438+ if on_unmapped is not None :
439+ body ["on_unmapped" ] = on_unmapped
405440 return await self ._request .post (
406441 "/fhir/resolve/codeable-concept" , json_data = body
407442 )
0 commit comments