phenoml 0.0.19__py3-none-any.whl → 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ from json.decoder import JSONDecodeError
6
6
  from ..core.api_error import ApiError
7
7
  from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
8
8
  from ..core.http_response import AsyncHttpResponse, HttpResponse
9
+ from ..core.jsonable_encoder import jsonable_encoder
9
10
  from ..core.pydantic_utilities import parse_obj_as
10
11
  from ..core.request_options import RequestOptions
11
12
  from ..core.serialization import convert_and_respect_annotation_metadata
@@ -13,11 +14,19 @@ from .errors.bad_request_error import BadRequestError
13
14
  from .errors.conflict_error import ConflictError
14
15
  from .errors.failed_dependency_error import FailedDependencyError
15
16
  from .errors.internal_server_error import InternalServerError
17
+ from .errors.not_found_error import NotFoundError
18
+ from .errors.not_implemented_error import NotImplementedError
19
+ from .errors.service_unavailable_error import ServiceUnavailableError
16
20
  from .errors.unauthorized_error import UnauthorizedError
17
21
  from .types.construe_upload_code_system_response import ConstrueUploadCodeSystemResponse
18
22
  from .types.extract_codes_result import ExtractCodesResult
19
23
  from .types.extract_request_config import ExtractRequestConfig
20
24
  from .types.extract_request_system import ExtractRequestSystem
25
+ from .types.get_code_response import GetCodeResponse
26
+ from .types.list_code_systems_response import ListCodeSystemsResponse
27
+ from .types.list_codes_response import ListCodesResponse
28
+ from .types.semantic_search_response import SemanticSearchResponse
29
+ from .types.text_search_response import TextSearchResponse
21
30
  from .types.upload_request_format import UploadRequestFormat
22
31
 
23
32
  # this is used as the default value for optional parameters
@@ -274,88 +283,882 @@ class RawConstrueClient:
274
283
  raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
275
284
  raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
276
285
 
286
+ def list_available_code_systems(
287
+ self, *, request_options: typing.Optional[RequestOptions] = None
288
+ ) -> HttpResponse[ListCodeSystemsResponse]:
289
+ """
290
+ Returns metadata about all available code systems including built-in and custom systems.
291
+
292
+ Parameters
293
+ ----------
294
+ request_options : typing.Optional[RequestOptions]
295
+ Request-specific configuration.
296
+
297
+ Returns
298
+ -------
299
+ HttpResponse[ListCodeSystemsResponse]
300
+ List of available code systems
301
+ """
302
+ _response = self._client_wrapper.httpx_client.request(
303
+ "construe/codes/systems",
304
+ method="GET",
305
+ request_options=request_options,
306
+ )
307
+ try:
308
+ if 200 <= _response.status_code < 300:
309
+ _data = typing.cast(
310
+ ListCodeSystemsResponse,
311
+ parse_obj_as(
312
+ type_=ListCodeSystemsResponse, # type: ignore
313
+ object_=_response.json(),
314
+ ),
315
+ )
316
+ return HttpResponse(response=_response, data=_data)
317
+ if _response.status_code == 401:
318
+ raise UnauthorizedError(
319
+ headers=dict(_response.headers),
320
+ body=typing.cast(
321
+ typing.Optional[typing.Any],
322
+ parse_obj_as(
323
+ type_=typing.Optional[typing.Any], # type: ignore
324
+ object_=_response.json(),
325
+ ),
326
+ ),
327
+ )
328
+ if _response.status_code == 500:
329
+ raise InternalServerError(
330
+ headers=dict(_response.headers),
331
+ body=typing.cast(
332
+ typing.Optional[typing.Any],
333
+ parse_obj_as(
334
+ type_=typing.Optional[typing.Any], # type: ignore
335
+ object_=_response.json(),
336
+ ),
337
+ ),
338
+ )
339
+ _response_json = _response.json()
340
+ except JSONDecodeError:
341
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
342
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
343
+
344
+ def list_codes_in_a_code_system(
345
+ self,
346
+ codesystem: str,
347
+ *,
348
+ version: typing.Optional[str] = None,
349
+ cursor: typing.Optional[str] = None,
350
+ limit: typing.Optional[int] = None,
351
+ request_options: typing.Optional[RequestOptions] = None,
352
+ ) -> HttpResponse[ListCodesResponse]:
353
+ """
354
+ Returns a paginated list of all codes in the specified code system.
355
+
356
+ Parameters
357
+ ----------
358
+ codesystem : str
359
+ Code system name (e.g., "ICD-10-CM", "SNOMED_CT_US_LITE")
360
+
361
+ version : typing.Optional[str]
362
+ Specific version of the code system. Required if multiple versions exist.
363
+
364
+ cursor : typing.Optional[str]
365
+ Pagination cursor from previous response
366
+
367
+ limit : typing.Optional[int]
368
+ Maximum number of codes to return (default 20)
369
+
370
+ request_options : typing.Optional[RequestOptions]
371
+ Request-specific configuration.
372
+
373
+ Returns
374
+ -------
375
+ HttpResponse[ListCodesResponse]
376
+ Paginated list of codes
377
+ """
378
+ _response = self._client_wrapper.httpx_client.request(
379
+ f"construe/codes/{jsonable_encoder(codesystem)}",
380
+ method="GET",
381
+ params={
382
+ "version": version,
383
+ "cursor": cursor,
384
+ "limit": limit,
385
+ },
386
+ request_options=request_options,
387
+ )
388
+ try:
389
+ if 200 <= _response.status_code < 300:
390
+ _data = typing.cast(
391
+ ListCodesResponse,
392
+ parse_obj_as(
393
+ type_=ListCodesResponse, # type: ignore
394
+ object_=_response.json(),
395
+ ),
396
+ )
397
+ return HttpResponse(response=_response, data=_data)
398
+ if _response.status_code == 400:
399
+ raise BadRequestError(
400
+ headers=dict(_response.headers),
401
+ body=typing.cast(
402
+ typing.Optional[typing.Any],
403
+ parse_obj_as(
404
+ type_=typing.Optional[typing.Any], # type: ignore
405
+ object_=_response.json(),
406
+ ),
407
+ ),
408
+ )
409
+ if _response.status_code == 401:
410
+ raise UnauthorizedError(
411
+ headers=dict(_response.headers),
412
+ body=typing.cast(
413
+ typing.Optional[typing.Any],
414
+ parse_obj_as(
415
+ type_=typing.Optional[typing.Any], # type: ignore
416
+ object_=_response.json(),
417
+ ),
418
+ ),
419
+ )
420
+ if _response.status_code == 404:
421
+ raise NotFoundError(
422
+ headers=dict(_response.headers),
423
+ body=typing.cast(
424
+ typing.Optional[typing.Any],
425
+ parse_obj_as(
426
+ type_=typing.Optional[typing.Any], # type: ignore
427
+ object_=_response.json(),
428
+ ),
429
+ ),
430
+ )
431
+ if _response.status_code == 500:
432
+ raise InternalServerError(
433
+ headers=dict(_response.headers),
434
+ body=typing.cast(
435
+ typing.Optional[typing.Any],
436
+ parse_obj_as(
437
+ type_=typing.Optional[typing.Any], # type: ignore
438
+ object_=_response.json(),
439
+ ),
440
+ ),
441
+ )
442
+ _response_json = _response.json()
443
+ except JSONDecodeError:
444
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
445
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
446
+
447
+ def get_a_specific_code(
448
+ self,
449
+ codesystem: str,
450
+ code_id: str,
451
+ *,
452
+ version: typing.Optional[str] = None,
453
+ request_options: typing.Optional[RequestOptions] = None,
454
+ ) -> HttpResponse[GetCodeResponse]:
455
+ """
456
+ Returns details for a specific code within a code system.
457
+
458
+ Parameters
459
+ ----------
460
+ codesystem : str
461
+ Code system name
462
+
463
+ code_id : str
464
+ The code identifier
465
+
466
+ version : typing.Optional[str]
467
+ Specific version of the code system
468
+
469
+ request_options : typing.Optional[RequestOptions]
470
+ Request-specific configuration.
471
+
472
+ Returns
473
+ -------
474
+ HttpResponse[GetCodeResponse]
475
+ Code details
476
+ """
477
+ _response = self._client_wrapper.httpx_client.request(
478
+ f"construe/codes/{jsonable_encoder(codesystem)}/{jsonable_encoder(code_id)}",
479
+ method="GET",
480
+ params={
481
+ "version": version,
482
+ },
483
+ request_options=request_options,
484
+ )
485
+ try:
486
+ if 200 <= _response.status_code < 300:
487
+ _data = typing.cast(
488
+ GetCodeResponse,
489
+ parse_obj_as(
490
+ type_=GetCodeResponse, # type: ignore
491
+ object_=_response.json(),
492
+ ),
493
+ )
494
+ return HttpResponse(response=_response, data=_data)
495
+ if _response.status_code == 400:
496
+ raise BadRequestError(
497
+ headers=dict(_response.headers),
498
+ body=typing.cast(
499
+ typing.Optional[typing.Any],
500
+ parse_obj_as(
501
+ type_=typing.Optional[typing.Any], # type: ignore
502
+ object_=_response.json(),
503
+ ),
504
+ ),
505
+ )
506
+ if _response.status_code == 401:
507
+ raise UnauthorizedError(
508
+ headers=dict(_response.headers),
509
+ body=typing.cast(
510
+ typing.Optional[typing.Any],
511
+ parse_obj_as(
512
+ type_=typing.Optional[typing.Any], # type: ignore
513
+ object_=_response.json(),
514
+ ),
515
+ ),
516
+ )
517
+ if _response.status_code == 404:
518
+ raise NotFoundError(
519
+ headers=dict(_response.headers),
520
+ body=typing.cast(
521
+ typing.Optional[typing.Any],
522
+ parse_obj_as(
523
+ type_=typing.Optional[typing.Any], # type: ignore
524
+ object_=_response.json(),
525
+ ),
526
+ ),
527
+ )
528
+ if _response.status_code == 500:
529
+ raise InternalServerError(
530
+ headers=dict(_response.headers),
531
+ body=typing.cast(
532
+ typing.Optional[typing.Any],
533
+ parse_obj_as(
534
+ type_=typing.Optional[typing.Any], # type: ignore
535
+ object_=_response.json(),
536
+ ),
537
+ ),
538
+ )
539
+ _response_json = _response.json()
540
+ except JSONDecodeError:
541
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
542
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
543
+
544
+ def semantic_search_embedding_based(
545
+ self,
546
+ codesystem: str,
547
+ *,
548
+ text: str,
549
+ version: typing.Optional[str] = None,
550
+ limit: typing.Optional[int] = None,
551
+ request_options: typing.Optional[RequestOptions] = None,
552
+ ) -> HttpResponse[SemanticSearchResponse]:
553
+ """
554
+ Performs semantic similarity search using vector embeddings.
555
+
556
+ **When to use**: Best for natural language queries where you want to find conceptually
557
+ related codes, even when different terminology is used. The search understands meaning,
558
+ not just keywords.
559
+
560
+ **Examples**:
561
+ - Query "trouble breathing at night" finds codes like "Sleep apnea", "Orthopnea",
562
+ "Nocturnal dyspnea" — semantically related but no exact keyword matches
563
+ - Query "heart problems" finds "Myocardial infarction", "Cardiac arrest", "Arrhythmia"
564
+
565
+ **Trade-offs**: Slower than text search (requires embedding generation), but finds
566
+ conceptually similar results that keyword search would miss.
567
+
568
+ See also: `/search/text` for faster keyword-based lookup with typo tolerance.
569
+
570
+ Parameters
571
+ ----------
572
+ codesystem : str
573
+ Code system name
574
+
575
+ text : str
576
+ Natural language text to find semantically similar codes for
577
+
578
+ version : typing.Optional[str]
579
+ Specific version of the code system
580
+
581
+ limit : typing.Optional[int]
582
+ Maximum number of results (default 10, max 50)
583
+
584
+ request_options : typing.Optional[RequestOptions]
585
+ Request-specific configuration.
586
+
587
+ Returns
588
+ -------
589
+ HttpResponse[SemanticSearchResponse]
590
+ Semantic search results ordered by similarity
591
+ """
592
+ _response = self._client_wrapper.httpx_client.request(
593
+ f"construe/codes/{jsonable_encoder(codesystem)}/search/semantic",
594
+ method="GET",
595
+ params={
596
+ "text": text,
597
+ "version": version,
598
+ "limit": limit,
599
+ },
600
+ request_options=request_options,
601
+ )
602
+ try:
603
+ if 200 <= _response.status_code < 300:
604
+ _data = typing.cast(
605
+ SemanticSearchResponse,
606
+ parse_obj_as(
607
+ type_=SemanticSearchResponse, # type: ignore
608
+ object_=_response.json(),
609
+ ),
610
+ )
611
+ return HttpResponse(response=_response, data=_data)
612
+ if _response.status_code == 400:
613
+ raise BadRequestError(
614
+ headers=dict(_response.headers),
615
+ body=typing.cast(
616
+ typing.Optional[typing.Any],
617
+ parse_obj_as(
618
+ type_=typing.Optional[typing.Any], # type: ignore
619
+ object_=_response.json(),
620
+ ),
621
+ ),
622
+ )
623
+ if _response.status_code == 401:
624
+ raise UnauthorizedError(
625
+ headers=dict(_response.headers),
626
+ body=typing.cast(
627
+ typing.Optional[typing.Any],
628
+ parse_obj_as(
629
+ type_=typing.Optional[typing.Any], # type: ignore
630
+ object_=_response.json(),
631
+ ),
632
+ ),
633
+ )
634
+ if _response.status_code == 404:
635
+ raise NotFoundError(
636
+ headers=dict(_response.headers),
637
+ body=typing.cast(
638
+ typing.Optional[typing.Any],
639
+ parse_obj_as(
640
+ type_=typing.Optional[typing.Any], # type: ignore
641
+ object_=_response.json(),
642
+ ),
643
+ ),
644
+ )
645
+ if _response.status_code == 500:
646
+ raise InternalServerError(
647
+ headers=dict(_response.headers),
648
+ body=typing.cast(
649
+ typing.Optional[typing.Any],
650
+ parse_obj_as(
651
+ type_=typing.Optional[typing.Any], # type: ignore
652
+ object_=_response.json(),
653
+ ),
654
+ ),
655
+ )
656
+ _response_json = _response.json()
657
+ except JSONDecodeError:
658
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
659
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
660
+
661
+ def text_search_keyword_based(
662
+ self,
663
+ codesystem: str,
664
+ *,
665
+ q: str,
666
+ version: typing.Optional[str] = None,
667
+ limit: typing.Optional[int] = None,
668
+ request_options: typing.Optional[RequestOptions] = None,
669
+ ) -> HttpResponse[TextSearchResponse]:
670
+ """
671
+ Performs fast full-text search over code IDs and descriptions.
672
+
673
+ **When to use**: Best for autocomplete UIs, code lookup, or when users know part of
674
+ the code ID or specific keywords. Fast response times suitable for typeahead interfaces.
675
+
676
+ **Features**:
677
+ - Substring matching on code IDs (e.g., "11.65" finds "E11.65")
678
+ - Typo tolerance on descriptions (not on code IDs)
679
+ - Fast response times (~10-50ms)
680
+
681
+ **Examples**:
682
+ - Query "E11" finds all codes starting with E11 (diabetes codes)
683
+ - Query "diabtes" (typo) still finds "diabetes" codes
684
+
685
+ **Trade-offs**: Faster than semantic search, but only matches keywords/substrings.
686
+ Won't find conceptually related codes with different terminology.
687
+
688
+ See also: `/search/semantic` for finding conceptually similar codes.
689
+
690
+ Parameters
691
+ ----------
692
+ codesystem : str
693
+ Code system name
694
+
695
+ q : str
696
+ Search query (searches code IDs and descriptions)
697
+
698
+ version : typing.Optional[str]
699
+ Specific version of the code system
700
+
701
+ limit : typing.Optional[int]
702
+ Maximum number of results (default 20, max 100)
703
+
704
+ request_options : typing.Optional[RequestOptions]
705
+ Request-specific configuration.
706
+
707
+ Returns
708
+ -------
709
+ HttpResponse[TextSearchResponse]
710
+ Text search results
711
+ """
712
+ _response = self._client_wrapper.httpx_client.request(
713
+ f"construe/codes/{jsonable_encoder(codesystem)}/search/text",
714
+ method="GET",
715
+ params={
716
+ "q": q,
717
+ "version": version,
718
+ "limit": limit,
719
+ },
720
+ request_options=request_options,
721
+ )
722
+ try:
723
+ if 200 <= _response.status_code < 300:
724
+ _data = typing.cast(
725
+ TextSearchResponse,
726
+ parse_obj_as(
727
+ type_=TextSearchResponse, # type: ignore
728
+ object_=_response.json(),
729
+ ),
730
+ )
731
+ return HttpResponse(response=_response, data=_data)
732
+ if _response.status_code == 400:
733
+ raise BadRequestError(
734
+ headers=dict(_response.headers),
735
+ body=typing.cast(
736
+ typing.Optional[typing.Any],
737
+ parse_obj_as(
738
+ type_=typing.Optional[typing.Any], # type: ignore
739
+ object_=_response.json(),
740
+ ),
741
+ ),
742
+ )
743
+ if _response.status_code == 401:
744
+ raise UnauthorizedError(
745
+ headers=dict(_response.headers),
746
+ body=typing.cast(
747
+ typing.Optional[typing.Any],
748
+ parse_obj_as(
749
+ type_=typing.Optional[typing.Any], # type: ignore
750
+ object_=_response.json(),
751
+ ),
752
+ ),
753
+ )
754
+ if _response.status_code == 404:
755
+ raise NotFoundError(
756
+ headers=dict(_response.headers),
757
+ body=typing.cast(
758
+ typing.Optional[typing.Any],
759
+ parse_obj_as(
760
+ type_=typing.Optional[typing.Any], # type: ignore
761
+ object_=_response.json(),
762
+ ),
763
+ ),
764
+ )
765
+ if _response.status_code == 500:
766
+ raise InternalServerError(
767
+ headers=dict(_response.headers),
768
+ body=typing.cast(
769
+ typing.Optional[typing.Any],
770
+ parse_obj_as(
771
+ type_=typing.Optional[typing.Any], # type: ignore
772
+ object_=_response.json(),
773
+ ),
774
+ ),
775
+ )
776
+ if _response.status_code == 501:
777
+ raise NotImplementedError(
778
+ headers=dict(_response.headers),
779
+ body=typing.cast(
780
+ typing.Optional[typing.Any],
781
+ parse_obj_as(
782
+ type_=typing.Optional[typing.Any], # type: ignore
783
+ object_=_response.json(),
784
+ ),
785
+ ),
786
+ )
787
+ if _response.status_code == 503:
788
+ raise ServiceUnavailableError(
789
+ headers=dict(_response.headers),
790
+ body=typing.cast(
791
+ typing.Optional[typing.Any],
792
+ parse_obj_as(
793
+ type_=typing.Optional[typing.Any], # type: ignore
794
+ object_=_response.json(),
795
+ ),
796
+ ),
797
+ )
798
+ _response_json = _response.json()
799
+ except JSONDecodeError:
800
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
801
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
802
+
803
+
804
+ class AsyncRawConstrueClient:
805
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
806
+ self._client_wrapper = client_wrapper
807
+
808
+ async def upload_code_system(
809
+ self,
810
+ *,
811
+ name: str,
812
+ version: str,
813
+ format: UploadRequestFormat,
814
+ file: str,
815
+ revision: typing.Optional[float] = OMIT,
816
+ code_col: typing.Optional[str] = OMIT,
817
+ desc_col: typing.Optional[str] = OMIT,
818
+ defn_col: typing.Optional[str] = OMIT,
819
+ request_options: typing.Optional[RequestOptions] = None,
820
+ ) -> AsyncHttpResponse[ConstrueUploadCodeSystemResponse]:
821
+ """
822
+ Upload a custom medical code system with codes and descriptions for use in code extraction.
823
+ Upon upload, construe generates embeddings for all of the codes in the code system and stores them in the vector database so you can
824
+ subsequently use the code system for construe/extract and lang2fhir/create (coming soon!)
825
+
826
+ Parameters
827
+ ----------
828
+ name : str
829
+ Name of the code system
830
+
831
+ version : str
832
+ Version of the code system
833
+
834
+ format : UploadRequestFormat
835
+ Format of the uploaded file
836
+
837
+ file : str
838
+ The file contents as a base64-encoded string
839
+
840
+ revision : typing.Optional[float]
841
+ Optional revision number
842
+
843
+ code_col : typing.Optional[str]
844
+ Column name containing codes (required for CSV format)
277
845
 
278
- class AsyncRawConstrueClient:
279
- def __init__(self, *, client_wrapper: AsyncClientWrapper):
280
- self._client_wrapper = client_wrapper
846
+ desc_col : typing.Optional[str]
847
+ Column name containing descriptions (required for CSV format)
848
+
849
+ defn_col : typing.Optional[str]
850
+ Optional column name containing long definitions (for CSV format)
851
+
852
+ request_options : typing.Optional[RequestOptions]
853
+ Request-specific configuration.
854
+
855
+ Returns
856
+ -------
857
+ AsyncHttpResponse[ConstrueUploadCodeSystemResponse]
858
+ Successfully uploaded code system
859
+ """
860
+ _response = await self._client_wrapper.httpx_client.request(
861
+ "construe/upload",
862
+ method="POST",
863
+ json={
864
+ "name": name,
865
+ "version": version,
866
+ "revision": revision,
867
+ "format": format,
868
+ "file": file,
869
+ "code_col": code_col,
870
+ "desc_col": desc_col,
871
+ "defn_col": defn_col,
872
+ },
873
+ headers={
874
+ "content-type": "application/json",
875
+ },
876
+ request_options=request_options,
877
+ omit=OMIT,
878
+ )
879
+ try:
880
+ if 200 <= _response.status_code < 300:
881
+ _data = typing.cast(
882
+ ConstrueUploadCodeSystemResponse,
883
+ parse_obj_as(
884
+ type_=ConstrueUploadCodeSystemResponse, # type: ignore
885
+ object_=_response.json(),
886
+ ),
887
+ )
888
+ return AsyncHttpResponse(response=_response, data=_data)
889
+ if _response.status_code == 400:
890
+ raise BadRequestError(
891
+ headers=dict(_response.headers),
892
+ body=typing.cast(
893
+ typing.Optional[typing.Any],
894
+ parse_obj_as(
895
+ type_=typing.Optional[typing.Any], # type: ignore
896
+ object_=_response.json(),
897
+ ),
898
+ ),
899
+ )
900
+ if _response.status_code == 401:
901
+ raise UnauthorizedError(
902
+ headers=dict(_response.headers),
903
+ body=typing.cast(
904
+ typing.Optional[typing.Any],
905
+ parse_obj_as(
906
+ type_=typing.Optional[typing.Any], # type: ignore
907
+ object_=_response.json(),
908
+ ),
909
+ ),
910
+ )
911
+ if _response.status_code == 409:
912
+ raise ConflictError(
913
+ headers=dict(_response.headers),
914
+ body=typing.cast(
915
+ typing.Optional[typing.Any],
916
+ parse_obj_as(
917
+ type_=typing.Optional[typing.Any], # type: ignore
918
+ object_=_response.json(),
919
+ ),
920
+ ),
921
+ )
922
+ if _response.status_code == 424:
923
+ raise FailedDependencyError(
924
+ headers=dict(_response.headers),
925
+ body=typing.cast(
926
+ typing.Optional[typing.Any],
927
+ parse_obj_as(
928
+ type_=typing.Optional[typing.Any], # type: ignore
929
+ object_=_response.json(),
930
+ ),
931
+ ),
932
+ )
933
+ if _response.status_code == 500:
934
+ raise InternalServerError(
935
+ headers=dict(_response.headers),
936
+ body=typing.cast(
937
+ typing.Optional[typing.Any],
938
+ parse_obj_as(
939
+ type_=typing.Optional[typing.Any], # type: ignore
940
+ object_=_response.json(),
941
+ ),
942
+ ),
943
+ )
944
+ _response_json = _response.json()
945
+ except JSONDecodeError:
946
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
947
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
948
+
949
+ async def extract_codes(
950
+ self,
951
+ *,
952
+ text: str,
953
+ system: typing.Optional[ExtractRequestSystem] = OMIT,
954
+ config: typing.Optional[ExtractRequestConfig] = OMIT,
955
+ request_options: typing.Optional[RequestOptions] = None,
956
+ ) -> AsyncHttpResponse[ExtractCodesResult]:
957
+ """
958
+ Converts natural language text into structured medical codes
959
+
960
+ Parameters
961
+ ----------
962
+ text : str
963
+ Natural language text to extract codes from
964
+
965
+ system : typing.Optional[ExtractRequestSystem]
966
+
967
+ config : typing.Optional[ExtractRequestConfig]
968
+
969
+ request_options : typing.Optional[RequestOptions]
970
+ Request-specific configuration.
971
+
972
+ Returns
973
+ -------
974
+ AsyncHttpResponse[ExtractCodesResult]
975
+ Successfully extracted codes
976
+ """
977
+ _response = await self._client_wrapper.httpx_client.request(
978
+ "construe/extract",
979
+ method="POST",
980
+ json={
981
+ "text": text,
982
+ "system": convert_and_respect_annotation_metadata(
983
+ object_=system, annotation=ExtractRequestSystem, direction="write"
984
+ ),
985
+ "config": convert_and_respect_annotation_metadata(
986
+ object_=config, annotation=ExtractRequestConfig, direction="write"
987
+ ),
988
+ },
989
+ headers={
990
+ "content-type": "application/json",
991
+ },
992
+ request_options=request_options,
993
+ omit=OMIT,
994
+ )
995
+ try:
996
+ if 200 <= _response.status_code < 300:
997
+ _data = typing.cast(
998
+ ExtractCodesResult,
999
+ parse_obj_as(
1000
+ type_=ExtractCodesResult, # type: ignore
1001
+ object_=_response.json(),
1002
+ ),
1003
+ )
1004
+ return AsyncHttpResponse(response=_response, data=_data)
1005
+ if _response.status_code == 400:
1006
+ raise BadRequestError(
1007
+ headers=dict(_response.headers),
1008
+ body=typing.cast(
1009
+ typing.Optional[typing.Any],
1010
+ parse_obj_as(
1011
+ type_=typing.Optional[typing.Any], # type: ignore
1012
+ object_=_response.json(),
1013
+ ),
1014
+ ),
1015
+ )
1016
+ if _response.status_code == 401:
1017
+ raise UnauthorizedError(
1018
+ headers=dict(_response.headers),
1019
+ body=typing.cast(
1020
+ typing.Optional[typing.Any],
1021
+ parse_obj_as(
1022
+ type_=typing.Optional[typing.Any], # type: ignore
1023
+ object_=_response.json(),
1024
+ ),
1025
+ ),
1026
+ )
1027
+ if _response.status_code == 424:
1028
+ raise FailedDependencyError(
1029
+ headers=dict(_response.headers),
1030
+ body=typing.cast(
1031
+ typing.Optional[typing.Any],
1032
+ parse_obj_as(
1033
+ type_=typing.Optional[typing.Any], # type: ignore
1034
+ object_=_response.json(),
1035
+ ),
1036
+ ),
1037
+ )
1038
+ if _response.status_code == 500:
1039
+ raise InternalServerError(
1040
+ headers=dict(_response.headers),
1041
+ body=typing.cast(
1042
+ typing.Optional[typing.Any],
1043
+ parse_obj_as(
1044
+ type_=typing.Optional[typing.Any], # type: ignore
1045
+ object_=_response.json(),
1046
+ ),
1047
+ ),
1048
+ )
1049
+ _response_json = _response.json()
1050
+ except JSONDecodeError:
1051
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1052
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
1053
+
1054
+ async def list_available_code_systems(
1055
+ self, *, request_options: typing.Optional[RequestOptions] = None
1056
+ ) -> AsyncHttpResponse[ListCodeSystemsResponse]:
1057
+ """
1058
+ Returns metadata about all available code systems including built-in and custom systems.
1059
+
1060
+ Parameters
1061
+ ----------
1062
+ request_options : typing.Optional[RequestOptions]
1063
+ Request-specific configuration.
1064
+
1065
+ Returns
1066
+ -------
1067
+ AsyncHttpResponse[ListCodeSystemsResponse]
1068
+ List of available code systems
1069
+ """
1070
+ _response = await self._client_wrapper.httpx_client.request(
1071
+ "construe/codes/systems",
1072
+ method="GET",
1073
+ request_options=request_options,
1074
+ )
1075
+ try:
1076
+ if 200 <= _response.status_code < 300:
1077
+ _data = typing.cast(
1078
+ ListCodeSystemsResponse,
1079
+ parse_obj_as(
1080
+ type_=ListCodeSystemsResponse, # type: ignore
1081
+ object_=_response.json(),
1082
+ ),
1083
+ )
1084
+ return AsyncHttpResponse(response=_response, data=_data)
1085
+ if _response.status_code == 401:
1086
+ raise UnauthorizedError(
1087
+ headers=dict(_response.headers),
1088
+ body=typing.cast(
1089
+ typing.Optional[typing.Any],
1090
+ parse_obj_as(
1091
+ type_=typing.Optional[typing.Any], # type: ignore
1092
+ object_=_response.json(),
1093
+ ),
1094
+ ),
1095
+ )
1096
+ if _response.status_code == 500:
1097
+ raise InternalServerError(
1098
+ headers=dict(_response.headers),
1099
+ body=typing.cast(
1100
+ typing.Optional[typing.Any],
1101
+ parse_obj_as(
1102
+ type_=typing.Optional[typing.Any], # type: ignore
1103
+ object_=_response.json(),
1104
+ ),
1105
+ ),
1106
+ )
1107
+ _response_json = _response.json()
1108
+ except JSONDecodeError:
1109
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1110
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
281
1111
 
282
- async def upload_code_system(
1112
+ async def list_codes_in_a_code_system(
283
1113
  self,
1114
+ codesystem: str,
284
1115
  *,
285
- name: str,
286
- version: str,
287
- format: UploadRequestFormat,
288
- file: str,
289
- revision: typing.Optional[float] = OMIT,
290
- code_col: typing.Optional[str] = OMIT,
291
- desc_col: typing.Optional[str] = OMIT,
292
- defn_col: typing.Optional[str] = OMIT,
1116
+ version: typing.Optional[str] = None,
1117
+ cursor: typing.Optional[str] = None,
1118
+ limit: typing.Optional[int] = None,
293
1119
  request_options: typing.Optional[RequestOptions] = None,
294
- ) -> AsyncHttpResponse[ConstrueUploadCodeSystemResponse]:
1120
+ ) -> AsyncHttpResponse[ListCodesResponse]:
295
1121
  """
296
- Upload a custom medical code system with codes and descriptions for use in code extraction.
297
- Upon upload, construe generates embeddings for all of the codes in the code system and stores them in the vector database so you can
298
- subsequently use the code system for construe/extract and lang2fhir/create (coming soon!)
1122
+ Returns a paginated list of all codes in the specified code system.
299
1123
 
300
1124
  Parameters
301
1125
  ----------
302
- name : str
303
- Name of the code system
304
-
305
- version : str
306
- Version of the code system
307
-
308
- format : UploadRequestFormat
309
- Format of the uploaded file
1126
+ codesystem : str
1127
+ Code system name (e.g., "ICD-10-CM", "SNOMED_CT_US_LITE")
310
1128
 
311
- file : str
312
- The file contents as a base64-encoded string
313
-
314
- revision : typing.Optional[float]
315
- Optional revision number
316
-
317
- code_col : typing.Optional[str]
318
- Column name containing codes (required for CSV format)
1129
+ version : typing.Optional[str]
1130
+ Specific version of the code system. Required if multiple versions exist.
319
1131
 
320
- desc_col : typing.Optional[str]
321
- Column name containing descriptions (required for CSV format)
1132
+ cursor : typing.Optional[str]
1133
+ Pagination cursor from previous response
322
1134
 
323
- defn_col : typing.Optional[str]
324
- Optional column name containing long definitions (for CSV format)
1135
+ limit : typing.Optional[int]
1136
+ Maximum number of codes to return (default 20)
325
1137
 
326
1138
  request_options : typing.Optional[RequestOptions]
327
1139
  Request-specific configuration.
328
1140
 
329
1141
  Returns
330
1142
  -------
331
- AsyncHttpResponse[ConstrueUploadCodeSystemResponse]
332
- Successfully uploaded code system
1143
+ AsyncHttpResponse[ListCodesResponse]
1144
+ Paginated list of codes
333
1145
  """
334
1146
  _response = await self._client_wrapper.httpx_client.request(
335
- "construe/upload",
336
- method="POST",
337
- json={
338
- "name": name,
1147
+ f"construe/codes/{jsonable_encoder(codesystem)}",
1148
+ method="GET",
1149
+ params={
339
1150
  "version": version,
340
- "revision": revision,
341
- "format": format,
342
- "file": file,
343
- "code_col": code_col,
344
- "desc_col": desc_col,
345
- "defn_col": defn_col,
346
- },
347
- headers={
348
- "content-type": "application/json",
1151
+ "cursor": cursor,
1152
+ "limit": limit,
349
1153
  },
350
1154
  request_options=request_options,
351
- omit=OMIT,
352
1155
  )
353
1156
  try:
354
1157
  if 200 <= _response.status_code < 300:
355
1158
  _data = typing.cast(
356
- ConstrueUploadCodeSystemResponse,
1159
+ ListCodesResponse,
357
1160
  parse_obj_as(
358
- type_=ConstrueUploadCodeSystemResponse, # type: ignore
1161
+ type_=ListCodesResponse, # type: ignore
359
1162
  object_=_response.json(),
360
1163
  ),
361
1164
  )
@@ -382,8 +1185,8 @@ class AsyncRawConstrueClient:
382
1185
  ),
383
1186
  ),
384
1187
  )
385
- if _response.status_code == 409:
386
- raise ConflictError(
1188
+ if _response.status_code == 404:
1189
+ raise NotFoundError(
387
1190
  headers=dict(_response.headers),
388
1191
  body=typing.cast(
389
1192
  typing.Optional[typing.Any],
@@ -393,8 +1196,94 @@ class AsyncRawConstrueClient:
393
1196
  ),
394
1197
  ),
395
1198
  )
396
- if _response.status_code == 424:
397
- raise FailedDependencyError(
1199
+ if _response.status_code == 500:
1200
+ raise InternalServerError(
1201
+ headers=dict(_response.headers),
1202
+ body=typing.cast(
1203
+ typing.Optional[typing.Any],
1204
+ parse_obj_as(
1205
+ type_=typing.Optional[typing.Any], # type: ignore
1206
+ object_=_response.json(),
1207
+ ),
1208
+ ),
1209
+ )
1210
+ _response_json = _response.json()
1211
+ except JSONDecodeError:
1212
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1213
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
1214
+
1215
+ async def get_a_specific_code(
1216
+ self,
1217
+ codesystem: str,
1218
+ code_id: str,
1219
+ *,
1220
+ version: typing.Optional[str] = None,
1221
+ request_options: typing.Optional[RequestOptions] = None,
1222
+ ) -> AsyncHttpResponse[GetCodeResponse]:
1223
+ """
1224
+ Returns details for a specific code within a code system.
1225
+
1226
+ Parameters
1227
+ ----------
1228
+ codesystem : str
1229
+ Code system name
1230
+
1231
+ code_id : str
1232
+ The code identifier
1233
+
1234
+ version : typing.Optional[str]
1235
+ Specific version of the code system
1236
+
1237
+ request_options : typing.Optional[RequestOptions]
1238
+ Request-specific configuration.
1239
+
1240
+ Returns
1241
+ -------
1242
+ AsyncHttpResponse[GetCodeResponse]
1243
+ Code details
1244
+ """
1245
+ _response = await self._client_wrapper.httpx_client.request(
1246
+ f"construe/codes/{jsonable_encoder(codesystem)}/{jsonable_encoder(code_id)}",
1247
+ method="GET",
1248
+ params={
1249
+ "version": version,
1250
+ },
1251
+ request_options=request_options,
1252
+ )
1253
+ try:
1254
+ if 200 <= _response.status_code < 300:
1255
+ _data = typing.cast(
1256
+ GetCodeResponse,
1257
+ parse_obj_as(
1258
+ type_=GetCodeResponse, # type: ignore
1259
+ object_=_response.json(),
1260
+ ),
1261
+ )
1262
+ return AsyncHttpResponse(response=_response, data=_data)
1263
+ if _response.status_code == 400:
1264
+ raise BadRequestError(
1265
+ headers=dict(_response.headers),
1266
+ body=typing.cast(
1267
+ typing.Optional[typing.Any],
1268
+ parse_obj_as(
1269
+ type_=typing.Optional[typing.Any], # type: ignore
1270
+ object_=_response.json(),
1271
+ ),
1272
+ ),
1273
+ )
1274
+ if _response.status_code == 401:
1275
+ raise UnauthorizedError(
1276
+ headers=dict(_response.headers),
1277
+ body=typing.cast(
1278
+ typing.Optional[typing.Any],
1279
+ parse_obj_as(
1280
+ type_=typing.Optional[typing.Any], # type: ignore
1281
+ object_=_response.json(),
1282
+ ),
1283
+ ),
1284
+ )
1285
+ if _response.status_code == 404:
1286
+ raise NotFoundError(
398
1287
  headers=dict(_response.headers),
399
1288
  body=typing.cast(
400
1289
  typing.Optional[typing.Any],
@@ -420,58 +1309,190 @@ class AsyncRawConstrueClient:
420
1309
  raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
421
1310
  raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
422
1311
 
423
- async def extract_codes(
1312
+ async def semantic_search_embedding_based(
424
1313
  self,
1314
+ codesystem: str,
425
1315
  *,
426
1316
  text: str,
427
- system: typing.Optional[ExtractRequestSystem] = OMIT,
428
- config: typing.Optional[ExtractRequestConfig] = OMIT,
1317
+ version: typing.Optional[str] = None,
1318
+ limit: typing.Optional[int] = None,
429
1319
  request_options: typing.Optional[RequestOptions] = None,
430
- ) -> AsyncHttpResponse[ExtractCodesResult]:
1320
+ ) -> AsyncHttpResponse[SemanticSearchResponse]:
431
1321
  """
432
- Converts natural language text into structured medical codes
1322
+ Performs semantic similarity search using vector embeddings.
1323
+
1324
+ **When to use**: Best for natural language queries where you want to find conceptually
1325
+ related codes, even when different terminology is used. The search understands meaning,
1326
+ not just keywords.
1327
+
1328
+ **Examples**:
1329
+ - Query "trouble breathing at night" finds codes like "Sleep apnea", "Orthopnea",
1330
+ "Nocturnal dyspnea" — semantically related but no exact keyword matches
1331
+ - Query "heart problems" finds "Myocardial infarction", "Cardiac arrest", "Arrhythmia"
1332
+
1333
+ **Trade-offs**: Slower than text search (requires embedding generation), but finds
1334
+ conceptually similar results that keyword search would miss.
1335
+
1336
+ See also: `/search/text` for faster keyword-based lookup with typo tolerance.
433
1337
 
434
1338
  Parameters
435
1339
  ----------
1340
+ codesystem : str
1341
+ Code system name
1342
+
436
1343
  text : str
437
- Natural language text to extract codes from
1344
+ Natural language text to find semantically similar codes for
438
1345
 
439
- system : typing.Optional[ExtractRequestSystem]
1346
+ version : typing.Optional[str]
1347
+ Specific version of the code system
440
1348
 
441
- config : typing.Optional[ExtractRequestConfig]
1349
+ limit : typing.Optional[int]
1350
+ Maximum number of results (default 10, max 50)
442
1351
 
443
1352
  request_options : typing.Optional[RequestOptions]
444
1353
  Request-specific configuration.
445
1354
 
446
1355
  Returns
447
1356
  -------
448
- AsyncHttpResponse[ExtractCodesResult]
449
- Successfully extracted codes
1357
+ AsyncHttpResponse[SemanticSearchResponse]
1358
+ Semantic search results ordered by similarity
450
1359
  """
451
1360
  _response = await self._client_wrapper.httpx_client.request(
452
- "construe/extract",
453
- method="POST",
454
- json={
1361
+ f"construe/codes/{jsonable_encoder(codesystem)}/search/semantic",
1362
+ method="GET",
1363
+ params={
455
1364
  "text": text,
456
- "system": convert_and_respect_annotation_metadata(
457
- object_=system, annotation=ExtractRequestSystem, direction="write"
458
- ),
459
- "config": convert_and_respect_annotation_metadata(
460
- object_=config, annotation=ExtractRequestConfig, direction="write"
461
- ),
1365
+ "version": version,
1366
+ "limit": limit,
462
1367
  },
463
- headers={
464
- "content-type": "application/json",
1368
+ request_options=request_options,
1369
+ )
1370
+ try:
1371
+ if 200 <= _response.status_code < 300:
1372
+ _data = typing.cast(
1373
+ SemanticSearchResponse,
1374
+ parse_obj_as(
1375
+ type_=SemanticSearchResponse, # type: ignore
1376
+ object_=_response.json(),
1377
+ ),
1378
+ )
1379
+ return AsyncHttpResponse(response=_response, data=_data)
1380
+ if _response.status_code == 400:
1381
+ raise BadRequestError(
1382
+ headers=dict(_response.headers),
1383
+ body=typing.cast(
1384
+ typing.Optional[typing.Any],
1385
+ parse_obj_as(
1386
+ type_=typing.Optional[typing.Any], # type: ignore
1387
+ object_=_response.json(),
1388
+ ),
1389
+ ),
1390
+ )
1391
+ if _response.status_code == 401:
1392
+ raise UnauthorizedError(
1393
+ headers=dict(_response.headers),
1394
+ body=typing.cast(
1395
+ typing.Optional[typing.Any],
1396
+ parse_obj_as(
1397
+ type_=typing.Optional[typing.Any], # type: ignore
1398
+ object_=_response.json(),
1399
+ ),
1400
+ ),
1401
+ )
1402
+ if _response.status_code == 404:
1403
+ raise NotFoundError(
1404
+ headers=dict(_response.headers),
1405
+ body=typing.cast(
1406
+ typing.Optional[typing.Any],
1407
+ parse_obj_as(
1408
+ type_=typing.Optional[typing.Any], # type: ignore
1409
+ object_=_response.json(),
1410
+ ),
1411
+ ),
1412
+ )
1413
+ if _response.status_code == 500:
1414
+ raise InternalServerError(
1415
+ headers=dict(_response.headers),
1416
+ body=typing.cast(
1417
+ typing.Optional[typing.Any],
1418
+ parse_obj_as(
1419
+ type_=typing.Optional[typing.Any], # type: ignore
1420
+ object_=_response.json(),
1421
+ ),
1422
+ ),
1423
+ )
1424
+ _response_json = _response.json()
1425
+ except JSONDecodeError:
1426
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
1427
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
1428
+
1429
+ async def text_search_keyword_based(
1430
+ self,
1431
+ codesystem: str,
1432
+ *,
1433
+ q: str,
1434
+ version: typing.Optional[str] = None,
1435
+ limit: typing.Optional[int] = None,
1436
+ request_options: typing.Optional[RequestOptions] = None,
1437
+ ) -> AsyncHttpResponse[TextSearchResponse]:
1438
+ """
1439
+ Performs fast full-text search over code IDs and descriptions.
1440
+
1441
+ **When to use**: Best for autocomplete UIs, code lookup, or when users know part of
1442
+ the code ID or specific keywords. Fast response times suitable for typeahead interfaces.
1443
+
1444
+ **Features**:
1445
+ - Substring matching on code IDs (e.g., "11.65" finds "E11.65")
1446
+ - Typo tolerance on descriptions (not on code IDs)
1447
+ - Fast response times (~10-50ms)
1448
+
1449
+ **Examples**:
1450
+ - Query "E11" finds all codes starting with E11 (diabetes codes)
1451
+ - Query "diabtes" (typo) still finds "diabetes" codes
1452
+
1453
+ **Trade-offs**: Faster than semantic search, but only matches keywords/substrings.
1454
+ Won't find conceptually related codes with different terminology.
1455
+
1456
+ See also: `/search/semantic` for finding conceptually similar codes.
1457
+
1458
+ Parameters
1459
+ ----------
1460
+ codesystem : str
1461
+ Code system name
1462
+
1463
+ q : str
1464
+ Search query (searches code IDs and descriptions)
1465
+
1466
+ version : typing.Optional[str]
1467
+ Specific version of the code system
1468
+
1469
+ limit : typing.Optional[int]
1470
+ Maximum number of results (default 20, max 100)
1471
+
1472
+ request_options : typing.Optional[RequestOptions]
1473
+ Request-specific configuration.
1474
+
1475
+ Returns
1476
+ -------
1477
+ AsyncHttpResponse[TextSearchResponse]
1478
+ Text search results
1479
+ """
1480
+ _response = await self._client_wrapper.httpx_client.request(
1481
+ f"construe/codes/{jsonable_encoder(codesystem)}/search/text",
1482
+ method="GET",
1483
+ params={
1484
+ "q": q,
1485
+ "version": version,
1486
+ "limit": limit,
465
1487
  },
466
1488
  request_options=request_options,
467
- omit=OMIT,
468
1489
  )
469
1490
  try:
470
1491
  if 200 <= _response.status_code < 300:
471
1492
  _data = typing.cast(
472
- ExtractCodesResult,
1493
+ TextSearchResponse,
473
1494
  parse_obj_as(
474
- type_=ExtractCodesResult, # type: ignore
1495
+ type_=TextSearchResponse, # type: ignore
475
1496
  object_=_response.json(),
476
1497
  ),
477
1498
  )
@@ -498,8 +1519,8 @@ class AsyncRawConstrueClient:
498
1519
  ),
499
1520
  ),
500
1521
  )
501
- if _response.status_code == 424:
502
- raise FailedDependencyError(
1522
+ if _response.status_code == 404:
1523
+ raise NotFoundError(
503
1524
  headers=dict(_response.headers),
504
1525
  body=typing.cast(
505
1526
  typing.Optional[typing.Any],
@@ -520,6 +1541,28 @@ class AsyncRawConstrueClient:
520
1541
  ),
521
1542
  ),
522
1543
  )
1544
+ if _response.status_code == 501:
1545
+ raise NotImplementedError(
1546
+ headers=dict(_response.headers),
1547
+ body=typing.cast(
1548
+ typing.Optional[typing.Any],
1549
+ parse_obj_as(
1550
+ type_=typing.Optional[typing.Any], # type: ignore
1551
+ object_=_response.json(),
1552
+ ),
1553
+ ),
1554
+ )
1555
+ if _response.status_code == 503:
1556
+ raise ServiceUnavailableError(
1557
+ headers=dict(_response.headers),
1558
+ body=typing.cast(
1559
+ typing.Optional[typing.Any],
1560
+ parse_obj_as(
1561
+ type_=typing.Optional[typing.Any], # type: ignore
1562
+ object_=_response.json(),
1563
+ ),
1564
+ ),
1565
+ )
523
1566
  _response_json = _response.json()
524
1567
  except JSONDecodeError:
525
1568
  raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)