From 00634ffb4ffb3a49294c90d5ece247ed758e7e14 Mon Sep 17 00:00:00 2001 From: <> Date: Wed, 27 Nov 2024 15:09:49 +0000 Subject: [PATCH] Deployed 261c6d8 with MkDocs version: 1.6.1 --- .nojekyll | 0 404.html | 1921 +++++ api/cda_parser/index.html | 5714 +++++++++++++ api/cds_hooks/index.html | 3791 +++++++++ api/clients/index.html | 3053 +++++++ api/component/index.html | 6851 ++++++++++++++++ api/connectors/index.html | 4025 +++++++++ api/containers/index.html | 7235 +++++++++++++++++ api/data_generators/index.html | 3958 +++++++++ api/data_models/index.html | 3071 +++++++ api/index.html | 1967 +++++ api/pipeline/index.html | 7034 ++++++++++++++++ api/service/index.html | 2370 ++++++ api/use_cases/index.html | 6505 +++++++++++++++ assets/_mkdocstrings.css | 143 + assets/images/favicon.png | Bin 0 -> 1870 bytes assets/images/healthchain_logo.png | Bin 0 -> 373000 bytes assets/images/synthetic_data_ons.png | Bin 0 -> 33429 bytes assets/javascripts/bundle.83f73b43.min.js | 16 + assets/javascripts/bundle.83f73b43.min.js.map | 7 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.el.min.js | 1 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.he.min.js | 1 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++ .../workers/search.6ce7567c.min.js | 42 + .../workers/search.6ce7567c.min.js.map | 7 + assets/stylesheets/main.6f8fc17f.min.css | 1 + assets/stylesheets/main.6f8fc17f.min.css.map | 1 + assets/stylesheets/palette.06af60db.min.css | 1 + .../stylesheets/palette.06af60db.min.css.map | 1 + blog/index.html | 1930 +++++ community/contribution_guide/index.html | 1967 +++++ community/index.html | 2025 +++++ community/resources/index.html | 1965 +++++ cookbook/cds_sandbox/index.html | 2279 ++++++ cookbook/index.html | 1972 +++++ cookbook/notereader_sandbox/index.html | 2000 +++++ distribution/index.html | 1982 +++++ index.html | 2083 +++++ installation/index.html | 1976 +++++ objects.inv | Bin 0 -> 2641 bytes quickstart/index.html | 2412 ++++++ reference/index.html | 1979 +++++ .../components/cdscardcreator/index.html | 2365 ++++++ .../pipeline/components/components/index.html | 2098 +++++ .../connectors/cdaconnector/index.html | 2112 +++++ .../connectors/cdsfhirconnector/index.html | 2105 +++++ .../pipeline/connectors/connectors/index.html | 2145 +++++ reference/pipeline/data_container/index.html | 2165 +++++ .../integrations/integrations/index.html | 2479 ++++++ reference/pipeline/pipeline/index.html | 2507 ++++++ .../medicalcoding/index.html | 1980 +++++ .../summarization/index.html | 1980 +++++ reference/sandbox/client/index.html | 2021 +++++ reference/sandbox/sandbox/index.html | 2039 +++++ reference/sandbox/service/index.html | 2038 +++++ reference/sandbox/use_cases/cds/index.html | 2333 ++++++ .../sandbox/use_cases/clindoc/index.html | 2441 ++++++ .../sandbox/use_cases/use_cases/index.html | 1986 +++++ reference/utilities/cda_parser/index.html | 2191 +++++ reference/utilities/data_generator/index.html | 2184 +++++ search/search_index.json | 1 + sitemap.xml | 3 + sitemap.xml.gz | Bin 0 -> 127 bytes stylesheets/extra.css | 125 + 95 files changed, 128783 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 api/cda_parser/index.html create mode 100644 api/cds_hooks/index.html create mode 100644 api/clients/index.html create mode 100644 api/component/index.html create mode 100644 api/connectors/index.html create mode 100644 api/containers/index.html create mode 100644 api/data_generators/index.html create mode 100644 api/data_models/index.html create mode 100644 api/index.html create mode 100644 api/pipeline/index.html create mode 100644 api/service/index.html create mode 100644 api/use_cases/index.html create mode 100644 assets/_mkdocstrings.css create mode 100644 assets/images/favicon.png create mode 100644 assets/images/healthchain_logo.png create mode 100644 assets/images/synthetic_data_ons.png create mode 100644 assets/javascripts/bundle.83f73b43.min.js create mode 100644 assets/javascripts/bundle.83f73b43.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js.map create mode 100644 assets/stylesheets/main.6f8fc17f.min.css create mode 100644 assets/stylesheets/main.6f8fc17f.min.css.map create mode 100644 assets/stylesheets/palette.06af60db.min.css create mode 100644 assets/stylesheets/palette.06af60db.min.css.map create mode 100644 blog/index.html create mode 100644 community/contribution_guide/index.html create mode 100644 community/index.html create mode 100644 community/resources/index.html create mode 100644 cookbook/cds_sandbox/index.html create mode 100644 cookbook/index.html create mode 100644 cookbook/notereader_sandbox/index.html create mode 100644 distribution/index.html create mode 100644 index.html create mode 100644 installation/index.html create mode 100644 objects.inv create mode 100644 quickstart/index.html create mode 100644 reference/index.html create mode 100644 reference/pipeline/components/cdscardcreator/index.html create mode 100644 reference/pipeline/components/components/index.html create mode 100644 reference/pipeline/connectors/cdaconnector/index.html create mode 100644 reference/pipeline/connectors/cdsfhirconnector/index.html create mode 100644 reference/pipeline/connectors/connectors/index.html create mode 100644 reference/pipeline/data_container/index.html create mode 100644 reference/pipeline/integrations/integrations/index.html create mode 100644 reference/pipeline/pipeline/index.html create mode 100644 reference/pipeline/prebuilt_pipelines/medicalcoding/index.html create mode 100644 reference/pipeline/prebuilt_pipelines/summarization/index.html create mode 100644 reference/sandbox/client/index.html create mode 100644 reference/sandbox/sandbox/index.html create mode 100644 reference/sandbox/service/index.html create mode 100644 reference/sandbox/use_cases/cds/index.html create mode 100644 reference/sandbox/use_cases/clindoc/index.html create mode 100644 reference/sandbox/use_cases/use_cases/index.html create mode 100644 reference/utilities/cda_parser/index.html create mode 100644 reference/utilities/data_generator/index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 stylesheets/extra.css diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..666efeb --- /dev/null +++ b/404.html @@ -0,0 +1,1921 @@ + + + + + + + + + + + + + + + + + + + + + + + HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/cda_parser/index.html b/api/cda_parser/index.html new file mode 100644 index 0000000..623700e --- /dev/null +++ b/api/cda_parser/index.html @@ -0,0 +1,5714 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + CDA Parser - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

CDA Parser

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdaAnnotator + + +

+ + +
+ + +

Annotates a Clinical Document Architecture (CDA) document.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ cda_data + +
+

The CDA document data.

+
+

+ + TYPE: + ClinicalDocument + +

+
+ fallback + +
+

The fallback value. Defaults to "LLM".

+
+

+ + TYPE: + str + + + DEFAULT: + 'LLM' + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
clinical_document +
+

The CDA document data.

+
+

+ + TYPE: + ClinicalDocument + +

+
fallback +
+

The fallback value.

+
+

+ + TYPE: + str + +

+
problem_list +
+

The list of problems extracted from the CDA document.

+
+

+ + TYPE: + List[ProblemConcept] + +

+
medication_list +
+

The list of medications extracted from the CDA document.

+
+

+ + TYPE: + List[MedicationConcept] + +

+
allergy_list +
+

The list of allergies extracted from the CDA document.

+
+

+ + TYPE: + List[AllergyConcept] + +

+
note +
+

The note extracted from the CDA document.

+
+

+ + TYPE: + str + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
from_dict +
+

Dict): Creates a CdaAnnotator instance from a dictionary.

+
+
from_xml +
+

str): Creates a CdaAnnotator instance from an XML string.

+
+
add_to_problem_list +
+

List[ProblemConcept], overwrite: bool = False) -> None: Adds a list of problem concepts to the problems section.

+
+
export +
+

bool = True) -> str: Exports the CDA document as an XML string.

+
+
+ + + + + + +
+ Source code in healthchain/cda_parser/cdaannotator.py +
 158
+ 159
+ 160
+ 161
+ 162
+ 163
+ 164
+ 165
+ 166
+ 167
+ 168
+ 169
+ 170
+ 171
+ 172
+ 173
+ 174
+ 175
+ 176
+ 177
+ 178
+ 179
+ 180
+ 181
+ 182
+ 183
+ 184
+ 185
+ 186
+ 187
+ 188
+ 189
+ 190
+ 191
+ 192
+ 193
+ 194
+ 195
+ 196
+ 197
+ 198
+ 199
+ 200
+ 201
+ 202
+ 203
+ 204
+ 205
+ 206
+ 207
+ 208
+ 209
+ 210
+ 211
+ 212
+ 213
+ 214
+ 215
+ 216
+ 217
+ 218
+ 219
+ 220
+ 221
+ 222
+ 223
+ 224
+ 225
+ 226
+ 227
+ 228
+ 229
+ 230
+ 231
+ 232
+ 233
+ 234
+ 235
+ 236
+ 237
+ 238
+ 239
+ 240
+ 241
+ 242
+ 243
+ 244
+ 245
+ 246
+ 247
+ 248
+ 249
+ 250
+ 251
+ 252
+ 253
+ 254
+ 255
+ 256
+ 257
+ 258
+ 259
+ 260
+ 261
+ 262
+ 263
+ 264
+ 265
+ 266
+ 267
+ 268
+ 269
+ 270
+ 271
+ 272
+ 273
+ 274
+ 275
+ 276
+ 277
+ 278
+ 279
+ 280
+ 281
+ 282
+ 283
+ 284
+ 285
+ 286
+ 287
+ 288
+ 289
+ 290
+ 291
+ 292
+ 293
+ 294
+ 295
+ 296
+ 297
+ 298
+ 299
+ 300
+ 301
+ 302
+ 303
+ 304
+ 305
+ 306
+ 307
+ 308
+ 309
+ 310
+ 311
+ 312
+ 313
+ 314
+ 315
+ 316
+ 317
+ 318
+ 319
+ 320
+ 321
+ 322
+ 323
+ 324
+ 325
+ 326
+ 327
+ 328
+ 329
+ 330
+ 331
+ 332
+ 333
+ 334
+ 335
+ 336
+ 337
+ 338
+ 339
+ 340
+ 341
+ 342
+ 343
+ 344
+ 345
+ 346
+ 347
+ 348
+ 349
+ 350
+ 351
+ 352
+ 353
+ 354
+ 355
+ 356
+ 357
+ 358
+ 359
+ 360
+ 361
+ 362
+ 363
+ 364
+ 365
+ 366
+ 367
+ 368
+ 369
+ 370
+ 371
+ 372
+ 373
+ 374
+ 375
+ 376
+ 377
+ 378
+ 379
+ 380
+ 381
+ 382
+ 383
+ 384
+ 385
+ 386
+ 387
+ 388
+ 389
+ 390
+ 391
+ 392
+ 393
+ 394
+ 395
+ 396
+ 397
+ 398
+ 399
+ 400
+ 401
+ 402
+ 403
+ 404
+ 405
+ 406
+ 407
+ 408
+ 409
+ 410
+ 411
+ 412
+ 413
+ 414
+ 415
+ 416
+ 417
+ 418
+ 419
+ 420
+ 421
+ 422
+ 423
+ 424
+ 425
+ 426
+ 427
+ 428
+ 429
+ 430
+ 431
+ 432
+ 433
+ 434
+ 435
+ 436
+ 437
+ 438
+ 439
+ 440
+ 441
+ 442
+ 443
+ 444
+ 445
+ 446
+ 447
+ 448
+ 449
+ 450
+ 451
+ 452
+ 453
+ 454
+ 455
+ 456
+ 457
+ 458
+ 459
+ 460
+ 461
+ 462
+ 463
+ 464
+ 465
+ 466
+ 467
+ 468
+ 469
+ 470
+ 471
+ 472
+ 473
+ 474
+ 475
+ 476
+ 477
+ 478
+ 479
+ 480
+ 481
+ 482
+ 483
+ 484
+ 485
+ 486
+ 487
+ 488
+ 489
+ 490
+ 491
+ 492
+ 493
+ 494
+ 495
+ 496
+ 497
+ 498
+ 499
+ 500
+ 501
+ 502
+ 503
+ 504
+ 505
+ 506
+ 507
+ 508
+ 509
+ 510
+ 511
+ 512
+ 513
+ 514
+ 515
+ 516
+ 517
+ 518
+ 519
+ 520
+ 521
+ 522
+ 523
+ 524
+ 525
+ 526
+ 527
+ 528
+ 529
+ 530
+ 531
+ 532
+ 533
+ 534
+ 535
+ 536
+ 537
+ 538
+ 539
+ 540
+ 541
+ 542
+ 543
+ 544
+ 545
+ 546
+ 547
+ 548
+ 549
+ 550
+ 551
+ 552
+ 553
+ 554
+ 555
+ 556
+ 557
+ 558
+ 559
+ 560
+ 561
+ 562
+ 563
+ 564
+ 565
+ 566
+ 567
+ 568
+ 569
+ 570
+ 571
+ 572
+ 573
+ 574
+ 575
+ 576
+ 577
+ 578
+ 579
+ 580
+ 581
+ 582
+ 583
+ 584
+ 585
+ 586
+ 587
+ 588
+ 589
+ 590
+ 591
+ 592
+ 593
+ 594
+ 595
+ 596
+ 597
+ 598
+ 599
+ 600
+ 601
+ 602
+ 603
+ 604
+ 605
+ 606
+ 607
+ 608
+ 609
+ 610
+ 611
+ 612
+ 613
+ 614
+ 615
+ 616
+ 617
+ 618
+ 619
+ 620
+ 621
+ 622
+ 623
+ 624
+ 625
+ 626
+ 627
+ 628
+ 629
+ 630
+ 631
+ 632
+ 633
+ 634
+ 635
+ 636
+ 637
+ 638
+ 639
+ 640
+ 641
+ 642
+ 643
+ 644
+ 645
+ 646
+ 647
+ 648
+ 649
+ 650
+ 651
+ 652
+ 653
+ 654
+ 655
+ 656
+ 657
+ 658
+ 659
+ 660
+ 661
+ 662
+ 663
+ 664
+ 665
+ 666
+ 667
+ 668
+ 669
+ 670
+ 671
+ 672
+ 673
+ 674
+ 675
+ 676
+ 677
+ 678
+ 679
+ 680
+ 681
+ 682
+ 683
+ 684
+ 685
+ 686
+ 687
+ 688
+ 689
+ 690
+ 691
+ 692
+ 693
+ 694
+ 695
+ 696
+ 697
+ 698
+ 699
+ 700
+ 701
+ 702
+ 703
+ 704
+ 705
+ 706
+ 707
+ 708
+ 709
+ 710
+ 711
+ 712
+ 713
+ 714
+ 715
+ 716
+ 717
+ 718
+ 719
+ 720
+ 721
+ 722
+ 723
+ 724
+ 725
+ 726
+ 727
+ 728
+ 729
+ 730
+ 731
+ 732
+ 733
+ 734
+ 735
+ 736
+ 737
+ 738
+ 739
+ 740
+ 741
+ 742
+ 743
+ 744
+ 745
+ 746
+ 747
+ 748
+ 749
+ 750
+ 751
+ 752
+ 753
+ 754
+ 755
+ 756
+ 757
+ 758
+ 759
+ 760
+ 761
+ 762
+ 763
+ 764
+ 765
+ 766
+ 767
+ 768
+ 769
+ 770
+ 771
+ 772
+ 773
+ 774
+ 775
+ 776
+ 777
+ 778
+ 779
+ 780
+ 781
+ 782
+ 783
+ 784
+ 785
+ 786
+ 787
+ 788
+ 789
+ 790
+ 791
+ 792
+ 793
+ 794
+ 795
+ 796
+ 797
+ 798
+ 799
+ 800
+ 801
+ 802
+ 803
+ 804
+ 805
+ 806
+ 807
+ 808
+ 809
+ 810
+ 811
+ 812
+ 813
+ 814
+ 815
+ 816
+ 817
+ 818
+ 819
+ 820
+ 821
+ 822
+ 823
+ 824
+ 825
+ 826
+ 827
+ 828
+ 829
+ 830
+ 831
+ 832
+ 833
+ 834
+ 835
+ 836
+ 837
+ 838
+ 839
+ 840
+ 841
+ 842
+ 843
+ 844
+ 845
+ 846
+ 847
+ 848
+ 849
+ 850
+ 851
+ 852
+ 853
+ 854
+ 855
+ 856
+ 857
+ 858
+ 859
+ 860
+ 861
+ 862
+ 863
+ 864
+ 865
+ 866
+ 867
+ 868
+ 869
+ 870
+ 871
+ 872
+ 873
+ 874
+ 875
+ 876
+ 877
+ 878
+ 879
+ 880
+ 881
+ 882
+ 883
+ 884
+ 885
+ 886
+ 887
+ 888
+ 889
+ 890
+ 891
+ 892
+ 893
+ 894
+ 895
+ 896
+ 897
+ 898
+ 899
+ 900
+ 901
+ 902
+ 903
+ 904
+ 905
+ 906
+ 907
+ 908
+ 909
+ 910
+ 911
+ 912
+ 913
+ 914
+ 915
+ 916
+ 917
+ 918
+ 919
+ 920
+ 921
+ 922
+ 923
+ 924
+ 925
+ 926
+ 927
+ 928
+ 929
+ 930
+ 931
+ 932
+ 933
+ 934
+ 935
+ 936
+ 937
+ 938
+ 939
+ 940
+ 941
+ 942
+ 943
+ 944
+ 945
+ 946
+ 947
+ 948
+ 949
+ 950
+ 951
+ 952
+ 953
+ 954
+ 955
+ 956
+ 957
+ 958
+ 959
+ 960
+ 961
+ 962
+ 963
+ 964
+ 965
+ 966
+ 967
+ 968
+ 969
+ 970
+ 971
+ 972
+ 973
+ 974
+ 975
+ 976
+ 977
+ 978
+ 979
+ 980
+ 981
+ 982
+ 983
+ 984
+ 985
+ 986
+ 987
+ 988
+ 989
+ 990
+ 991
+ 992
+ 993
+ 994
+ 995
+ 996
+ 997
+ 998
+ 999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
class CdaAnnotator:
+    """
+    Annotates a Clinical Document Architecture (CDA) document.
+
+    Args:
+        cda_data (ClinicalDocument): The CDA document data.
+        fallback (str, optional): The fallback value. Defaults to "LLM".
+
+    Attributes:
+        clinical_document (ClinicalDocument): The CDA document data.
+        fallback (str): The fallback value.
+        problem_list (List[ProblemConcept]): The list of problems extracted from the CDA document.
+        medication_list (List[MedicationConcept]): The list of medications extracted from the CDA document.
+        allergy_list (List[AllergyConcept]): The list of allergies extracted from the CDA document.
+        note (str): The note extracted from the CDA document.
+
+    Methods:
+        from_dict(cls, data: Dict): Creates a CdaAnnotator instance from a dictionary.
+        from_xml(cls, data: str): Creates a CdaAnnotator instance from an XML string.
+        add_to_problem_list(problems: List[ProblemConcept], overwrite: bool = False) -> None: Adds a list of problem concepts to the problems section.
+        export(pretty_print: bool = True) -> str: Exports the CDA document as an XML string.
+    """
+
+    def __init__(self, cda_data: ClinicalDocument, fallback="LLM") -> None:
+        self.clinical_document = cda_data
+        self.fallback = fallback
+        self._get_ccd_sections()
+        self._extract_data()
+
+    @classmethod
+    def from_dict(cls, data: Dict) -> "CdaAnnotator":
+        """
+        Creates an instance of the class from a dictionary.
+
+        Args:
+            data (Dict): The dictionary containing the dictionary representation of the cda xml (using xmltodict.parse).
+
+        Returns:
+            CdaAnnotator: An instance of the class initialized with the data from the dictionary.
+        """
+        clinical_document_model = ClinicalDocument(**data.get("ClinicalDocument", {}))
+        return cls(cda_data=clinical_document_model)
+
+    @classmethod
+    def from_xml(cls, data: str) -> "CdaAnnotator":
+        """
+        Creates an instance of the CDAAnnotator class from an XML string.
+
+        Args:
+            data (str): The XML string representing the CDA document.
+
+        Returns:
+            CDAAnnotator: An instance of the CDAAnnotator class initialized with the parsed CDA data.
+        """
+        cda_dict = xmltodict.parse(data)
+        clinical_document_model = ClinicalDocument(
+            **cda_dict.get("ClinicalDocument", {})
+        )
+        return cls(cda_data=clinical_document_model)
+
+    def __str__(self):
+        problems = ""
+        allergies = ""
+        medications = ""
+
+        if self.problem_list:
+            problems = "\n".join(
+                [problem.model_dump_json() for problem in self.problem_list]
+            )
+        if self.allergy_list:
+            allergies = "\n".join(
+                [allergy.model_dump_json() for allergy in self.allergy_list]
+            )
+        if self.medication_list:
+            medications = "\n".join(
+                [medication.model_dump_json() for medication in self.medication_list]
+            )
+
+        return problems + allergies + medications
+
+    def _get_ccd_sections(self) -> None:
+        """
+        Retrieves the different sections of the CCD document.
+
+        This method finds and assigns the problem section, medication section,
+        allergy section, and note section of the CCD document.
+
+        Returns:
+            None
+        """
+        self._problem_section = self._find_problems_section()
+        self._medication_section = self._find_medications_section()
+        self._allergy_section = self._find_allergies_section()
+        self._note_section = self._find_notes_section()
+
+    def _extract_data(self) -> None:
+        """
+        Extracts data from the CDA document and assigns it to instance variables.
+
+        This method extracts problem list, medication list, allergy list, and note from the CDA document
+        and assigns them to the corresponding instance variables.
+
+        Returns:
+            None
+        """
+        self.problem_list: List[ProblemConcept] = self._extract_problems()
+        self.medication_list: List[MedicationConcept] = self._extract_medications()
+        self.allergy_list: List[AllergyConcept] = self._extract_allergies()
+        self.note: str = self._extract_note()
+
+    def _find_section_by_code(self, section_code: str) -> Optional[Section]:
+        """
+        Finds a section in the clinical document by its code value.
+
+        Args:
+            section_code (str): The code of the section to find.
+
+        Returns:
+            Optional[Section]: The section with the specified code, or None if not found.
+        """
+        components = self.clinical_document.component.structuredBody.component
+
+        if not isinstance(components, list):
+            components = [components]
+
+        for component in components:
+            code = component.section.code.code
+
+            if code is None:
+                continue
+            if code == section_code:
+                return component.section
+        log.warning(f"unable to find section with code {section_code}")
+        return None
+
+    def _find_section_by_template_id(self, section_id: str) -> Optional[Section]:
+        """
+        Finds a section in the clinical document by its template ID.
+
+        Args:
+            section_id (str): The template ID of the section to find.
+
+        Returns:
+            Optional[Section]: The section with the specified template ID, or None if not found.
+        """
+        # NOTE not all CDAs have template ids in each section (don't ask me why)
+        # TODO: It's probably safer to parse by 'code' which is a required field
+        components = self.clinical_document.component.structuredBody.component
+        # Ensure components is a list
+        if not isinstance(components, list):
+            components = [components]
+
+        for component in components:
+            template_ids = component.section.templateId
+            if template_ids is None:
+                continue
+
+            if isinstance(template_ids, list):
+                for template_id in template_ids:
+                    if template_id.root == section_id:
+                        return component.section
+            elif template_ids.root == section_id:
+                return component.section
+
+        log.warning(f"Unable to find section templateId {section_id}")
+
+        return None
+
+    def _find_problems_section(self) -> Optional[Section]:
+        return self._find_section_by_template_id(
+            SectionId.PROBLEM.value
+        ) or self._find_section_by_code(SectionCode.PROBLEM.value)
+
+    def _find_medications_section(self) -> Optional[Section]:
+        return self._find_section_by_template_id(
+            SectionId.MEDICATION.value
+        ) or self._find_section_by_code(SectionCode.MEDICATION.value)
+
+    def _find_allergies_section(self) -> Optional[Section]:
+        return self._find_section_by_template_id(
+            SectionId.ALLERGY.value
+        ) or self._find_section_by_code(SectionCode.ALLERGY.value)
+
+    def _find_notes_section(self) -> Optional[Section]:
+        return self._find_section_by_template_id(
+            SectionId.NOTE.value
+        ) or self._find_section_by_code(SectionCode.NOTE.value)
+
+    def _extract_problems(self) -> List[ProblemConcept]:
+        """
+        Extracts problem concepts from the problem section of the CDA document.
+
+        Returns:
+            A list of ProblemConcept objects representing the extracted problem concepts.
+        """
+        if not self._problem_section:
+            log.warning("Empty problem section!")
+            return []
+
+        concepts = []
+
+        def get_problem_concept_from_cda_data_field(value: Dict) -> ProblemConcept:
+            concept = ProblemConcept(_standard="cda")
+            concept.code = value.get("@code")
+            concept.code_system = value.get("@codeSystem")
+            concept.code_system_name = value.get("@codeSystemName")
+            concept.display_name = value.get("@displayName")
+
+            return concept
+
+        entries = (
+            self._problem_section.entry
+            if isinstance(self._problem_section.entry, list)
+            else [self._problem_section.entry]
+        )
+
+        for entry in entries:
+            entry_relationship = entry.act.entryRelationship
+            values = get_value_from_entry_relationship(entry_relationship)
+            for value in values:
+                concept = get_problem_concept_from_cda_data_field(value)
+                concepts.append(concept)
+
+        return concepts
+
+    def _extract_medications(self) -> List[MedicationConcept]:
+        """
+        Extracts medication concepts from the medication section of the CDA document.
+
+        Returns:
+            A list of MedicationConcept objects representing the extracted medication concepts.
+        """
+        if not self._medication_section:
+            log.warning("Empty medication section!")
+            return []
+
+        def get_medication_concept_from_cda_data_field(
+            code: CD,
+            dose_quantity: Optional[IVL_PQ],
+            route_code: Optional[CE],
+            effective_times: Optional[Union[List[Dict], Dict]],
+            precondition: Optional[Dict],
+        ) -> MedicationConcept:
+            concept = MedicationConcept(_standard="cda")
+            concept.code = code.code
+            concept.code_system = code.codeSystem
+            concept.code_system_name = code.codeSystemName
+            concept.display_name = code.displayName
+
+            if dose_quantity:
+                concept.dosage = Quantity(
+                    _source=dose_quantity.model_dump(),
+                    value=dose_quantity.value,
+                    unit=dose_quantity.unit,
+                )
+            if route_code:
+                concept.route = Concept(
+                    code=route_code.code,
+                    code_system=route_code.codeSystem,
+                    code_system_name=route_code.codeSystemName,
+                    display_name=route_code.displayName,
+                )
+            if effective_times:
+                effective_times = (
+                    effective_times
+                    if isinstance(effective_times, list)
+                    else [effective_times]
+                )
+                # TODO: could refactor this into a pydantic validator
+                for effective_time in effective_times:
+                    if effective_time.get("@xsi:type") == "IVL_TS":
+                        concept.duration = get_time_range_from_cda_value(effective_time)
+                        concept.duration._source = effective_time
+                    elif effective_time.get("@xsi:type") == "PIVL_TS":
+                        period = effective_time.get("period")
+                        if period:
+                            concept.frequency = TimeInterval(
+                                period=Quantity(
+                                    value=period.get("@value"), unit=period.get("@unit")
+                                ),
+                                institution_specified=effective_time.get(
+                                    "@institutionSpecified"
+                                ),
+                            )
+                        concept.frequency._source = effective_time
+
+            # TODO: this is read-only for now! can also extract status, translations, supply in entryRelationships
+            if precondition:
+                concept.precondition = precondition.model_dump(
+                    exclude_none=True, by_alias=True
+                )
+
+            return concept
+
+        def get_medication_details_from_substance_administration(
+            substance_administration: SubstanceAdministration,
+        ) -> Tuple[
+            Optional[CD],
+            Optional[CE],
+            Optional[IVL_PQ],
+            Optional[Union[List[Dict], Dict]],
+            Optional[Dict],
+        ]:
+            # Get the medication code from the consumable
+            consumable = substance_administration.consumable
+            manufactured_product = (
+                consumable.manufacturedProduct if consumable else None
+            )
+            manufactured_material = (
+                manufactured_product.manufacturedMaterial
+                if manufactured_product
+                else None
+            )
+            code = manufactured_material.code if manufactured_material else None
+
+            return (
+                code,
+                substance_administration.doseQuantity,
+                substance_administration.routeCode,
+                substance_administration.effectiveTime,
+                substance_administration.precondition,
+            )
+
+        concepts = []
+
+        entries = (
+            self._medication_section.entry
+            if isinstance(self._medication_section.entry, list)
+            else [self._medication_section.entry]
+        )
+        for entry in entries:
+            substance_administration = entry.substanceAdministration
+            if not substance_administration:
+                log.warning("Substance administration not found in entry.")
+                continue
+            code, dose_quantity, route_code, effective_times, precondition = (
+                get_medication_details_from_substance_administration(
+                    substance_administration
+                )
+            )
+            if not code:
+                log.warning("Code not found in the consumable")
+                continue
+            concept = get_medication_concept_from_cda_data_field(
+                code, dose_quantity, route_code, effective_times, precondition
+            )
+            concepts.append(concept)
+
+        return concepts
+
+    def _extract_allergies(self) -> List[AllergyConcept]:
+        if not self._allergy_section:
+            log.warning("Empty allergy section!")
+            return []
+
+        concepts = []
+
+        def get_allergy_concept_from_cda_data_fields(
+            value: Dict,
+            allergen_name: str,
+            allergy_type: CD,
+            reaction: Dict,
+            severity: Dict,
+        ) -> AllergyConcept:
+            concept = AllergyConcept(_standard="cda")
+            concept.code = value.get("@code")
+            concept.code_system = value.get("@codeSystem")
+            concept.code_system_name = value.get("@codeSystemName")
+            concept.display_name = value.get("@displayName")
+            if concept.display_name is None:
+                concept.display_name = allergen_name
+
+            if allergy_type:
+                concept.allergy_type = Concept()
+                concept.allergy_type.code = allergy_type.code
+                concept.allergy_type.code_system = allergy_type.codeSystem
+                concept.allergy_type.code_system_name = allergy_type.codeSystemName
+                concept.allergy_type.display_name = allergy_type.displayName
+
+            if reaction:
+                concept.reaction = Concept()
+                concept.reaction.code = reaction.get("@code")
+                concept.reaction.code_system = reaction.get("@codeSystem")
+                concept.reaction.code_system_name = reaction.get("@codeSystemName")
+                concept.reaction.display_name = reaction.get("@displayName")
+
+            if severity:
+                concept.severity = Concept()
+                concept.severity.code = severity.get("@code")
+                concept.severity.code_system = severity.get("@codeSystem")
+                concept.severity.code_system_name = severity.get("@codeSystemName")
+                concept.severity.display_name = severity.get("@displayName")
+
+            return concept
+
+        def get_allergy_details_from_entry_relationship(
+            entry_relationship: EntryRelationship,
+        ) -> Tuple[str, CD, Dict, Dict]:
+            allergen_name = None
+            allergy_type = None
+            reaction = None
+            severity = None
+
+            # TODO: Improve this
+
+            entry_relationships = (
+                entry_relationship
+                if isinstance(entry_relationship, list)
+                else [entry_relationship]
+            )
+            for entry_relationship in entry_relationships:
+                if check_for_entry_observation(entry_relationship):
+                    allergy_type = entry_relationship.observation.code
+                    observation = entry_relationship.observation
+                    allergen_name = (
+                        observation.participant.participantRole.playingEntity.name
+                    )
+
+                    if check_for_entry_observation(observation):
+                        observation_entry_relationships = (
+                            observation.entryRelationship
+                            if isinstance(observation.entryRelationship, list)
+                            else [observation.entryRelationship]
+                        )
+                        for observation_entry_rel in observation_entry_relationships:
+                            if check_has_template_id(
+                                observation_entry_rel.observation,
+                                "1.3.6.1.4.1.19376.1.5.3.1.4.5",
+                            ):
+                                reaction = observation_entry_rel.observation.value
+
+                        if check_for_entry_observation(
+                            observation_entry_rel.observation
+                        ):
+                            if check_has_template_id(
+                                observation_entry_rel.observation.entryRelationship.observation,
+                                "1.3.6.1.4.1.19376.1.5.3.1.4.1",
+                            ):
+                                severity = observation_entry_rel.observation.entryRelationship.observation.value
+
+            return allergen_name, allergy_type, reaction, severity
+
+        entries = (
+            self._allergy_section.entry
+            if isinstance(self._allergy_section.entry, list)
+            else [self._allergy_section.entry]
+        )
+        for entry in entries:
+            entry_relationship = entry.act.entryRelationship
+            values = get_value_from_entry_relationship(entry_relationship)
+
+            allergen_name, allergy_type, reaction, severity = (
+                get_allergy_details_from_entry_relationship(entry_relationship)
+            )
+
+            for value in values:
+                concept = get_allergy_concept_from_cda_data_fields(
+                    value, allergen_name, allergy_type, reaction, severity
+                )
+                concepts.append(concept)
+
+        return concepts
+
+    def _extract_note(self) -> str:
+        """
+        Extracts the note section from the CDA document.
+
+        Returns:
+            str: The extracted note section as a string.
+        """
+        # TODO: need to handle / escape html tags within the note section, parse with right field
+        if not self._note_section:
+            log.warning("Empty notes section!")
+            return []
+
+        return self._note_section.text
+
+    def _add_new_problem_entry(
+        self,
+        new_problem: ProblemConcept,
+        timestamp: str,
+        act_id: str,
+        problem_reference_name: str,
+    ) -> None:
+        """
+        Adds a new problem entry to the problem section of the CDA document.
+
+        Args:
+            new_problem (ProblemConcept): The new problem concept to be added.
+            timestamp (str): The timestamp of the entry.
+            act_id (str): The ID of the act.
+            problem_reference_name (str): The reference name of the problem.
+
+        Returns:
+            None
+        """
+        # TODO: This will need work
+        template = {
+            "act": {
+                "@classCode": "ACT",
+                "@moodCode": "EVN",
+                "templateId": [
+                    {"@root": "2.16.840.1.113883.10.20.1.27"},
+                    {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.5.1"},
+                    {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.5.2"},
+                    {"@root": "2.16.840.1.113883.3.88.11.32.7"},
+                    {"@root": "2.16.840.1.113883.3.88.11.83.7"},
+                ],
+                "id": {"@root": act_id},
+                "code": {"@nullflavor": "NA"},
+                "statusCode": {"@code": "active"},
+                "effectiveTime": {"low": {"@value": timestamp}},
+                "entryRelationship": {
+                    "@typeCode": "SUBJ",
+                    "@inversionInd": False,
+                    "observation": {
+                        "@classCode": "OBS",
+                        "@moodCode": "EVN",
+                        "templateId": [
+                            {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.5"},
+                            {"@root": "2.16.840.1.113883.10.20.1.28"},
+                        ],
+                        "id": {"@root": act_id},
+                        "code": {
+                            "@code": "55607006",
+                            "@codeSystem": "2.16.840.1.113883.6.96",
+                            "@codeSystemName": "SNOMED CT",
+                            "@displayName": "Problem",
+                        },
+                        "text": {"reference": {"@value": problem_reference_name}},
+                        "value": {
+                            "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+                            "@code": new_problem.code,
+                            "@codeSystem": new_problem.code_system,
+                            "@codeSystemName": new_problem.code_system_name,
+                            "@displayName": new_problem.display_name,
+                            "originalText": {
+                                "reference": {"@value": problem_reference_name}
+                            },
+                            "@xsi:type": "CD",
+                        },
+                        "statusCode": {"@code": "completed"},
+                        "effectiveTime": {"low": {"@value": timestamp}},
+                        "entryRelationship": {
+                            "@typeCode": "REFR",
+                            "observation": {
+                                "@classCode": "OBS",
+                                "@moodCode": "EVN",
+                                "code": {
+                                    "@code": "33999-4",
+                                    "@codeSystem": "2.16.840.1.113883.6.1",
+                                    "@displayName": "Status",
+                                },
+                                "value": {
+                                    "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+                                    "@code": "55561003",
+                                    "@codeSystem": "2.16.840.1.113883.6.96",
+                                    "@displayName": "Active",
+                                    "@xsi:type": "CE",
+                                },
+                                "statusCode": {"@code": "completed"},
+                                "effectiveTime": {"low": {"@value": timestamp}},
+                            },
+                        },
+                    },
+                },
+            }
+        }
+        if not isinstance(self._problem_section.entry, list):
+            self._problem_section.entry = [self._problem_section.entry]
+
+        new_entry = Entry(**template)
+        self._problem_section.entry.append(new_entry)
+
+    def add_to_problem_list(
+        self, problems: List[ProblemConcept], overwrite: bool = False
+    ) -> None:
+        """
+        Adds a list of problem lists to the problems section.
+
+        Args:
+            problems (List[ProblemConcept]): A list of problem concepts to be added.
+            overwrite (bool, optional): If True, the existing problem list will be overwritten.
+                Defaults to False.
+
+        Returns:
+            None
+        """
+        if self._problem_section is None:
+            log.warning(
+                "Skipping: No problem section to add to, check your CDA configuration"
+            )
+            return
+
+        timestamp = datetime.now().strftime(format="%Y%m%d")
+        act_id = str(uuid.uuid4())
+        problem_reference_name = "#p" + str(uuid.uuid4())[:8] + "name"
+
+        if overwrite:
+            self._problem_section.entry = []
+
+        added_problems = []
+
+        for problem in problems:
+            if problem in self.problem_list:
+                log.debug(
+                    f"Skipping: Problem {problem.display_name} already exists in the problem list."
+                )
+                continue
+            log.debug(f"Adding problem: {problem}")
+            self._add_new_problem_entry(
+                new_problem=problem,
+                timestamp=timestamp,
+                act_id=act_id,
+                problem_reference_name=problem_reference_name,
+            )
+            added_problems.append(problem)
+
+        if overwrite:
+            self.problem_list = added_problems
+        else:
+            self.problem_list.extend(added_problems)
+
+    def _add_new_medication_entry(
+        self,
+        new_medication: MedicationConcept,
+        timestamp: str,
+        subad_id: str,
+        medication_reference_name: str,
+    ):
+        effective_times = []
+        if new_medication.frequency:
+            effective_times.append(
+                {
+                    "@xsi:type": "PIVL_TS",
+                    "@institutionSpecified": new_medication.frequency.institution_specified,
+                    "@operator": "A",
+                    "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+                    "period": {
+                        "@unit": new_medication.frequency.period.unit,
+                        "@value": new_medication.frequency.period.value,
+                    },
+                }
+            )
+        if new_medication.duration:
+            low = {"@nullFlavor": "UNK"}
+            high = {"@nullFlavor": "UNK"}
+            if new_medication.duration.low:
+                low = {"@value": new_medication.duration.low.value}
+            if new_medication.duration.high:
+                high = {"@value": new_medication.duration.high.value}
+            effective_times.append(
+                {
+                    "@xsi:type": "IVL_TS",
+                    "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+                    "low": low,
+                    "high": high,
+                }
+            )
+
+        if len(effective_times) == 1:
+            effective_times = effective_times[0]
+
+        template = {
+            "substanceAdministration": {
+                "@classCode": "SBADM",
+                "@moodCode": "INT",
+                "templateId": [
+                    {"@root": "2.16.840.1.113883.10.20.1.24"},
+                    {"@root": "2.16.840.1.113883.3.88.11.83.8"},
+                    {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.7"},
+                    {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.7.1"},
+                    {"@root": "2.16.840.1.113883.3.88.11.32.8"},
+                ],
+                "id": {"@root": subad_id},
+                "statusCode": {"@code": "completed"},
+            }
+        }
+        # Add dosage, route, duration, frequency
+        if effective_times:
+            template["substanceAdministration"]["effectiveTime"] = effective_times
+        if new_medication.route:
+            template["substanceAdministration"]["routeCode"] = {
+                "@code": new_medication.route.code,
+                "@codeSystem": new_medication.route.code_system,
+                "@codeSystemDisplayName": new_medication.route.code_system_name,
+                "@displayName": new_medication.route.display_name,
+            }
+        if new_medication.dosage:
+            template["substanceAdministration"]["doseQuantity"] = {
+                "@value": new_medication.dosage.value,
+                "@unit": new_medication.dosage.unit,
+            }
+
+        # Add medication entry
+        template["substanceAdministration"]["consumable"] = {
+            "@typeCode": "CSM",
+            "manufacturedProduct": {
+                "@classCode": "MANU",
+                "templateId": [
+                    {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.7.2"},
+                    {"@root": "2.16.840.1.113883.10.20.1.53"},
+                    {"@root": "2.16.840.1.113883.3.88.11.32.9"},
+                    {"@root": "2.16.840.1.113883.3.88.11.83.8.2"},
+                ],
+                "manufacturedMaterial": {
+                    "code": {
+                        "@code": new_medication.code,
+                        "@codeSystem": new_medication.code_system,
+                        "@codeSystemName": new_medication.code_system_name,
+                        "@displayName": new_medication.display_name,
+                        "originalText": {
+                            "reference": {"@value": medication_reference_name}
+                        },
+                    }
+                },
+            },
+        }
+
+        # Add an Active status
+        template["substanceAdministration"]["entryRelationship"] = (
+            {
+                "@typeCode": "REFR",
+                "observation": {
+                    "@classCode": "OBS",
+                    "@moodCode": "EVN",
+                    "effectiveTime": {"low": {"@value": timestamp}},
+                    "templateId": {"@root": "2.16.840.1.113883.10.20.1.47"},
+                    "code": {
+                        "@code": "33999-4",
+                        "@codeSystem": "2.16.840.1.113883.6.1",
+                        "@codeSystemName": "LOINC",
+                        "@displayName": "Status",
+                    },
+                    "value": {
+                        "@code": "755561003",
+                        "@codeSystem": "2.16.840.1.113883.6.96",
+                        "@codeSystemName": "SNOMED CT",
+                        "@xsi:type": "CE",
+                        "@displayName": "Active",
+                        "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+                    },
+                    "statusCode": {"@code": "completed"},
+                },
+            },
+        )
+        template["substanceAdministration"]["precondition"] = (
+            new_medication.precondition
+        )
+
+        if not isinstance(self._medication_section.entry, list):
+            self._medication_section.entry = [self._medication_section.entry]
+
+        new_entry = Entry(**template)
+        self._medication_section.entry.append(new_entry)
+
+    def add_to_medication_list(
+        self, medications: List[MedicationConcept], overwrite: bool = False
+    ) -> None:
+        """
+        Adds medications to the medication list.
+
+        Args:
+            medications (List[MedicationConcept]): A list of MedicationConcept objects representing the medications to be added.
+            overwrite (bool, optional): If True, the existing medication list will be overwritten. Defaults to False.
+
+        Returns:
+            None
+        """
+        if self._medication_section is None:
+            log.warning(
+                "Skipping: No medication section to add to, check your CDA configuration"
+            )
+            return
+
+        timestamp = datetime.now().strftime(format="%Y%m%d")
+        subad_id = str(uuid.uuid4())
+        medication_reference_name = "#m" + str(uuid.uuid4())[:8] + "name"
+
+        if overwrite:
+            self._medication_section.entry = []
+
+        added_medications = []
+
+        for medication in medications:
+            if medication in self.medication_list:
+                log.debug(
+                    f"Skipping: medication {medication.display_name} already exists in the medication list."
+                )
+                continue
+            log.debug(f"Adding medication {medication}")
+            self._add_new_medication_entry(
+                new_medication=medication,
+                timestamp=timestamp,
+                subad_id=subad_id,
+                medication_reference_name=medication_reference_name,
+            )
+            added_medications.append(medication)
+
+        if overwrite:
+            self.medication_list = added_medications
+        else:
+            self.medication_list.extend(added_medications)
+
+    def _add_new_allergy_entry(
+        self,
+        new_allergy: AllergyConcept,
+        timestamp: str,
+        act_id: str,
+        allergy_reference_name: str,
+    ) -> None:
+        """
+        Adds a new allergy entry to the allergy section of the CDA document.
+
+        Args:
+            new_allergy (AllergyConcept): The new allergy concept to be added.
+            timestamp (str): The timestamp of the entry.
+            act_id (str): The ID of the act.
+            allergy_reference_name (str): The reference name of the allergy.
+
+        Returns:
+            None
+        """
+
+        template = {
+            "act": {
+                "@classCode": "ACT",
+                "@moodCode": "EVN",
+                "templateId": [
+                    {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.5.1"},
+                    {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.5.3"},
+                    {"@root": "2.16.840.1.113883.3.88.11.32.6"},
+                    {"@root": "2.16.840.1.113883.3.88.11.83.6"},
+                ],
+                "id": {"@root": act_id},
+                "code": {"@nullFlavor": "NA"},
+                "statusCode": {"@code": "active"},
+                "effectiveTime": {"low": {"@value": timestamp}},
+                "entryRelationship": {
+                    "@typeCode": "SUBJ",
+                    "@inversionInd": False,
+                    "observation": {
+                        "@classCode": "OBS",
+                        "@moodCode": "EVN",
+                        "templateId": [
+                            {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.5"},
+                            {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.6"},
+                            {"@root": "2.16.840.1.113883.10.20.1.18"},
+                            {
+                                "@root": "1.3.6.1.4.1.19376.1.5.3.1",
+                                "@extension": "allergy",
+                            },
+                            {"@root": "2.16.840.1.113883.10.20.1.28"},
+                        ],
+                        "id": {"@root": act_id},
+                        "text": {"reference": {"@value": allergy_reference_name}},
+                        "statusCode": {"@code": "completed"},
+                        "effectiveTime": {"low": {"@value": timestamp}},
+                    },
+                },
+            }
+        }
+        allergen_observation = template["act"]["entryRelationship"]["observation"]
+
+        # Attach allergy type code
+        if new_allergy.allergy_type:
+            allergen_observation["code"] = {
+                "@code": new_allergy.allergy_type.code,
+                "@codeSystem": new_allergy.allergy_type.code_system,
+                "@codeSystemName": new_allergy.allergy_type.code_system_name,
+                "@displayName": new_allergy.allergy_type.display_name,
+            }
+        else:
+            raise ValueError("Allergy_type code cannot be missing when adding allergy.")
+
+        # Attach allergen code to value and participant
+        allergen_observation["value"] = {
+            "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+            "@code": new_allergy.code,
+            "@codeSystem": new_allergy.code_system,
+            "@codeSystemName": new_allergy.code_system_name,
+            "@displayName": new_allergy.display_name,
+            "originalText": {"reference": {"@value": allergy_reference_name}},
+            "@xsi:type": "CD",
+        }
+
+        allergen_observation["participant"] = {
+            "@typeCode": "CSM",
+            "participantRole": {
+                "@classCode": "MANU",
+                "playingEntity": {
+                    "@classCode": "MMAT",
+                    "code": {
+                        "originalText": {
+                            "reference": {"@value": allergy_reference_name}
+                        },
+                        "@code": new_allergy.code,
+                        "@codeSystem": new_allergy.code_system,
+                        "@codeSystemName": new_allergy.code_system_name,
+                        "@displayName": new_allergy.display_name,
+                    },
+                    "name": new_allergy.display_name,
+                },
+            },
+        }
+
+        # We need an entryRelationship if either reaction or severity is present
+        if new_allergy.reaction or new_allergy.severity:
+            allergen_observation["entryRelationship"] = {
+                "@typeCode": "MFST",
+                "observation": {
+                    "@classCode": "OBS",
+                    "@moodCode": "EVN",
+                    "templateId": [
+                        {"@root": "2.16.840.1.113883.10.20.1.54"},
+                        {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.5"},
+                        {
+                            "@root": "1.3.6.1.4.1.19376.1.5.3.1.4.5",
+                            "@extension": "reaction",
+                        },
+                    ],
+                    "id": {"@root": act_id},
+                    "code": {"@code": "RXNASSESS"},
+                    "text": {
+                        "reference": {"@value": allergy_reference_name + "reaction"}
+                    },
+                    "statusCode": {"@code": "completed"},
+                    "effectiveTime": {"low": {"@value": timestamp}},
+                },
+            }
+            # Attach reaction code if given otherwise attach nullFlavor
+            if new_allergy.reaction:
+                allergen_observation["entryRelationship"]["observation"]["value"] = {
+                    "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+                    "@code": new_allergy.reaction.code,
+                    "@codeSystem": new_allergy.reaction.code_system,
+                    "@codeSystemName": new_allergy.reaction.code_system_name,
+                    "@displayName": new_allergy.reaction.display_name,
+                    "@xsi:type": "CD",
+                    "originalText": {
+                        "reference": {"@value": allergy_reference_name + "reaction"}
+                    },
+                }
+            else:
+                allergen_observation["entryRelationship"]["observation"]["value"] = {
+                    "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+                    "@nullFlavor": "OTH",
+                    "@xsi:type": "CD",
+                }
+            # Attach severity code if given
+            if new_allergy.severity:
+                allergen_observation["entryRelationship"]["observation"][
+                    "entryRelationship"
+                ] = {
+                    "@typeCode": "SUBJ",
+                    "observation": {
+                        "@classCode": "OBS",
+                        "@moodCode": "EVN",
+                        "templateId": [
+                            {"@root": "2.16.840.1.113883.10.20.1.55"},
+                            {"@root": "1.3.6.1.4.1.19376.1.5.3.1.4.1"},
+                        ],
+                        "code": {
+                            "@code": "SEV",
+                            "@codeSystem": "2.16.840.1.113883.5.4",
+                            "@codeSystemName": "ActCode",
+                            "@displayName": "Severity",
+                        },
+                        "text": {
+                            "reference": {"@value": allergy_reference_name + "severity"}
+                        },
+                        "statusCode": {"@code": "completed"},
+                        "value": {
+                            "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
+                            "@code": new_allergy.severity.code,
+                            "@codeSystem": new_allergy.severity.code_system,
+                            "@codeSystemName": new_allergy.severity.code_system_name,
+                            "@displayName": new_allergy.severity.display_name,
+                            "@xsi:type": "CD",
+                        },
+                    },
+                }
+
+        if not isinstance(self._allergy_section.entry, list):
+            self._allergy_section.entry = [self._allergy_section.entry]
+
+        new_entry = Entry(**template)
+        self._allergy_section.entry.append(new_entry)
+
+    def add_to_allergy_list(
+        self, allergies: List[AllergyConcept], overwrite: bool = False
+    ) -> None:
+        if self._allergy_section is None:
+            log.warning(
+                "Skipping: No allergy section to add to, check your CDA configuration"
+            )
+            return
+
+        timestamp = datetime.now().strftime(format="%Y%m%d")
+        act_id = str(uuid.uuid4())
+        allergy_reference_name = "#a" + str(uuid.uuid4())[:8] + "name"
+
+        if overwrite:
+            self._allergy_section.entry = []
+
+        added_allergies = []
+
+        for allergy in allergies:
+            if allergy in self.allergy_list:
+                log.debug(
+                    f"Skipping: Allergy {allergy.display_name} already exists in the allergy list."
+                )
+                continue
+            log.debug(f"Adding allergy: {allergy}")
+            self._add_new_allergy_entry(
+                new_allergy=allergy,
+                timestamp=timestamp,
+                act_id=act_id,
+                allergy_reference_name=allergy_reference_name,
+            )
+            added_allergies.append(allergy)
+
+        if overwrite:
+            self.allergy_list = added_allergies
+        else:
+            self.allergy_list.extend(added_allergies)
+
+    def export(self, pretty_print: bool = True) -> str:
+        """
+        Exports CDA document as an XML string
+        """
+        out_string = xmltodict.unparse(
+            {
+                "ClinicalDocument": self.clinical_document.model_dump(
+                    exclude_none=True, exclude_unset=True, by_alias=True
+                )
+            },
+            pretty=pretty_print,
+        )
+        # Fixes self closing tags - this is not strictly necessary, just looks more readable
+        pattern = r"(<(\w+)(\s+[^>]*?)?)></\2>"
+        export_xml = re.sub(pattern, r"\1/>", out_string)
+
+        return export_xml
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ add_to_medication_list(medications, overwrite=False) + +

+ + +
+ +

Adds medications to the medication list.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ medications + +
+

A list of MedicationConcept objects representing the medications to be added.

+
+

+ + TYPE: + List[MedicationConcept] + +

+
+ overwrite + +
+

If True, the existing medication list will be overwritten. Defaults to False.

+
+

+ + TYPE: + bool + + + DEFAULT: + False + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + None + + +
+

None

+
+
+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
def add_to_medication_list(
+    self, medications: List[MedicationConcept], overwrite: bool = False
+) -> None:
+    """
+    Adds medications to the medication list.
+
+    Args:
+        medications (List[MedicationConcept]): A list of MedicationConcept objects representing the medications to be added.
+        overwrite (bool, optional): If True, the existing medication list will be overwritten. Defaults to False.
+
+    Returns:
+        None
+    """
+    if self._medication_section is None:
+        log.warning(
+            "Skipping: No medication section to add to, check your CDA configuration"
+        )
+        return
+
+    timestamp = datetime.now().strftime(format="%Y%m%d")
+    subad_id = str(uuid.uuid4())
+    medication_reference_name = "#m" + str(uuid.uuid4())[:8] + "name"
+
+    if overwrite:
+        self._medication_section.entry = []
+
+    added_medications = []
+
+    for medication in medications:
+        if medication in self.medication_list:
+            log.debug(
+                f"Skipping: medication {medication.display_name} already exists in the medication list."
+            )
+            continue
+        log.debug(f"Adding medication {medication}")
+        self._add_new_medication_entry(
+            new_medication=medication,
+            timestamp=timestamp,
+            subad_id=subad_id,
+            medication_reference_name=medication_reference_name,
+        )
+        added_medications.append(medication)
+
+    if overwrite:
+        self.medication_list = added_medications
+    else:
+        self.medication_list.extend(added_medications)
+
+
+
+ +
+ +
+ + +

+ add_to_problem_list(problems, overwrite=False) + +

+ + +
+ +

Adds a list of problem lists to the problems section.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ problems + +
+

A list of problem concepts to be added.

+
+

+ + TYPE: + List[ProblemConcept] + +

+
+ overwrite + +
+

If True, the existing problem list will be overwritten. +Defaults to False.

+
+

+ + TYPE: + bool + + + DEFAULT: + False + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + None + + +
+

None

+
+
+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
def add_to_problem_list(
+    self, problems: List[ProblemConcept], overwrite: bool = False
+) -> None:
+    """
+    Adds a list of problem lists to the problems section.
+
+    Args:
+        problems (List[ProblemConcept]): A list of problem concepts to be added.
+        overwrite (bool, optional): If True, the existing problem list will be overwritten.
+            Defaults to False.
+
+    Returns:
+        None
+    """
+    if self._problem_section is None:
+        log.warning(
+            "Skipping: No problem section to add to, check your CDA configuration"
+        )
+        return
+
+    timestamp = datetime.now().strftime(format="%Y%m%d")
+    act_id = str(uuid.uuid4())
+    problem_reference_name = "#p" + str(uuid.uuid4())[:8] + "name"
+
+    if overwrite:
+        self._problem_section.entry = []
+
+    added_problems = []
+
+    for problem in problems:
+        if problem in self.problem_list:
+            log.debug(
+                f"Skipping: Problem {problem.display_name} already exists in the problem list."
+            )
+            continue
+        log.debug(f"Adding problem: {problem}")
+        self._add_new_problem_entry(
+            new_problem=problem,
+            timestamp=timestamp,
+            act_id=act_id,
+            problem_reference_name=problem_reference_name,
+        )
+        added_problems.append(problem)
+
+    if overwrite:
+        self.problem_list = added_problems
+    else:
+        self.problem_list.extend(added_problems)
+
+
+
+ +
+ +
+ + +

+ export(pretty_print=True) + +

+ + +
+ +

Exports CDA document as an XML string

+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
def export(self, pretty_print: bool = True) -> str:
+    """
+    Exports CDA document as an XML string
+    """
+    out_string = xmltodict.unparse(
+        {
+            "ClinicalDocument": self.clinical_document.model_dump(
+                exclude_none=True, exclude_unset=True, by_alias=True
+            )
+        },
+        pretty=pretty_print,
+    )
+    # Fixes self closing tags - this is not strictly necessary, just looks more readable
+    pattern = r"(<(\w+)(\s+[^>]*?)?)></\2>"
+    export_xml = re.sub(pattern, r"\1/>", out_string)
+
+    return export_xml
+
+
+
+ +
+ +
+ + +

+ from_dict(data) + + + classmethod + + +

+ + +
+ +

Creates an instance of the class from a dictionary.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ data + +
+

The dictionary containing the dictionary representation of the cda xml (using xmltodict.parse).

+
+

+ + TYPE: + Dict + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CdaAnnotator + +
+

An instance of the class initialized with the data from the dictionary.

+
+

+ + TYPE: + CdaAnnotator + +

+
+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
@classmethod
+def from_dict(cls, data: Dict) -> "CdaAnnotator":
+    """
+    Creates an instance of the class from a dictionary.
+
+    Args:
+        data (Dict): The dictionary containing the dictionary representation of the cda xml (using xmltodict.parse).
+
+    Returns:
+        CdaAnnotator: An instance of the class initialized with the data from the dictionary.
+    """
+    clinical_document_model = ClinicalDocument(**data.get("ClinicalDocument", {}))
+    return cls(cda_data=clinical_document_model)
+
+
+
+ +
+ +
+ + +

+ from_xml(data) + + + classmethod + + +

+ + +
+ +

Creates an instance of the CDAAnnotator class from an XML string.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ data + +
+

The XML string representing the CDA document.

+
+

+ + TYPE: + str + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CDAAnnotator + +
+

An instance of the CDAAnnotator class initialized with the parsed CDA data.

+
+

+ + TYPE: + CdaAnnotator + +

+
+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
@classmethod
+def from_xml(cls, data: str) -> "CdaAnnotator":
+    """
+    Creates an instance of the CDAAnnotator class from an XML string.
+
+    Args:
+        data (str): The XML string representing the CDA document.
+
+    Returns:
+        CDAAnnotator: An instance of the CDAAnnotator class initialized with the parsed CDA data.
+    """
+    cda_dict = xmltodict.parse(data)
+    clinical_document_model = ClinicalDocument(
+        **cda_dict.get("ClinicalDocument", {})
+    )
+    return cls(cda_data=clinical_document_model)
+
+
+
+ +
+ + + +
+ +
+ +
+ + +
+ + +

+ check_for_entry_observation(entry) + +

+ + +
+ +

Checks if the given entry contains an observation.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ entry + +
+

The entry to check.

+
+

+ + TYPE: + Entry + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + bool + + +
+

True if the entry contains an observation, False otherwise.

+
+
+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
def check_for_entry_observation(entry: Entry) -> bool:
+    """
+    Checks if the given entry contains an observation.
+
+    Args:
+        entry: The entry to check.
+
+    Returns:
+        True if the entry contains an observation, False otherwise.
+    """
+    if isinstance(entry, EntryRelationship):
+        if entry.observation:
+            return True
+    elif isinstance(entry, Observation):
+        if entry.entryRelationship:
+            return check_for_entry_observation(entry.entryRelationship)
+    elif isinstance(entry, list):
+        for item in entry:
+            if isinstance(item, EntryRelationship):
+                if item.observation:
+                    return True
+            elif isinstance(item, Observation):
+                if item.entryRelationship:
+                    return check_for_entry_observation(item.entryRelationship)
+    return False
+
+
+
+ +
+ +
+ + +

+ check_has_template_id(section, template_id) + +

+ + +
+ +

Check if the given section has a matching template ID.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ section + +
+

The section to check.

+
+

+ + TYPE: + Section + +

+
+ template_id + +
+

The template ID to match.

+
+

+ + TYPE: + str + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + bool + + +
+

True if the section has a matching template ID, False otherwise.

+
+
+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
def check_has_template_id(section: Section, template_id: str) -> bool:
+    """
+    Check if the given section has a matching template ID.
+
+    Args:
+        section: The section to check.
+        template_id: The template ID to match.
+
+    Returns:
+        True if the section has a matching template ID, False otherwise.
+    """
+
+    if section.templateId is None:
+        return False
+
+    if isinstance(section.templateId, list):
+        for template in section.templateId:
+            if template.root == template_id:
+                return True
+    elif section.templateId.root == template_id:
+        return True
+
+    return False
+
+
+
+ +
+ +
+ + +

+ get_time_range_from_cda_value(value) + +

+ + +
+ +

Converts a dictionary representing a time range from a CDA value into a Range object.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ value + +
+

A dictionary representing the CDA value.

+
+

+ + TYPE: + Dict + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ Range + +
+

A Range object representing the time range.

+
+

+ + TYPE: + Range + +

+
+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
def get_time_range_from_cda_value(value: Dict) -> Range:
+    """
+    Converts a dictionary representing a time range from a CDA value into a Range object.
+
+    Args:
+        value (Dict): A dictionary representing the CDA value.
+
+    Returns:
+        Range: A Range object representing the time range.
+
+    """
+    range_model = Range(
+        low=Quantity(
+            value=value.get("low", {}).get("@value"),
+            unit=value.get("low", {}).get("@unit"),
+        ),
+        high=Quantity(
+            value=value.get("high", {}).get("@value"),
+            unit=value.get("high", {}).get("@unit"),
+        ),
+    )
+    if range_model.low.value is None:
+        range_model.low = None
+    if range_model.high.value is None:
+        range_model.high = None
+
+    return range_model
+
+
+
+ +
+ +
+ + +

+ get_value_from_entry_relationship(entry_relationship) + +

+ + +
+ +

Retrieves the values from the given entry_relationship.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ entry_relationship + +
+

The entry_relationship object to extract values from.

+
+

+ + TYPE: + EntryRelationship + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + List + + +
+

A list of values extracted from the entry_relationship.

+
+
+ +
+ Source code in healthchain/cda_parser/cdaannotator.py +
60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
def get_value_from_entry_relationship(entry_relationship: EntryRelationship) -> List:
+    """
+    Retrieves the values from the given entry_relationship.
+
+    Args:
+        entry_relationship: The entry_relationship object to extract values from.
+
+    Returns:
+        A list of values extracted from the entry_relationship.
+
+    """
+    values = []
+    if isinstance(entry_relationship, list):
+        for item in entry_relationship:
+            if item.observation:
+                values.append(item.observation.value)
+    else:
+        if entry_relationship.observation:
+            values.append(entry_relationship.observation.value)
+    return values
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/cds_hooks/index.html b/api/cds_hooks/index.html new file mode 100644 index 0000000..65b0afe --- /dev/null +++ b/api/cds_hooks/index.html @@ -0,0 +1,3791 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + CDS Hooks - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

CDS Hooks

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ EncounterDischargeContext + + +

+ + +
+

+ Bases: BaseHookContext

+ + +

Workflow: +This hook is triggered during the discharge process for typically inpatient encounters. It can be invoked +at any point from the start to the end of the discharge process. The purpose is to allow hook services to +intervene in various aspects of the discharge decision. This includes verifying discharge medications, +ensuring continuity of care planning, and verifying necessary documentation for discharge processing.

+ + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
userId +
+

REQUIRED. The ID of the current user, expected to be a Practitioner or PractitionerRole. + For example, 'Practitioner/123'.

+
+

+ + TYPE: + str + +

+
patientId +
+

REQUIRED. The FHIR Patient.id of the patient being discharged.

+
+

+ + TYPE: + str + +

+
encounterId +
+

REQUIRED. The FHIR Encounter.id of the encounter being ended.

+
+

+ + TYPE: + str + +

+
+

Documentation: https://cds-hooks.org/hooks/encounter-discharge/

+ + + + + + +
+ Source code in healthchain/models/hooks/encounterdischarge.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
class EncounterDischargeContext(BaseHookContext):
+    """
+    Workflow:
+    This hook is triggered during the discharge process for typically inpatient encounters. It can be invoked
+    at any point from the start to the end of the discharge process. The purpose is to allow hook services to
+    intervene in various aspects of the discharge decision. This includes verifying discharge medications,
+    ensuring continuity of care planning, and verifying necessary documentation for discharge processing.
+
+    Attributes:
+        userId (str): REQUIRED. The ID of the current user, expected to be a Practitioner or PractitionerRole.
+                      For example, 'Practitioner/123'.
+        patientId (str): REQUIRED. The FHIR Patient.id of the patient being discharged.
+        encounterId (str): REQUIRED. The FHIR Encounter.id of the encounter being ended.
+
+    Documentation: https://cds-hooks.org/hooks/encounter-discharge/
+    """
+
+    userId: str = Field(
+        default_factory=id_generator.generate_random_user_id,
+        pattern=r"^(Practitioner|PractitionerRole)/[^\s]+$",
+        description="The ID of the current user, expected to be in the format 'Practitioner/123'.",
+    )
+    patientId: str = Field(
+        default_factory=id_generator.generate_random_patient_id,
+        description="The FHIR Patient.id of the patient being discharged.",
+    )
+    encounterId: str = Field(
+        default_factory=id_generator.generate_random_encounter_id,
+        description="The FHIR Encounter.id of the encounter being ended.",
+    )
+
+    @model_validator(mode="before")
+    @classmethod
+    def check_unexpected_keys(cls, values):
+        allowed_keys = {"userId", "patientId", "encounterId"}
+        unexpected_keys = set(values) - allowed_keys
+        if unexpected_keys:
+            raise ValueError(f"Unexpected keys provided: {unexpected_keys}")
+        return values
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ OrderSelectContext + + +

+ + +
+

+ Bases: BaseHookContext

+ + +

Workflow: The order-select hook occurs after the clinician selects the order and before signing. +This hook occurs when a clinician initially selects one or more new orders from a list of +potential orders for a specific patient (including orders for medications, procedures, labs +and other orders). The newly selected order defines that medication, procedure, lab, etc, +but may or may not define the additional details necessary to finalize the order.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
userId +
+

REQUIRED. An identifier of the current user, in the format [ResourceType]/[id], + where ResourceType is either 'Practitioner' or 'PractitionerRole'. Examples: 'PractitionerRole/123', + 'Practitioner/abc'.

+
+

+ + TYPE: + str + +

+
patientId +
+

REQUIRED. The FHIR Patient.id representing the current patient in context.

+
+

+ + TYPE: + str + +

+
encounterId +
+

OPTIONAL. The FHIR Encounter.id representing the current encounter in context, + if applicable.

+
+

+ + TYPE: + Optional[str] + +

+
selections +
+

REQUIRED. A list of the FHIR id(s) of the newly selected orders, referencing resources + in the draftOrders Bundle. Example: 'MedicationRequest/103'.

+
+

+ + TYPE: + [str] + +

+
draftOrders +
+

REQUIRED. A Bundle of FHIR request resources with a draft status, representing all unsigned + orders from the current session, including newly selected orders.

+
+

+ + TYPE: + object + +

+
+

Documentation: https://cds-hooks.org/hooks/order-select/

+ + + + + + +
+ Source code in healthchain/models/hooks/orderselect.py +
12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
class OrderSelectContext(BaseHookContext):
+    """
+    Workflow: The order-select hook occurs after the clinician selects the order and before signing.
+    This hook occurs when a clinician initially selects one or more new orders from a list of
+    potential orders for a specific patient (including orders for medications, procedures, labs
+    and other orders). The newly selected order defines that medication, procedure, lab, etc,
+    but may or may not define the additional details necessary to finalize the order.
+
+    Attributes:
+        userId (str): REQUIRED. An identifier of the current user, in the format [ResourceType]/[id],
+                      where ResourceType is either 'Practitioner' or 'PractitionerRole'. Examples: 'PractitionerRole/123',
+                      'Practitioner/abc'.
+        patientId (str): REQUIRED. The FHIR Patient.id representing the current patient in context.
+        encounterId (Optional[str]): OPTIONAL. The FHIR Encounter.id representing the current encounter in context,
+                                     if applicable.
+        selections ([str]): REQUIRED. A list of the FHIR id(s) of the newly selected orders, referencing resources
+                            in the draftOrders Bundle. Example: 'MedicationRequest/103'.
+        draftOrders (object): REQUIRED. A Bundle of FHIR request resources with a draft status, representing all unsigned
+                              orders from the current session, including newly selected orders.
+
+    Documentation: https://cds-hooks.org/hooks/order-select/
+    """
+
+    # TODO: validate selection and FHIR Bundle resource
+
+    userId: str = Field(
+        default_factory=id_generator.generate_random_user_id,
+        pattern=r"^(Practitioner|PractitionerRole)/[^\s]+$",
+        description="An identifier of the current user in the format [ResourceType]/[id].",
+    )
+    patientId: str = Field(
+        default_factory=id_generator.generate_random_patient_id,
+        description="The FHIR Patient.id representing the current patient in context.",
+    )
+    encounterId: Optional[str] = Field(
+        default_factory=id_generator.generate_random_encounter_id,
+        description="The FHIR Encounter.id of the current encounter, if applicable.",
+    )
+    selections: List[str] = Field(
+        ..., description="A list of the FHIR ids of the newly selected orders."
+    )
+    draftOrders: Dict[str, Any] = Field(
+        ..., description="A Bundle of FHIR request resources with a draft status."
+    )
+
+    @model_validator(mode="before")
+    @classmethod
+    def check_unexpected_keys(cls, values):
+        allowed_keys = {
+            "userId",
+            "patientId",
+            "encounterId",
+            "selections",
+            "draftOrders",
+        }
+        unexpected_keys = set(values) - allowed_keys
+        if unexpected_keys:
+            raise ValueError(f"Unexpected keys provided: {unexpected_keys}")
+        return values
+
+    @model_validator(mode="after")
+    def validate_selections(self) -> Self:
+        for selection in self.selections:
+            if "/" not in selection:
+                raise ValueError(
+                    "Each selection must be a valid FHIR resource identifier in the format 'ResourceType/ResourceID'."
+                )
+        return self
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ OrderSignContext + + +

+ + +
+

+ Bases: BaseHookContext

+ + +

Workflow: +The order-sign hook is triggered when a clinician is ready to sign one or more orders for a patient. +This includes orders for medications, procedures, labs, and other orders. It is one of the last workflow +events before an order is promoted from a draft status. The context includes all order details such as +dose, quantity, route, etc., even though the order is still in a draft status. This hook is also applicable +for re-signing revised orders, which may have a status other than 'draft'. The hook replaces the +medication-prescribe and order-review hooks.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
userId +
+

REQUIRED. The ID of the current user, expected to be of type 'Practitioner' or 'PractitionerRole'. + Examples include 'PractitionerRole/123' or 'Practitioner/abc'.

+
+

+ + TYPE: + str + +

+
patientId +
+

REQUIRED. The FHIR Patient.id representing the current patient in context.

+
+

+ + TYPE: + str + +

+
encounterId +
+

OPTIONAL. The FHIR Encounter.id of the current encounter in context.

+
+

+ + TYPE: + Optional[str] + +

+
draftOrders +
+

REQUIRED. A Bundle of FHIR request resources with a draft status, representing orders that + aren't yet signed from the current ordering session.

+
+

+ + TYPE: + dict + +

+
+

Documentation: https://cds-hooks.org/hooks/order-sign/

+ + + + + + +
+ Source code in healthchain/models/hooks/ordersign.py +
11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
class OrderSignContext(BaseHookContext):
+    """
+    Workflow:
+    The order-sign hook is triggered when a clinician is ready to sign one or more orders for a patient.
+    This includes orders for medications, procedures, labs, and other orders. It is one of the last workflow
+    events before an order is promoted from a draft status. The context includes all order details such as
+    dose, quantity, route, etc., even though the order is still in a draft status. This hook is also applicable
+    for re-signing revised orders, which may have a status other than 'draft'. The hook replaces the
+    medication-prescribe and order-review hooks.
+
+    Attributes:
+        userId (str): REQUIRED. The ID of the current user, expected to be of type 'Practitioner' or 'PractitionerRole'.
+                      Examples include 'PractitionerRole/123' or 'Practitioner/abc'.
+        patientId (str): REQUIRED. The FHIR Patient.id representing the current patient in context.
+        encounterId (Optional[str]): OPTIONAL. The FHIR Encounter.id of the current encounter in context.
+        draftOrders (dict): REQUIRED. A Bundle of FHIR request resources with a draft status, representing orders that
+                            aren't yet signed from the current ordering session.
+
+    Documentation: https://cds-hooks.org/hooks/order-sign/
+    """
+
+    # TODO: validate draftOrders
+
+    userId: str = Field(
+        default_factory=id_generator.generate_random_user_id,
+        pattern=r"^(Practitioner|PractitionerRole)/[^\s]+$",
+        description="The ID of the current user in the format [ResourceType]/[id].",
+    )
+    patientId: str = Field(
+        default_factory=id_generator.generate_random_patient_id,
+        description="The FHIR Patient.id representing the current patient in context.",
+    )
+    encounterId: Optional[str] = Field(
+        default_factory=id_generator.generate_random_encounter_id,
+        description="The FHIR Encounter.id of the current encounter, if applicable.",
+    )
+    draftOrders: Dict[str, Any] = Field(
+        ..., description="A Bundle of FHIR request resources with a draft status."
+    )
+
+    @model_validator(mode="before")
+    @classmethod
+    def check_unexpected_keys(cls, values):
+        allowed_keys = {"userId", "patientId", "encounterId", "draftOrders"}
+        unexpected_keys = set(values) - allowed_keys
+        if unexpected_keys:
+            raise ValueError(f"Unexpected keys provided: {unexpected_keys}")
+        return values
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ PatientViewContext + + +

+ + +
+

+ Bases: BaseHookContext

+ + +

Workflow: The user has just opened a patient's record; typically called only once at the beginning of a user's +interaction with a specific patient's record.

+ + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
userId +
+

An identifier of the current user, in the format [ResourceType]/[id], + where ResourceType is one of 'Practitioner', 'PractitionerRole', 'Patient', + or 'RelatedPerson'. Examples: 'Practitioner/abc', 'Patient/123'.

+
+

+ + TYPE: + str + +

+
patientId +
+

The FHIR Patient.id representing the current patient in context.

+
+

+ + TYPE: + str + +

+
encounterId +
+

The FHIR Encounter.id representing the current encounter in context, + if applicable. This field is optional.

+
+

+ + TYPE: + Optional[str] + +

+
+

Documentation: https://cds-hooks.org/hooks/patient-view/

+ + + + + + +
+ Source code in healthchain/models/hooks/patientview.py +
13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
class PatientViewContext(BaseHookContext):
+    """
+    Workflow: The user has just opened a patient's record; typically called only once at the beginning of a user's
+    interaction with a specific patient's record.
+
+    Attributes:
+        userId (str): An identifier of the current user, in the format [ResourceType]/[id],
+                      where ResourceType is one of 'Practitioner', 'PractitionerRole', 'Patient',
+                      or 'RelatedPerson'. Examples: 'Practitioner/abc', 'Patient/123'.
+        patientId (str): The FHIR Patient.id representing the current patient in context.
+        encounterId (Optional[str]): The FHIR Encounter.id representing the current encounter in context,
+                                     if applicable. This field is optional.
+
+    Documentation: https://cds-hooks.org/hooks/patient-view/
+    """
+
+    # TODO: more comprehensive validator? for now regex should suffice
+
+    userId: str = Field(
+        default_factory=id_generator.generate_random_user_id,
+        pattern=r"^(Practitioner|PractitionerRole|Patient|RelatedPerson)/[^\s]+$",
+        description="The ID of the current user, expected to be in the format 'Practitioner/123'.",
+    )
+    patientId: str = Field(
+        default_factory=id_generator.generate_random_patient_id,
+        description="The FHIR Patient.id of the patient.",
+    )
+    encounterId: Optional[str] = Field(
+        None, description="The FHIR Encounter.id of the encounter, if applicable."
+    )
+
+    @model_validator(mode="before")
+    @classmethod
+    def check_unexpected_keys(cls, values):
+        allowed_keys = {"userId", "patientId", "encounterId"}
+        unexpected_keys = set(values) - allowed_keys
+        if unexpected_keys:
+            raise ValueError(f"Unexpected keys provided: {unexpected_keys}")
+        return values
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + +
+ +
+ +
+ + + + +
+ + + + + + +
+ +
+ +
+ + + + +
+ +

https://cds-hooks.org/specification/current/#discovery

+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CDSService + + +

+ + +
+

+ Bases: BaseModel

+ + +

A model representing a CDS service configuration.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
hook +
+

The hook this service should be invoked on. This should correspond to one of the predefined hooks.

+
+

+ + TYPE: + str + +

+
title +
+

The human-friendly name of this service. It is recommended to provide this for better usability.

+
+

+ + TYPE: + Optional[str] + +

+
description +
+

A detailed description of what this service does and its purpose within the CDS framework.

+
+

+ + TYPE: + str + +

+
id +
+

The unique identifier of this service. It forms part of the URL as {baseUrl}/cds-services/{id}.

+
+

+ + TYPE: + str + +

+
prefetch +
+

Optional FHIR queries that the service requests the CDS Client to perform + and provide on each service call. Keys describe the type of data and values are the actual FHIR query strings.

+
+

+ + TYPE: + Optional[Dict[str, str]] + +

+
usageRequirements +
+

Human-friendly description of any preconditions for the use of this CDS service.

+
+

+ + TYPE: + Optional[str] + +

+
+

Documentation: https://cds-hooks.org/specification/current/#response

+ + + + + + +
+ Source code in healthchain/models/responses/cdsdiscovery.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
class CDSService(BaseModel):
+    """
+    A model representing a CDS service configuration.
+
+    Attributes:
+        hook (str): The hook this service should be invoked on. This should correspond to one of the predefined hooks.
+        title (Optional[str]): The human-friendly name of this service. It is recommended to provide this for better usability.
+        description (str): A detailed description of what this service does and its purpose within the CDS framework.
+        id (str): The unique identifier of this service. It forms part of the URL as {baseUrl}/cds-services/{id}.
+        prefetch (Optional[Dict[str, str]]): Optional FHIR queries that the service requests the CDS Client to perform
+                                            and provide on each service call. Keys describe the type of data and values are the actual FHIR query strings.
+        usageRequirements (Optional[str]): Human-friendly description of any preconditions for the use of this CDS service.
+
+    Documentation: https://cds-hooks.org/specification/current/#response
+    """
+
+    hook: str
+    description: str
+    id: str
+    title: Optional[str] = None
+    prefetch: Optional[Dict[str, Any]] = None
+    usageRequirements: Optional[str] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ CDSServiceInformation + + +

+ + +
+

+ Bases: BaseModel

+ + +

A CDS Service is discoverable via a stable endpoint by CDS Clients. The Discovery endpoint includes information such as a +description of the CDS Service, when it should be invoked, and any data that is requested to be prefetched.

+ + + + + + +
+ Source code in healthchain/models/responses/cdsdiscovery.py +
33
+34
+35
+36
+37
+38
+39
class CDSServiceInformation(BaseModel):
+    """
+    A CDS Service is discoverable via a stable endpoint by CDS Clients. The Discovery endpoint includes information such as a
+    description of the CDS Service, when it should be invoked, and any data that is requested to be prefetched.
+    """
+
+    services: List[CDSService] = []
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ +

This is not compulsary

+

https://cds-hooks.org/specification/current/#feedback

+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CDSFeedback + + +

+ + +
+

+ Bases: BaseModel

+ + +

A feedback endpoint enables suggestion tracking & analytics. +A CDS Service MAY support a feedback endpoint; a CDS Client SHOULD be capable of sending feedback.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
card +
+

The card.uuid from the CDS Hooks response. Uniquely identifies the card.

+
+

+ + TYPE: + str + +

+
outcome +
+

The outcome of the action, either 'accepted' or 'overridden'.

+
+

+ + TYPE: + str + +

+
acceptedSuggestions +
+

An array of accepted suggestions, required if the outcome is 'accepted'.

+
+

+ + TYPE: + List[AcceptedSuggestion] + +

+
overrideReason +
+

The reason for overriding, including any coding and comments.

+
+

+ + TYPE: + Optional[OverrideReason] + +

+
outcomeTimestamp +
+

The ISO8601 timestamp of when the action was taken on the card.

+
+

+ + TYPE: + datetime + +

+
+

Documentation: https://cds-hooks.org/specification/current/#feedback

+ + + + + + +
+ Source code in healthchain/models/responses/cdsfeedback.py +
24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
class CDSFeedback(BaseModel):
+    """
+    A feedback endpoint enables suggestion tracking & analytics.
+    A CDS Service MAY support a feedback endpoint; a CDS Client SHOULD be capable of sending feedback.
+
+    Attributes:
+        card (str): The card.uuid from the CDS Hooks response. Uniquely identifies the card.
+        outcome (str): The outcome of the action, either 'accepted' or 'overridden'.
+        acceptedSuggestions (List[AcceptedSuggestion]): An array of accepted suggestions, required if the outcome is 'accepted'.
+        overrideReason (Optional[OverrideReason]): The reason for overriding, including any coding and comments.
+        outcomeTimestamp (datetime): The ISO8601 timestamp of when the action was taken on the card.
+
+    Documentation: https://cds-hooks.org/specification/current/#feedback
+    """
+
+    card: str
+    outcome: OutcomeEnum
+    outcomeTimestamp: str
+    acceptedSuggestion: Optional[Dict[str, Any]] = None
+    overriddeReason: Optional[OverrideReason] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/clients/index.html b/api/clients/index.html new file mode 100644 index 0000000..addd24b --- /dev/null +++ b/api/clients/index.html @@ -0,0 +1,3053 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Clients - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Clients

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ EHRClient + + +

+ + +
+

+ Bases: BaseClient

+ + + + + + + +
+ Source code in healthchain/clients/ehrclient.py +
class EHRClient(BaseClient):
+    def __init__(
+        self,
+        func: Callable[..., Any],
+        workflow: Workflow,
+        strategy: BaseStrategy,
+        timeout: Optional[float] = 10.0,
+    ):
+        """
+        Initializes the EHRClient with a data generator function and optional workflow and use case.
+        Should be a subclass of BaseUseCase. Example - ClinicalDecisionSupport()
+
+        Parameters:
+            func (Callable[..., Any]): A function to generate data for requests.
+            workflow ([Workflow]): The workflow context to apply to the data generator.
+            strategy (BaseStrategy): The strategy object to construct requests based on the generated data.
+            timeout (Optional[float], default=10.0) : The maximum time in seconds to wait for a response from the server. This parameter determines how long the client will wait before considering a request timed out.
+
+        """
+        # TODO: Add option to pass in different provider options
+        self.data_generator_func: Callable[..., Any] = func
+        self.workflow: Workflow = workflow
+        self.strategy: BaseStrategy = strategy
+        self.vendor = None
+        self.request_data: List[CDSRequest] = []
+        self.timeout = timeout
+
+    def set_vendor(self, name) -> None:
+        self.vendor = name
+
+    def generate_request(self, *args: Any, **kwargs: Any) -> None:
+        """
+        Generates a request using the data produced by the data generator function,
+        and appends it to the internal request queue.
+
+            Parameters:
+                *args (Any): Positional arguments passed to the data generator function.
+                **kwargs (Any): Keyword arguments passed to the data generator function.
+
+            Raises:
+                ValueError: If the use case is not configured.
+        """
+        data = self.data_generator_func(*args, **kwargs)
+        self.request_data.append(self.strategy.construct_request(data, self.workflow))
+
+    async def send_request(self, url: str) -> List[Dict]:
+        """
+        Sends all queued requests to the specified URL and collects the responses.
+
+            Parameters:
+                url (str): The URL to which the requests will be sent.
+            Returns:
+                List[dict]: A list of JSON responses from the server.
+            Notes:
+                This method logs errors rather than raising them, to avoid interrupting the batch processing of requests.
+        """
+        async with httpx.AsyncClient() as client:
+            responses: List[Dict] = []
+            # TODO: pass timeout as config
+            timeout = httpx.Timeout(self.timeout, read=None)
+            for request in self.request_data:
+                try:
+                    if self.strategy.api_protocol == ApiProtocol.soap:
+                        headers = {"Content-Type": "text/xml; charset=utf-8"}
+                        response = await client.post(
+                            url=url,
+                            data=request.document,
+                            headers=headers,
+                            timeout=timeout,
+                        )
+                        response.raise_for_status()
+                        response_model = CdaResponse(document=response.text)
+                        responses.append(response_model.model_dump_xml())
+                    else:
+                        response = await client.post(
+                            url=url,
+                            json=request.model_dump(exclude_none=True),
+                            timeout=timeout,
+                        )
+                        response.raise_for_status()
+                        responses.append(response.json())
+                except httpx.HTTPStatusError as exc:
+                    log.error(
+                        f"Error response {exc.response.status_code} while requesting {exc.request.url!r}."
+                    )
+                    responses.append({})
+                except httpx.TimeoutException as exc:
+                    log.error(f"Request to {exc.request.url!r} timed out!")
+                    responses.append({})
+                except httpx.RequestError as exc:
+                    log.error(
+                        f"An error occurred while requesting {exc.request.url!r}."
+                    )
+                    responses.append({})
+
+        return responses
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __init__(func, workflow, strategy, timeout=10.0) + +

+ + +
+ +

Initializes the EHRClient with a data generator function and optional workflow and use case. +Should be a subclass of BaseUseCase. Example - ClinicalDecisionSupport()

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ func + +
+

A function to generate data for requests.

+
+

+ + TYPE: + Callable[..., Any] + +

+
+ workflow + +
+

The workflow context to apply to the data generator.

+
+

+ + TYPE: + [Workflow] + +

+
+ strategy + +
+

The strategy object to construct requests based on the generated data.

+
+

+ + TYPE: + BaseStrategy + +

+
+ timeout + +
+

The maximum time in seconds to wait for a response from the server. This parameter determines how long the client will wait before considering a request timed out.

+
+

+ + TYPE: + Optional[float], default=10.0) + + + DEFAULT: + 10.0 + +

+
+ +
+ Source code in healthchain/clients/ehrclient.py +
def __init__(
+    self,
+    func: Callable[..., Any],
+    workflow: Workflow,
+    strategy: BaseStrategy,
+    timeout: Optional[float] = 10.0,
+):
+    """
+    Initializes the EHRClient with a data generator function and optional workflow and use case.
+    Should be a subclass of BaseUseCase. Example - ClinicalDecisionSupport()
+
+    Parameters:
+        func (Callable[..., Any]): A function to generate data for requests.
+        workflow ([Workflow]): The workflow context to apply to the data generator.
+        strategy (BaseStrategy): The strategy object to construct requests based on the generated data.
+        timeout (Optional[float], default=10.0) : The maximum time in seconds to wait for a response from the server. This parameter determines how long the client will wait before considering a request timed out.
+
+    """
+    # TODO: Add option to pass in different provider options
+    self.data_generator_func: Callable[..., Any] = func
+    self.workflow: Workflow = workflow
+    self.strategy: BaseStrategy = strategy
+    self.vendor = None
+    self.request_data: List[CDSRequest] = []
+    self.timeout = timeout
+
+
+
+ +
+ +
+ + +

+ generate_request(*args, **kwargs) + +

+ + +
+ +

Generates a request using the data produced by the data generator function, +and appends it to the internal request queue.

+
Parameters:
+    *args (Any): Positional arguments passed to the data generator function.
+    **kwargs (Any): Keyword arguments passed to the data generator function.
+
+Raises:
+    ValueError: If the use case is not configured.
+
+ +
+ Source code in healthchain/clients/ehrclient.py +
def generate_request(self, *args: Any, **kwargs: Any) -> None:
+    """
+    Generates a request using the data produced by the data generator function,
+    and appends it to the internal request queue.
+
+        Parameters:
+            *args (Any): Positional arguments passed to the data generator function.
+            **kwargs (Any): Keyword arguments passed to the data generator function.
+
+        Raises:
+            ValueError: If the use case is not configured.
+    """
+    data = self.data_generator_func(*args, **kwargs)
+    self.request_data.append(self.strategy.construct_request(data, self.workflow))
+
+
+
+ +
+ +
+ + +

+ send_request(url) + + + async + + +

+ + +
+ +

Sends all queued requests to the specified URL and collects the responses.

+
Parameters:
+    url (str): The URL to which the requests will be sent.
+Returns:
+    List[dict]: A list of JSON responses from the server.
+Notes:
+    This method logs errors rather than raising them, to avoid interrupting the batch processing of requests.
+
+ +
+ Source code in healthchain/clients/ehrclient.py +
async def send_request(self, url: str) -> List[Dict]:
+    """
+    Sends all queued requests to the specified URL and collects the responses.
+
+        Parameters:
+            url (str): The URL to which the requests will be sent.
+        Returns:
+            List[dict]: A list of JSON responses from the server.
+        Notes:
+            This method logs errors rather than raising them, to avoid interrupting the batch processing of requests.
+    """
+    async with httpx.AsyncClient() as client:
+        responses: List[Dict] = []
+        # TODO: pass timeout as config
+        timeout = httpx.Timeout(self.timeout, read=None)
+        for request in self.request_data:
+            try:
+                if self.strategy.api_protocol == ApiProtocol.soap:
+                    headers = {"Content-Type": "text/xml; charset=utf-8"}
+                    response = await client.post(
+                        url=url,
+                        data=request.document,
+                        headers=headers,
+                        timeout=timeout,
+                    )
+                    response.raise_for_status()
+                    response_model = CdaResponse(document=response.text)
+                    responses.append(response_model.model_dump_xml())
+                else:
+                    response = await client.post(
+                        url=url,
+                        json=request.model_dump(exclude_none=True),
+                        timeout=timeout,
+                    )
+                    response.raise_for_status()
+                    responses.append(response.json())
+            except httpx.HTTPStatusError as exc:
+                log.error(
+                    f"Error response {exc.response.status_code} while requesting {exc.request.url!r}."
+                )
+                responses.append({})
+            except httpx.TimeoutException as exc:
+                log.error(f"Request to {exc.request.url!r} timed out!")
+                responses.append({})
+            except httpx.RequestError as exc:
+                log.error(
+                    f"An error occurred while requesting {exc.request.url!r}."
+                )
+                responses.append({})
+
+    return responses
+
+
+
+ +
+ + + +
+ +
+ +
+ + +
+ + +

+ ehr(func=None, *, workflow, num=1) + +

+ + +
+ +

A decorator that wraps around a data generator function and returns an EHRClient

+ + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ func + +
+

The function to be decorated. If None, this allows the decorator to + be used with arguments.

+
+

+ + TYPE: + Optional[Callable] + + + DEFAULT: + None + +

+
+ workflow + +
+

The workflow identifier which should match an item in the Workflow enum. + This specifies the context in which the EHR function will operate.

+
+

+ + TYPE: + [str] + +

+
+ num + +
+

The number of requests to generate in the queue; defaults to 1.

+
+

+ + TYPE: + int + + + DEFAULT: + 1 + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ Callable + +
+

A decorated callable that incorporates EHR functionality or the decorator itself + if 'func' is None, allowing it to be used as a parameterized decorator.

+
+

+ + TYPE: + Union[Callable[..., Any], Callable[[F], F]] + +

+
+ + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If the workflow does not correspond to any defined enum or if use case is not configured.

+
+
+ + NotImplementedError + + +
+

If the use case class is not one of the supported types.

+
+
+ + +
+ Example +

@ehr(workflow='patient-view', num=2) +def generate_data(self, config): + # Function implementation

+
+
+ Source code in healthchain/clients/ehrclient.py +
20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
def ehr(
+    func: Optional[F] = None, *, workflow: Workflow, num: int = 1
+) -> Union[Callable[..., Any], Callable[[F], F]]:
+    """
+    A decorator that wraps around a data generator function and returns an EHRClient
+
+    Parameters:
+        func (Optional[Callable]): The function to be decorated. If None, this allows the decorator to
+                                   be used with arguments.
+        workflow ([str]): The workflow identifier which should match an item in the Workflow enum.
+                                  This specifies the context in which the EHR function will operate.
+        num (int): The number of requests to generate in the queue; defaults to 1.
+
+    Returns:
+        Callable: A decorated callable that incorporates EHR functionality or the decorator itself
+                  if 'func' is None, allowing it to be used as a parameterized decorator.
+
+    Raises:
+        ValueError: If the workflow does not correspond to any defined enum or if use case is not configured.
+        NotImplementedError: If the use case class is not one of the supported types.
+
+    Example:
+        @ehr(workflow='patient-view', num=2)
+        def generate_data(self, config):
+            # Function implementation
+    """
+
+    def decorator(func: F) -> F:
+        func.is_client = True
+
+        @wraps(func)
+        def wrapper(self, *args: Any, **kwargs: Any) -> EHRClient:
+            # Validate function decorated is a use case base class
+            assert issubclass(
+                type(self), BaseUseCase
+            ), f"{self.__class__.__name__} must be subclass of valid Use Case strategy!"
+
+            # Validate workflow is a valid workflow
+            try:
+                workflow_enum = Workflow(workflow)
+            except ValueError as e:
+                raise ValueError(
+                    f"{e}: please select from {[x.value for x in Workflow]}"
+                )
+
+            # Set workflow in data generator if configured
+            data_generator_attributes = find_attributes_of_type(self, CdsDataGenerator)
+            for i in range(len(data_generator_attributes)):
+                attribute_name = data_generator_attributes[i]
+                try:
+                    assign_to_attribute(
+                        self, attribute_name, "set_workflow", workflow_enum
+                    )
+                except Exception as e:
+                    log.error(
+                        f"Could not set workflow {workflow_enum.value} for data generator method {attribute_name}: {e}"
+                    )
+                if i > 1:
+                    log.warning("More than one DataGenerator instances found.")
+
+            # Wrap the function in EHRClient with workflow and strategy passed in
+            if self.type in UseCaseType:
+                method = EHRClient(func, workflow=workflow_enum, strategy=self.strategy)
+                # Generate the number of requests specified with method
+                for _ in range(num):
+                    method.generate_request(self, *args, **kwargs)
+            else:
+                raise NotImplementedError(
+                    f"Use case {self.type} not recognised, check if implemented."
+                )
+            return method
+
+        return wrapper
+
+    if func is None:
+        return decorator
+    else:
+        return decorator(func)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/component/index.html b/api/component/index.html new file mode 100644 index 0000000..d923969 --- /dev/null +++ b/api/component/index.html @@ -0,0 +1,6851 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Component - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Component

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ BaseComponent + + +

+ + +
+

+ Bases: Generic[T], ABC

+ + +

Abstract base class for all components in the pipeline.

+

This class should be subclassed to create specific components. +Subclasses must implement the call method.

+ + + + + + +
+ Source code in healthchain/pipeline/components/base.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
class BaseComponent(Generic[T], ABC):
+    """
+    Abstract base class for all components in the pipeline.
+
+    This class should be subclassed to create specific components.
+    Subclasses must implement the __call__ method.
+
+    Attributes:
+        None
+    """
+
+    @abstractmethod
+    def __call__(self, data: DataContainer[T]) -> DataContainer[T]:
+        """
+        Process the input data and return the processed data.
+
+        Args:
+            data (DataContainer[T]): The input data to be processed.
+
+        Returns:
+            DataContainer[T]: The processed data.
+        """
+        pass
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __call__(data) + + + abstractmethod + + +

+ + +
+ +

Process the input data and return the processed data.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ data + +
+

The input data to be processed.

+
+

+ + TYPE: + DataContainer[T] + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + DataContainer[T] + + +
+

DataContainer[T]: The processed data.

+
+
+ +
+ Source code in healthchain/pipeline/components/base.py +
20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
@abstractmethod
+def __call__(self, data: DataContainer[T]) -> DataContainer[T]:
+    """
+    Process the input data and return the processed data.
+
+    Args:
+        data (DataContainer[T]): The input data to be processed.
+
+    Returns:
+        DataContainer[T]: The processed data.
+    """
+    pass
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ Component + + +

+ + +
+

+ Bases: BaseComponent[T]

+ + +

A concrete implementation of the BaseComponent class.

+

This class can be used as a base for creating specific components +that do not require any additional processing logic.

+ + + + + + + + + + + + + + + +
METHODDESCRIPTION
__call__ +
+

DataContainer[T]) -> DataContainer[T]: +Process the input data and return the processed data. +In this implementation, the input data is returned unmodified.

+
+
+ + + + + + +
+ Source code in healthchain/pipeline/components/base.py +
34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
class Component(BaseComponent[T]):
+    """
+    A concrete implementation of the BaseComponent class.
+
+    This class can be used as a base for creating specific components
+    that do not require any additional processing logic.
+
+    Methods:
+        __call__(data: DataContainer[T]) -> DataContainer[T]:
+            Process the input data and return the processed data.
+            In this implementation, the input data is returned unmodified.
+    """
+
+    def __call__(self, data: DataContainer[T]) -> DataContainer[T]:
+        return data
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ HFTransformer + + +

+ + +
+

+ Bases: BaseComponent[str]

+ + +

A component that integrates Hugging Face transformers models into the pipeline.

+

This component allows using any Hugging Face model and task within the pipeline +by wrapping the transformers.pipeline API. The model outputs are stored in the +document's model_outputs container under the "huggingface" source key.

+

Note that this component is only recommended for non-conversational language tasks. +For chat-based tasks, consider using LangChainLLM instead.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ pipeline + +
+

A pre-configured HuggingFace pipeline object to use for inference. +Must be an instance of transformers.pipelines.base.Pipeline.

+
+

+ + TYPE: + Any + +

+
+ + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
task +
+

The task name of the underlying pipeline, e.g. "sentiment-analysis", "ner". +Automatically extracted from the pipeline object.

+
+

+ + TYPE: + str + +

+
+ + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ImportError + + +
+

If the transformers package is not installed

+
+
+ + TypeError + + +
+

If pipeline is not a valid HuggingFace Pipeline instance

+
+
+ + +
+ Example +
+
+
+

Initialize for sentiment analysis

+

from transformers import pipeline +nlp = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english") +component = HFTransformer(pipeline=nlp) +doc = component(doc) # Analyzes sentiment of doc.data

+

Or use the factory method

+

component = HFTransformer.from_model_id( +... model="facebook/bart-large-cnn", +... task="summarization", +... max_length=130, +... min_length=30, +... do_sample=False +... ) +doc = component(doc) # Generates summary of doc.data

+
+
+
+
+ + + + + +
+ Source code in healthchain/pipeline/components/integrations.py +
class HFTransformer(BaseComponent[str]):
+    """
+    A component that integrates Hugging Face transformers models into the pipeline.
+
+    This component allows using any Hugging Face model and task within the pipeline
+    by wrapping the transformers.pipeline API. The model outputs are stored in the
+    document's model_outputs container under the "huggingface" source key.
+
+    Note that this component is only recommended for non-conversational language tasks.
+    For chat-based tasks, consider using LangChainLLM instead.
+
+    Args:
+        pipeline (Any): A pre-configured HuggingFace pipeline object to use for inference.
+            Must be an instance of transformers.pipelines.base.Pipeline.
+
+    Attributes:
+        task (str): The task name of the underlying pipeline, e.g. "sentiment-analysis", "ner".
+            Automatically extracted from the pipeline object.
+
+    Raises:
+        ImportError: If the transformers package is not installed
+        TypeError: If pipeline is not a valid HuggingFace Pipeline instance
+
+    Example:
+        >>> # Initialize for sentiment analysis
+        >>> from transformers import pipeline
+        >>> nlp = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
+        >>> component = HFTransformer(pipeline=nlp)
+        >>> doc = component(doc)  # Analyzes sentiment of doc.data
+        >>>
+        >>> # Or use the factory method
+        >>> component = HFTransformer.from_model_id(
+        ...     model="facebook/bart-large-cnn",
+        ...     task="summarization",
+        ...     max_length=130,
+        ...     min_length=30,
+        ...     do_sample=False
+        ... )
+        >>> doc = component(doc)  # Generates summary of doc.data
+    """
+
+    @requires_package("transformers", "transformers.pipelines")
+    def __init__(self, pipeline: Any):
+        """Initialize with a pre-configured HuggingFace pipeline.
+
+        Args:
+            pipeline: A pre-configured HuggingFace pipeline object from transformers.pipeline().
+                     Must be an instance of transformers.pipelines.base.Pipeline.
+
+        Raises:
+            ImportError: If transformers package is not installed
+            TypeError: If pipeline is not a valid HuggingFace Pipeline instance
+        """
+        from transformers.pipelines.base import Pipeline
+
+        if not isinstance(pipeline, Pipeline):
+            raise TypeError(
+                f"Expected HuggingFace Pipeline object, got {type(pipeline)}"
+            )
+        self._pipe = pipeline
+        self.task = pipeline.task
+
+    @classmethod
+    @requires_package("transformers", "transformers.pipelines")
+    def from_model_id(cls, model: str, task: str, **kwargs: Any) -> "HFTransformer":
+        """Create a transformer component from a model identifier.
+
+        Factory method that initializes a HuggingFace pipeline with the specified model and task,
+        then wraps it in a HFTransformer component.
+
+        Args:
+            model: The model identifier or path to load. Can be:
+                - A model ID from the HuggingFace Hub (e.g. "bert-base-uncased")
+                - A local path to a saved model
+            task: The task to run (e.g. "text-classification", "token-classification", "summarization")
+            **kwargs: Additional configuration options passed to transformers.pipeline()
+                Common options include:
+                - device: Device to run on ("cpu", "cuda", etc.)
+                - batch_size: Batch size for inference
+                - model_kwargs: Dict of model-specific args
+
+        Returns:
+            HFTransformer: Initialized transformer component wrapping the pipeline
+
+        Raises:
+            TypeError: If invalid kwargs are passed to pipeline initialization
+            ValueError: If pipeline initialization fails for any other reason
+            ImportError: If transformers package is not installed
+        """
+        from transformers import pipeline
+
+        try:
+            pipe = pipeline(task=task, model=model, **kwargs)
+        except TypeError as e:
+            raise TypeError(f"Invalid kwargs for transformers.pipeline: {str(e)}")
+        except Exception as e:
+            raise ValueError(f"Error initializing transformer pipeline: {str(e)}")
+
+        return cls(pipeline=pipe)
+
+    def __call__(self, doc: Document) -> Document:
+        """Process the document using the Hugging Face pipeline. Adds outputs to .model_outputs['huggingface']."""
+        output = self._pipe(doc.data)
+        doc.models.add_output("huggingface", self.task, output)
+
+        return doc
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __call__(doc) + +

+ + +
+ +

Process the document using the Hugging Face pipeline. Adds outputs to .model_outputs['huggingface'].

+ +
+ Source code in healthchain/pipeline/components/integrations.py +
def __call__(self, doc: Document) -> Document:
+    """Process the document using the Hugging Face pipeline. Adds outputs to .model_outputs['huggingface']."""
+    output = self._pipe(doc.data)
+    doc.models.add_output("huggingface", self.task, output)
+
+    return doc
+
+
+
+ +
+ +
+ + +

+ __init__(pipeline) + +

+ + +
+ +

Initialize with a pre-configured HuggingFace pipeline.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ pipeline + +
+

A pre-configured HuggingFace pipeline object from transformers.pipeline(). + Must be an instance of transformers.pipelines.base.Pipeline.

+
+

+ + TYPE: + Any + +

+
+ + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ImportError + + +
+

If transformers package is not installed

+
+
+ + TypeError + + +
+

If pipeline is not a valid HuggingFace Pipeline instance

+
+
+ +
+ Source code in healthchain/pipeline/components/integrations.py +
@requires_package("transformers", "transformers.pipelines")
+def __init__(self, pipeline: Any):
+    """Initialize with a pre-configured HuggingFace pipeline.
+
+    Args:
+        pipeline: A pre-configured HuggingFace pipeline object from transformers.pipeline().
+                 Must be an instance of transformers.pipelines.base.Pipeline.
+
+    Raises:
+        ImportError: If transformers package is not installed
+        TypeError: If pipeline is not a valid HuggingFace Pipeline instance
+    """
+    from transformers.pipelines.base import Pipeline
+
+    if not isinstance(pipeline, Pipeline):
+        raise TypeError(
+            f"Expected HuggingFace Pipeline object, got {type(pipeline)}"
+        )
+    self._pipe = pipeline
+    self.task = pipeline.task
+
+
+
+ +
+ +
+ + +

+ from_model_id(model, task, **kwargs) + + + classmethod + + +

+ + +
+ +

Create a transformer component from a model identifier.

+

Factory method that initializes a HuggingFace pipeline with the specified model and task, +then wraps it in a HFTransformer component.

+ + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ model + +
+

The model identifier or path to load. Can be: +- A model ID from the HuggingFace Hub (e.g. "bert-base-uncased") +- A local path to a saved model

+
+

+ + TYPE: + str + +

+
+ task + +
+

The task to run (e.g. "text-classification", "token-classification", "summarization")

+
+

+ + TYPE: + str + +

+
+ **kwargs + +
+

Additional configuration options passed to transformers.pipeline() +Common options include: +- device: Device to run on ("cpu", "cuda", etc.) +- batch_size: Batch size for inference +- model_kwargs: Dict of model-specific args

+
+

+ + TYPE: + Any + + + DEFAULT: + {} + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ HFTransformer + +
+

Initialized transformer component wrapping the pipeline

+
+

+ + TYPE: + HFTransformer + +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + TypeError + + +
+

If invalid kwargs are passed to pipeline initialization

+
+
+ + ValueError + + +
+

If pipeline initialization fails for any other reason

+
+
+ + ImportError + + +
+

If transformers package is not installed

+
+
+ +
+ Source code in healthchain/pipeline/components/integrations.py +
@classmethod
+@requires_package("transformers", "transformers.pipelines")
+def from_model_id(cls, model: str, task: str, **kwargs: Any) -> "HFTransformer":
+    """Create a transformer component from a model identifier.
+
+    Factory method that initializes a HuggingFace pipeline with the specified model and task,
+    then wraps it in a HFTransformer component.
+
+    Args:
+        model: The model identifier or path to load. Can be:
+            - A model ID from the HuggingFace Hub (e.g. "bert-base-uncased")
+            - A local path to a saved model
+        task: The task to run (e.g. "text-classification", "token-classification", "summarization")
+        **kwargs: Additional configuration options passed to transformers.pipeline()
+            Common options include:
+            - device: Device to run on ("cpu", "cuda", etc.)
+            - batch_size: Batch size for inference
+            - model_kwargs: Dict of model-specific args
+
+    Returns:
+        HFTransformer: Initialized transformer component wrapping the pipeline
+
+    Raises:
+        TypeError: If invalid kwargs are passed to pipeline initialization
+        ValueError: If pipeline initialization fails for any other reason
+        ImportError: If transformers package is not installed
+    """
+    from transformers import pipeline
+
+    try:
+        pipe = pipeline(task=task, model=model, **kwargs)
+    except TypeError as e:
+        raise TypeError(f"Invalid kwargs for transformers.pipeline: {str(e)}")
+    except Exception as e:
+        raise ValueError(f"Error initializing transformer pipeline: {str(e)}")
+
+    return cls(pipeline=pipe)
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ LangChainLLM + + +

+ + +
+

+ Bases: BaseComponent[str]

+ + +

A component that integrates LangChain chains into the pipeline.

+

This component allows using any LangChain chain within the pipeline by wrapping +the chain's invoke method. The chain outputs are stored in the document's +model_outputs container under the "langchain" source key.

+ + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ chain + +
+

The LangChain chain to run on the document text. +Must be a Runnable object from the LangChain library.

+
+

+ + TYPE: + Runnable + +

+
+ task + +
+

The task name to use when storing outputs, e.g. "summarization", "chat". +Used as key to organize model outputs in the document's model container.

+
+

+ + TYPE: + str + +

+
+ **kwargs + +
+

Additional parameters to pass to the chain's invoke method. +These are forwarded directly to the chain's invoke() call.

+
+

+ + TYPE: + Any + + + DEFAULT: + {} + +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + TypeError + + +
+

If chain is not a LangChain Runnable object or if invalid kwargs are passed

+
+
+ + ValueError + + +
+

If there is an error during chain invocation

+
+
+ + ImportError + + +
+

If langchain-core package is not installed

+
+
+ + +
+ Example +
+
+
+

from langchain_core.prompts import ChatPromptTemplate +from langchain_openai import ChatOpenAI

+

chain = ChatPromptTemplate.from_template("What is {input}?") | ChatOpenAI() +component = LangChainLLM(chain=chain, task="chat") +doc = component(doc) # Runs the chain on doc.data and stores output

+
+
+
+
+ + + + + +
+ Source code in healthchain/pipeline/components/integrations.py +
class LangChainLLM(BaseComponent[str]):
+    """
+    A component that integrates LangChain chains into the pipeline.
+
+    This component allows using any LangChain chain within the pipeline by wrapping
+    the chain's invoke method. The chain outputs are stored in the document's
+    model_outputs container under the "langchain" source key.
+
+    Args:
+        chain (Runnable): The LangChain chain to run on the document text.
+            Must be a Runnable object from the LangChain library.
+        task (str): The task name to use when storing outputs, e.g. "summarization", "chat".
+            Used as key to organize model outputs in the document's model container.
+        **kwargs: Additional parameters to pass to the chain's invoke method.
+            These are forwarded directly to the chain's invoke() call.
+
+    Raises:
+        TypeError: If chain is not a LangChain Runnable object or if invalid kwargs are passed
+        ValueError: If there is an error during chain invocation
+        ImportError: If langchain-core package is not installed
+
+    Example:
+        >>> from langchain_core.prompts import ChatPromptTemplate
+        >>> from langchain_openai import ChatOpenAI
+
+        >>> chain = ChatPromptTemplate.from_template("What is {input}?") | ChatOpenAI()
+        >>> component = LangChainLLM(chain=chain, task="chat")
+        >>> doc = component(doc)  # Runs the chain on doc.data and stores output
+    """
+
+    @requires_package("langchain-core", "langchain_core.runnables")
+    def __init__(self, chain: Any, task: str, **kwargs: Any):
+        """Initialize with a LangChain chain."""
+        from langchain_core.runnables import Runnable
+
+        if not isinstance(chain, Runnable):
+            raise TypeError(f"Expected LangChain Runnable object, got {type(chain)}")
+
+        self.chain = chain
+        self.task = task
+        self.kwargs = kwargs
+
+    def __call__(self, doc: Document) -> Document:
+        """Process the document using the LangChain chain. Adds outputs to .model_outputs['langchain']."""
+        try:
+            output = self.chain.invoke(doc.data, **self.kwargs)
+        except TypeError as e:
+            raise TypeError(f"Invalid kwargs for chain.invoke: {str(e)}")
+        except Exception as e:
+            raise ValueError(f"Error during chain invocation: {str(e)}")
+
+        doc.models.add_output("langchain", self.task, output)
+
+        return doc
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __call__(doc) + +

+ + +
+ +

Process the document using the LangChain chain. Adds outputs to .model_outputs['langchain'].

+ +
+ Source code in healthchain/pipeline/components/integrations.py +
def __call__(self, doc: Document) -> Document:
+    """Process the document using the LangChain chain. Adds outputs to .model_outputs['langchain']."""
+    try:
+        output = self.chain.invoke(doc.data, **self.kwargs)
+    except TypeError as e:
+        raise TypeError(f"Invalid kwargs for chain.invoke: {str(e)}")
+    except Exception as e:
+        raise ValueError(f"Error during chain invocation: {str(e)}")
+
+    doc.models.add_output("langchain", self.task, output)
+
+    return doc
+
+
+
+ +
+ +
+ + +

+ __init__(chain, task, **kwargs) + +

+ + +
+ +

Initialize with a LangChain chain.

+ +
+ Source code in healthchain/pipeline/components/integrations.py +
@requires_package("langchain-core", "langchain_core.runnables")
+def __init__(self, chain: Any, task: str, **kwargs: Any):
+    """Initialize with a LangChain chain."""
+    from langchain_core.runnables import Runnable
+
+    if not isinstance(chain, Runnable):
+        raise TypeError(f"Expected LangChain Runnable object, got {type(chain)}")
+
+    self.chain = chain
+    self.task = task
+    self.kwargs = kwargs
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ SpacyNLP + + +

+ + +
+

+ Bases: BaseComponent[str]

+ + +

A component that integrates spaCy models into the pipeline.

+

This component allows using any spaCy model within the pipeline by loading +and applying it to process text documents. The spaCy doc outputs are stored +in the document's nlp annotations container under .spacy_docs.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ nlp + +
+

A pre-configured spaCy Language object.

+
+

+ + TYPE: + Language + +

+
+ + +
+ Example +
+
+
+

Using pre-configured pipeline

+

import spacy +nlp = spacy.load("en_core_web_sm", disable=["parser"]) +component = SpacyNLP(nlp) +doc = component(doc)

+

Or using model name

+

component = SpacyNLP.from_model_id("en_core_web_sm", disable=["parser"]) +doc = component(doc)

+
+
+
+
+ + + + + +
+ Source code in healthchain/pipeline/components/integrations.py +
class SpacyNLP(BaseComponent[str]):
+    """
+    A component that integrates spaCy models into the pipeline.
+
+    This component allows using any spaCy model within the pipeline by loading
+    and applying it to process text documents. The spaCy doc outputs are stored
+    in the document's nlp annotations container under .spacy_docs.
+
+    Args:
+        nlp: A pre-configured spaCy Language object.
+
+    Example:
+        >>> # Using pre-configured pipeline
+        >>> import spacy
+        >>> nlp = spacy.load("en_core_web_sm", disable=["parser"])
+        >>> component = SpacyNLP(nlp)
+        >>> doc = component(doc)
+        >>>
+        >>> # Or using model name
+        >>> component = SpacyNLP.from_model_id("en_core_web_sm", disable=["parser"])
+        >>> doc = component(doc)
+    """
+
+    def __init__(self, nlp: "Language"):
+        """Initialize with a pre-configured spaCy Language object."""
+        self._nlp = nlp
+
+    @classmethod
+    def from_model_id(cls, model: str, **kwargs: Any) -> "SpacyNLP":
+        """
+        Create a SpacyNLP component from a model identifier.
+
+        Args:
+            model (str): The name or path of the spaCy model to load.
+                Can be a model name like 'en_core_web_sm' or path to saved model.
+            **kwargs: Additional configuration options passed to spacy.load.
+                Common options include disable, exclude, enable.
+
+        Returns:
+            SpacyNLP: Initialized spaCy component
+
+        Raises:
+            ImportError: If spaCy or the specified model is not installed
+            TypeError: If invalid kwargs are passed to spacy.load
+        """
+        try:
+            import spacy
+        except ImportError:
+            raise ImportError(
+                "Could not import spacy. Please install it with: " "`pip install spacy`"
+            )
+
+        try:
+            nlp = spacy.load(model, **kwargs)
+        except TypeError as e:
+            raise TypeError(f"Invalid kwargs for spacy.load: {str(e)}")
+        except Exception as e:
+            raise ImportError(
+                f"Could not load spaCy model {model}! "
+                "Make sure you have installed it with: "
+                f"`python -m spacy download {model}`"
+            ) from e
+
+        return cls(nlp)
+
+    def _add_concepts_to_hc_doc(self, spacy_doc: SpacyDoc, hc_doc: Document):
+        """
+        Extract entities from spaCy Doc and add them to the HealthChain Document concepts.
+
+        Args:
+            spacy_doc (Doc): The processed spaCy Doc object containing entities
+            hc_doc (Document): The HealthChain Document to store concepts in
+
+        Note: Defaults to ProblemConcepts and SNOMED CT concepts
+        # TODO: make configurable
+        """
+        concepts = []
+        for ent in spacy_doc.ents:
+            # Check for CUI attribute from extensions like medcat
+            concept = ProblemConcept(
+                code=ent._.cui if hasattr(ent, "_.cui") else None,
+                code_system="2.16.840.1.113883.6.96",
+                code_system_name="SNOMED CT",
+                display_name=ent.text,
+            )
+            concepts.append(concept)
+
+        # Add to document concepts
+        hc_doc.add_concepts(problems=concepts)
+
+    def __call__(self, doc: Document) -> Document:
+        """Process the document using the spaCy pipeline. Adds outputs to nlp.spacy_docs."""
+        spacy_doc = self._nlp(doc.data)
+        self._add_concepts_to_hc_doc(spacy_doc, doc)
+        doc.nlp.add_spacy_doc(spacy_doc)
+        return doc
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __call__(doc) + +

+ + +
+ +

Process the document using the spaCy pipeline. Adds outputs to nlp.spacy_docs.

+ +
+ Source code in healthchain/pipeline/components/integrations.py +
def __call__(self, doc: Document) -> Document:
+    """Process the document using the spaCy pipeline. Adds outputs to nlp.spacy_docs."""
+    spacy_doc = self._nlp(doc.data)
+    self._add_concepts_to_hc_doc(spacy_doc, doc)
+    doc.nlp.add_spacy_doc(spacy_doc)
+    return doc
+
+
+
+ +
+ +
+ + +

+ __init__(nlp) + +

+ + +
+ +

Initialize with a pre-configured spaCy Language object.

+ +
+ Source code in healthchain/pipeline/components/integrations.py +
61
+62
+63
def __init__(self, nlp: "Language"):
+    """Initialize with a pre-configured spaCy Language object."""
+    self._nlp = nlp
+
+
+
+ +
+ +
+ + +

+ from_model_id(model, **kwargs) + + + classmethod + + +

+ + +
+ +

Create a SpacyNLP component from a model identifier.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ model + +
+

The name or path of the spaCy model to load. +Can be a model name like 'en_core_web_sm' or path to saved model.

+
+

+ + TYPE: + str + +

+
+ **kwargs + +
+

Additional configuration options passed to spacy.load. +Common options include disable, exclude, enable.

+
+

+ + TYPE: + Any + + + DEFAULT: + {} + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ SpacyNLP + +
+

Initialized spaCy component

+
+

+ + TYPE: + SpacyNLP + +

+
+ + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ImportError + + +
+

If spaCy or the specified model is not installed

+
+
+ + TypeError + + +
+

If invalid kwargs are passed to spacy.load

+
+
+ +
+ Source code in healthchain/pipeline/components/integrations.py +
@classmethod
+def from_model_id(cls, model: str, **kwargs: Any) -> "SpacyNLP":
+    """
+    Create a SpacyNLP component from a model identifier.
+
+    Args:
+        model (str): The name or path of the spaCy model to load.
+            Can be a model name like 'en_core_web_sm' or path to saved model.
+        **kwargs: Additional configuration options passed to spacy.load.
+            Common options include disable, exclude, enable.
+
+    Returns:
+        SpacyNLP: Initialized spaCy component
+
+    Raises:
+        ImportError: If spaCy or the specified model is not installed
+        TypeError: If invalid kwargs are passed to spacy.load
+    """
+    try:
+        import spacy
+    except ImportError:
+        raise ImportError(
+            "Could not import spacy. Please install it with: " "`pip install spacy`"
+        )
+
+    try:
+        nlp = spacy.load(model, **kwargs)
+    except TypeError as e:
+        raise TypeError(f"Invalid kwargs for spacy.load: {str(e)}")
+    except Exception as e:
+        raise ImportError(
+            f"Could not load spaCy model {model}! "
+            "Make sure you have installed it with: "
+            f"`python -m spacy download {model}`"
+        ) from e
+
+    return cls(nlp)
+
+
+
+ +
+ + + +
+ +
+ +
+ + +
+ + +

+ requires_package(package_name, import_path) + +

+ + +
+ +

Decorator to check if an optional package is available.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ package_name + +
+

Name of the package to install (e.g., 'langchain-core')

+
+

+ + TYPE: + str + +

+
+ import_path + +
+

Import path to check (e.g., 'langchain_core.runnables')

+
+

+ + TYPE: + str + +

+
+ +
+ Source code in healthchain/pipeline/components/integrations.py +
13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
def requires_package(package_name: str, import_path: str) -> Callable:
+    """Decorator to check if an optional package is available.
+
+    Args:
+        package_name: Name of the package to install (e.g., 'langchain-core')
+        import_path: Import path to check (e.g., 'langchain_core.runnables')
+    """
+
+    def decorator(func: Callable[..., T]) -> Callable[..., T]:
+        @wraps(func)
+        def wrapper(*args, **kwargs) -> T:
+            try:
+                __import__(import_path)
+            except ImportError:
+                raise ImportError(
+                    f"This feature requires {package_name}. "
+                    f"Please install it with: `pip install {package_name}`"
+                )
+            return func(*args, **kwargs)
+
+        return wrapper
+
+    return decorator
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ TextPreProcessor + + +

+ + +
+

+ Bases: BaseComponent[Document]

+ + +

A component for preprocessing text documents.

+

This class applies various cleaning and tokenization steps to a Document object, +based on the provided configuration.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
tokenizer +
+

The tokenizer to use. Can be "basic" or a custom +tokenization function that takes a string and returns a list of tokens. Defaults to "basic".

+
+

+ + TYPE: + Union[str, Callable[[str], List[str]]] + +

+
lowercase +
+

Whether to convert text to lowercase. Defaults to False.

+
+

+ + TYPE: + bool + +

+
remove_punctuation +
+

Whether to remove punctuation. Defaults to False.

+
+

+ + TYPE: + bool + +

+
standardize_spaces +
+

Whether to standardize spaces. Defaults to False.

+
+

+ + TYPE: + bool + +

+
regex +
+

List of regex patterns and replacements. Defaults to an empty list.

+
+

+ + TYPE: + List[Tuple[str, str]] + +

+
tokenizer_func +
+

The tokenization function.

+
+

+ + TYPE: + Callable[[str], List[str]] + +

+
cleaning_steps +
+

List of text cleaning functions.

+
+

+ + TYPE: + List[Callable[[str], str]] + +

+
+ + + + + + +
+ Source code in healthchain/pipeline/components/preprocessors.py +
class TextPreProcessor(BaseComponent[Document]):
+    """
+    A component for preprocessing text documents.
+
+    This class applies various cleaning and tokenization steps to a Document object,
+    based on the provided configuration.
+
+    Attributes:
+        tokenizer (Union[str, Callable[[str], List[str]]]): The tokenizer to use. Can be "basic" or a custom
+            tokenization function that takes a string and returns a list of tokens. Defaults to "basic".
+        lowercase (bool): Whether to convert text to lowercase. Defaults to False.
+        remove_punctuation (bool): Whether to remove punctuation. Defaults to False.
+        standardize_spaces (bool): Whether to standardize spaces. Defaults to False.
+        regex (List[Tuple[str, str]]): List of regex patterns and replacements. Defaults to an empty list.
+        tokenizer_func (Callable[[str], List[str]]): The tokenization function.
+        cleaning_steps (List[Callable[[str], str]]): List of text cleaning functions.
+    """
+
+    def __init__(
+        self,
+        tokenizer: Union[str, Callable[[str], List[str]]] = "basic",
+        lowercase: bool = False,
+        remove_punctuation: bool = False,
+        standardize_spaces: bool = False,
+        regex: List[Tuple[str, str]] = None,
+    ):
+        """
+        Initialize the TextPreprocessor with the given configuration.
+
+        Args:
+            tokenizer (Union[str, Callable[[str], List[str]]]): The tokenizer to use. Can be "basic" or a custom
+                tokenization function that takes a string and returns a list of tokens. Defaults to "basic".
+            lowercase (bool): Whether to convert text to lowercase. Defaults to False.
+            remove_punctuation (bool): Whether to remove punctuation. Defaults to False.
+            standardize_spaces (bool): Whether to standardize spaces. Defaults to False.
+            regex (List[Tuple[str, str]], optional): List of regex patterns and replacements. Defaults to None.
+        """
+        self.lowercase = lowercase
+        self.remove_punctuation = remove_punctuation
+        self.standardize_spaces = standardize_spaces
+        self.regex = regex or []
+        self.tokenizer = self._get_tokenizer(tokenizer)
+        self.cleaning_steps = self._configure_cleaning_steps()
+
+    def _get_tokenizer(
+        self, tokenizer: Union[str, Callable[[str], List[str]]]
+    ) -> Callable[[str], List[str]]:
+        """
+        Get the tokenization function based on the specified tokenizer.
+
+        Args:
+            tokenizer: Either "basic" or a custom tokenization function.
+
+        Returns:
+            Callable[[str], List[str]]: The tokenization function.
+
+        Raises:
+            ValueError: If an unsupported tokenizer string is specified.
+        """
+        if callable(tokenizer):
+            return tokenizer
+        elif tokenizer == "basic":
+            return lambda text: text.split()
+        else:
+            raise ValueError(
+                f"Unsupported tokenizer: {tokenizer}. Use 'basic' or provide a custom tokenization function."
+            )
+
+    def _configure_cleaning_steps(self) -> List[Callable[[str], str]]:
+        """
+        Configure the text cleaning steps based on the preprocessor configuration.
+
+        Returns:
+            List[Callable[[str], str]]: List of text cleaning functions.
+        """
+        steps = []
+        if self.lowercase:
+            steps.append(lambda text: text.lower())
+
+        regex_steps = []
+        if self.regex:
+            regex_steps.extend(self.regex)
+        else:
+            if self.remove_punctuation:
+                regex_steps.append((r"[^\w\s]", ""))
+            if self.standardize_spaces:
+                regex_steps.append((r"\s+", " "))
+
+        for pattern, repl in regex_steps:
+            steps.append(self._create_regex_step(pattern, repl))
+
+        if self.standardize_spaces:
+            steps.append(str.strip)
+
+        return steps
+
+    @staticmethod
+    def _create_regex_step(pattern: str, repl: str) -> Callable[[str], str]:
+        """
+        Create a regex-based cleaning step. This can be used in place of other cleaning steps, if required.
+
+        Args:
+            pattern (str): The regex pattern to match.
+            repl (str): The replacement string.
+
+        Returns:
+            Callable[[str], str]: A function that applies the regex substitution.
+        """
+        return lambda text: re.sub(pattern, repl, text)
+
+    def _clean_text(self, text: str) -> str:
+        """
+        Apply all cleaning steps to the input text.
+
+        Args:
+            text (str): The input text to clean.
+
+        Returns:
+            str: The cleaned text.
+        """
+        for step in self.cleaning_steps:
+            text = step(text)
+        return text
+
+    def __call__(self, doc: Document) -> Document:
+        """
+        Preprocess the given Document.
+
+        This method applies the configured cleaning steps and tokenization to the document's text (in that order).
+
+        Args:
+            doc (Document): The document to preprocess.
+
+        Returns:
+            Document: The preprocessed document with updated tokens and preprocessed text.
+        """
+        # Preprocess text
+        preprocessed_text = self._clean_text(doc.text)
+        doc.preprocessed_text = preprocessed_text
+
+        if self.tokenizer:
+            tokens = self.tokenizer(preprocessed_text)
+            doc.tokens = tokens
+
+        return doc
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __call__(doc) + +

+ + +
+ +

Preprocess the given Document.

+

This method applies the configured cleaning steps and tokenization to the document's text (in that order).

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ doc + +
+

The document to preprocess.

+
+

+ + TYPE: + Document + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ Document + +
+

The preprocessed document with updated tokens and preprocessed text.

+
+

+ + TYPE: + Document + +

+
+ +
+ Source code in healthchain/pipeline/components/preprocessors.py +
def __call__(self, doc: Document) -> Document:
+    """
+    Preprocess the given Document.
+
+    This method applies the configured cleaning steps and tokenization to the document's text (in that order).
+
+    Args:
+        doc (Document): The document to preprocess.
+
+    Returns:
+        Document: The preprocessed document with updated tokens and preprocessed text.
+    """
+    # Preprocess text
+    preprocessed_text = self._clean_text(doc.text)
+    doc.preprocessed_text = preprocessed_text
+
+    if self.tokenizer:
+        tokens = self.tokenizer(preprocessed_text)
+        doc.tokens = tokens
+
+    return doc
+
+
+
+ +
+ +
+ + +

+ __init__(tokenizer='basic', lowercase=False, remove_punctuation=False, standardize_spaces=False, regex=None) + +

+ + +
+ +

Initialize the TextPreprocessor with the given configuration.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ tokenizer + +
+

The tokenizer to use. Can be "basic" or a custom +tokenization function that takes a string and returns a list of tokens. Defaults to "basic".

+
+

+ + TYPE: + Union[str, Callable[[str], List[str]]] + + + DEFAULT: + 'basic' + +

+
+ lowercase + +
+

Whether to convert text to lowercase. Defaults to False.

+
+

+ + TYPE: + bool + + + DEFAULT: + False + +

+
+ remove_punctuation + +
+

Whether to remove punctuation. Defaults to False.

+
+

+ + TYPE: + bool + + + DEFAULT: + False + +

+
+ standardize_spaces + +
+

Whether to standardize spaces. Defaults to False.

+
+

+ + TYPE: + bool + + + DEFAULT: + False + +

+
+ regex + +
+

List of regex patterns and replacements. Defaults to None.

+
+

+ + TYPE: + List[Tuple[str, str]] + + + DEFAULT: + None + +

+
+ +
+ Source code in healthchain/pipeline/components/preprocessors.py +
27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
def __init__(
+    self,
+    tokenizer: Union[str, Callable[[str], List[str]]] = "basic",
+    lowercase: bool = False,
+    remove_punctuation: bool = False,
+    standardize_spaces: bool = False,
+    regex: List[Tuple[str, str]] = None,
+):
+    """
+    Initialize the TextPreprocessor with the given configuration.
+
+    Args:
+        tokenizer (Union[str, Callable[[str], List[str]]]): The tokenizer to use. Can be "basic" or a custom
+            tokenization function that takes a string and returns a list of tokens. Defaults to "basic".
+        lowercase (bool): Whether to convert text to lowercase. Defaults to False.
+        remove_punctuation (bool): Whether to remove punctuation. Defaults to False.
+        standardize_spaces (bool): Whether to standardize spaces. Defaults to False.
+        regex (List[Tuple[str, str]], optional): List of regex patterns and replacements. Defaults to None.
+    """
+    self.lowercase = lowercase
+    self.remove_punctuation = remove_punctuation
+    self.standardize_spaces = standardize_spaces
+    self.regex = regex or []
+    self.tokenizer = self._get_tokenizer(tokenizer)
+    self.cleaning_steps = self._configure_cleaning_steps()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ TextPostProcessor + + +

+ + +
+

+ Bases: BaseComponent[Document]

+ + +

A component for post-processing text documents, specifically for refining entities.

+

This class applies post-coordination rules to entities in a Document object, +replacing entities with their refined versions based on a lookup dictionary.

+ + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
entity_lookup +
+

A dictionary for entity refinement lookups.

+
+

+ + TYPE: + Dict[str, str] + +

+
+ + + + + + +
+ Source code in healthchain/pipeline/components/postprocessors.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
class TextPostProcessor(BaseComponent[Document]):
+    """
+    A component for post-processing text documents, specifically for refining entities.
+
+    This class applies post-coordination rules to entities in a Document object,
+    replacing entities with their refined versions based on a lookup dictionary.
+
+    Attributes:
+        entity_lookup (Dict[str, str]): A dictionary for entity refinement lookups.
+    """
+
+    def __init__(self, postcoordination_lookup: Dict[str, str] = None):
+        """
+        Initialize the TextPostProcessor with an optional postcoordination lookup.
+
+        Args:
+            postcoordination_lookup (Dict[str, str], optional): A dictionary for entity refinement lookups.
+                If not provided, an empty dictionary will be used.
+        """
+        self.entity_lookup = postcoordination_lookup or {}
+
+    def __call__(self, doc: Document) -> Document:
+        """
+        Apply post-processing to the given Document.
+
+        This method refines the entities in the document based on the entity_lookup.
+        If an entity exists in the lookup, it is replaced with its refined version.
+
+        Args:
+            doc (Document): The document to be post-processed.
+
+        Returns:
+            Document: The post-processed document with refined entities.
+
+        Note:
+            If the entity_lookup is empty or the document has no 'entities' attribute,
+            the document is returned unchanged.
+        """
+        if not self.entity_lookup or not hasattr(doc._nlp, "_entities"):
+            return doc
+
+        refined_entities = []
+        for entity in doc.nlp.get_entities():
+            entity_text = entity["text"]
+            if entity_text in self.entity_lookup:
+                entity["text"] = self.entity_lookup[entity_text]
+            refined_entities.append(entity)
+
+        doc.nlp.set_entities(refined_entities)
+
+        return doc
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __call__(doc) + +

+ + +
+ +

Apply post-processing to the given Document.

+

This method refines the entities in the document based on the entity_lookup. +If an entity exists in the lookup, it is replaced with its refined version.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ doc + +
+

The document to be post-processed.

+
+

+ + TYPE: + Document + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ Document + +
+

The post-processed document with refined entities.

+
+

+ + TYPE: + Document + +

+
+ + +
+ Note +

If the entity_lookup is empty or the document has no 'entities' attribute, +the document is returned unchanged.

+
+
+ Source code in healthchain/pipeline/components/postprocessors.py +
30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
def __call__(self, doc: Document) -> Document:
+    """
+    Apply post-processing to the given Document.
+
+    This method refines the entities in the document based on the entity_lookup.
+    If an entity exists in the lookup, it is replaced with its refined version.
+
+    Args:
+        doc (Document): The document to be post-processed.
+
+    Returns:
+        Document: The post-processed document with refined entities.
+
+    Note:
+        If the entity_lookup is empty or the document has no 'entities' attribute,
+        the document is returned unchanged.
+    """
+    if not self.entity_lookup or not hasattr(doc._nlp, "_entities"):
+        return doc
+
+    refined_entities = []
+    for entity in doc.nlp.get_entities():
+        entity_text = entity["text"]
+        if entity_text in self.entity_lookup:
+            entity["text"] = self.entity_lookup[entity_text]
+        refined_entities.append(entity)
+
+    doc.nlp.set_entities(refined_entities)
+
+    return doc
+
+
+
+ +
+ +
+ + +

+ __init__(postcoordination_lookup=None) + +

+ + +
+ +

Initialize the TextPostProcessor with an optional postcoordination lookup.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ postcoordination_lookup + +
+

A dictionary for entity refinement lookups. +If not provided, an empty dictionary will be used.

+
+

+ + TYPE: + Dict[str, str] + + + DEFAULT: + None + +

+
+ +
+ Source code in healthchain/pipeline/components/postprocessors.py +
20
+21
+22
+23
+24
+25
+26
+27
+28
def __init__(self, postcoordination_lookup: Dict[str, str] = None):
+    """
+    Initialize the TextPostProcessor with an optional postcoordination lookup.
+
+    Args:
+        postcoordination_lookup (Dict[str, str], optional): A dictionary for entity refinement lookups.
+            If not provided, an empty dictionary will be used.
+    """
+    self.entity_lookup = postcoordination_lookup or {}
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdsCardCreator + + +

+ + +
+

+ Bases: BaseComponent[str]

+ + +
Component that creates CDS Hooks cards from model outputs or static content.
+
+This component formats text into CDS Hooks cards that can be displayed in an EHR system.
+It can create cards from either:
+1. Model-generated text stored in a document's model outputs container
+2. Static content provided during initialization
+
+The component uses Jinja2 templates to format the text into valid CDS Hooks card JSON.
+The generated cards are added to the document's CDS container.
+
+Args:
+    template (str, optional): Jinja2 template string for card creation. If not provided,
+        uses a default template that creates an info card.
+    template_path (Union[str, Path], optional): Path to a Jinja2 template file.
+    static_content (str, optional): Static text to use instead of model output.
+    source (str, optional): Source framework to get model output from (e.g. "huggingface").
+    task (str, optional): Task name to get model output from (e.g. "summarization").
+    delimiter (str, optional): String to split model output into multiple cards.
+    default_source (Dict[str, Any], optional): Default source info for cards.
+        Defaults to {"label": "Card Generated by HealthChain"}.
+
+Example:
+    >>> # Create cards from model output
+    >>> creator = CdsCardCreator(source="huggingface", task="summarization")
+    >>> doc = creator(doc)  # Creates cards from model output
+    >>>
+    >>> # Create cards with static content
+    >>> creator = CdsCardCreator(static_content="Static card message")
+    >>> doc = creator(doc)  # Creates card with static content
+    >>>
+    >>> # Create cards with custom template
+    >>> template = '''
+    ... {
+    ...     "summary": "{{ model_output[:140] }}",
+    ...     "indicator": "info",
+    ...     "source": {{ default_source | tojson }},
+    ...     "detail": "{{ model_output }}"
+    ... }
+    ... '''
+    >>> creator = CdsCardCreator(
+    ...     template=template,
+    ...     source="langchain",
+    ...     task="chat",
+    ...     delimiter="
+
+

" + ... ) + >>> doc = creator(doc) # Creates cards split by newlines

+ + + + + + +
+ Source code in healthchain/pipeline/components/cdscardcreator.py +
class CdsCardCreator(BaseComponent[str]):
+    """
+    Component that creates CDS Hooks cards from model outputs or static content.
+
+    This component formats text into CDS Hooks cards that can be displayed in an EHR system.
+    It can create cards from either:
+    1. Model-generated text stored in a document's model outputs container
+    2. Static content provided during initialization
+
+    The component uses Jinja2 templates to format the text into valid CDS Hooks card JSON.
+    The generated cards are added to the document's CDS container.
+
+    Args:
+        template (str, optional): Jinja2 template string for card creation. If not provided,
+            uses a default template that creates an info card.
+        template_path (Union[str, Path], optional): Path to a Jinja2 template file.
+        static_content (str, optional): Static text to use instead of model output.
+        source (str, optional): Source framework to get model output from (e.g. "huggingface").
+        task (str, optional): Task name to get model output from (e.g. "summarization").
+        delimiter (str, optional): String to split model output into multiple cards.
+        default_source (Dict[str, Any], optional): Default source info for cards.
+            Defaults to {"label": "Card Generated by HealthChain"}.
+
+    Example:
+        >>> # Create cards from model output
+        >>> creator = CdsCardCreator(source="huggingface", task="summarization")
+        >>> doc = creator(doc)  # Creates cards from model output
+        >>>
+        >>> # Create cards with static content
+        >>> creator = CdsCardCreator(static_content="Static card message")
+        >>> doc = creator(doc)  # Creates card with static content
+        >>>
+        >>> # Create cards with custom template
+        >>> template = '''
+        ... {
+        ...     "summary": "{{ model_output[:140] }}",
+        ...     "indicator": "info",
+        ...     "source": {{ default_source | tojson }},
+        ...     "detail": "{{ model_output }}"
+        ... }
+        ... '''
+        >>> creator = CdsCardCreator(
+        ...     template=template,
+        ...     source="langchain",
+        ...     task="chat",
+        ...     delimiter="\n"
+        ... )
+        >>> doc = creator(doc)  # Creates cards split by newlines
+    """
+
+    # TODO: make source and other fields configurable from model too
+    DEFAULT_TEMPLATE = """
+    {
+        "summary": "{{ model_output[:140] }}",
+        "indicator": "info",
+        "source": {{ default_source | tojson }},
+        "detail": "{{ model_output }}"
+    }
+    """
+
+    def __init__(
+        self,
+        template: Optional[str] = None,
+        template_path: Optional[Union[str, Path]] = None,
+        static_content: Optional[str] = None,
+        source: Optional[str] = None,
+        task: Optional[str] = None,
+        delimiter: Optional[str] = None,
+        default_source: Optional[Dict[str, Any]] = None,
+    ):
+        # Load template from file or use string template
+        if template_path:
+            try:
+                template_path = Path(template_path)
+                if not template_path.exists():
+                    raise FileNotFoundError(f"Template file not found: {template_path}")
+                with open(template_path) as f:
+                    template = f.read()
+            except Exception as e:
+                logger.error(f"Error loading template from {template_path}: {str(e)}")
+                template = self.DEFAULT_TEMPLATE
+
+        self.template = Template(
+            template if template is not None else self.DEFAULT_TEMPLATE
+        )
+        self.static_content = static_content
+        self.source = source
+        self.task = task
+        self.delimiter = delimiter
+        self.default_source = default_source or {
+            "label": "Card Generated by HealthChain"
+        }
+
+    def create_card(self, content: str) -> Card:
+        """Creates a CDS Card using the template and model output."""
+        try:
+            # Clean and escape the content
+            # TODO: format to html that can be rendered in card
+            content = content.replace("\n", " ").replace("\r", " ").strip()
+            content = content.replace('"', '\\"')  # Escape double quotes
+
+            try:
+                card_json = self.template.render(
+                    model_output=content, default_source=self.default_source
+                )
+            except Exception as e:
+                raise ValueError(f"Error rendering template: {str(e)}")
+
+            # Parse the rendered JSON into card fields
+            card_fields = json.loads(card_json)
+
+            return Card(
+                summary=card_fields["summary"][:140],  # Enforce max length
+                indicator=IndicatorEnum(card_fields["indicator"]),
+                source=Source(**card_fields["source"]),
+                detail=card_fields.get("detail"),
+                suggestions=card_fields.get("suggestions"),
+                selectionBehavior=card_fields.get("selectionBehavior"),
+                overrideReasons=card_fields.get("overrideReasons"),
+                links=card_fields.get("links"),
+            )
+        except Exception as e:
+            raise ValueError(
+                f"Error creating CDS card: Failed to render template or parse card fields: {str(e)}"
+            )
+
+    def __call__(self, doc: Document) -> Document:
+        """
+        Process a document and create CDS Hooks cards from model outputs or static content.
+
+        Creates cards in one of two ways:
+        1. From model-generated text stored in the document's model outputs container,
+           accessed using the configured source and task
+        2. From static content provided during initialization
+
+        The generated text can optionally be split into multiple cards using a delimiter.
+        Each piece of text is formatted using the configured template into a CDS Hooks card
+        and added to the document's CDS container.
+
+        Args:
+            doc (Document): Document containing model outputs and CDS container
+
+        Returns:
+            Document: The input document with generated CDS cards added to its CDS container
+
+        Raises:
+            ValueError: If neither model configuration (source and task) nor static content
+                is provided for card creation
+        """
+        if self.source and self.task:
+            generated_text = doc.models.get_generated_text(self.source, self.task)
+            if not generated_text:
+                logger.warning(
+                    f"No generated text for {self.source}/{self.task} found for CDS card creation!"
+                )
+                return doc
+        elif self.static_content:
+            generated_text = [self.static_content]
+        else:
+            raise ValueError(
+                "Either model output (source and task) or content need to be provided for CDS card creation!"
+            )
+
+        # Create card from model output
+        cards = []
+        for text in generated_text:
+            texts = [text] if not self.delimiter else text.split(self.delimiter)
+            for t in texts:
+                try:
+                    cards.append(self.create_card(t))
+                except Exception as e:
+                    logger.warning(f"Error creating card: {str(e)}")
+
+        if cards:
+            doc.add_cds_cards(cards)
+
+        return doc
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __call__(doc) + +

+ + +
+ +

Process a document and create CDS Hooks cards from model outputs or static content.

+

Creates cards in one of two ways: +1. From model-generated text stored in the document's model outputs container, + accessed using the configured source and task +2. From static content provided during initialization

+

The generated text can optionally be split into multiple cards using a delimiter. +Each piece of text is formatted using the configured template into a CDS Hooks card +and added to the document's CDS container.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ doc + +
+

Document containing model outputs and CDS container

+
+

+ + TYPE: + Document + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ Document + +
+

The input document with generated CDS cards added to its CDS container

+
+

+ + TYPE: + Document + +

+
+ + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If neither model configuration (source and task) nor static content +is provided for card creation

+
+
+ +
+ Source code in healthchain/pipeline/components/cdscardcreator.py +
def __call__(self, doc: Document) -> Document:
+    """
+    Process a document and create CDS Hooks cards from model outputs or static content.
+
+    Creates cards in one of two ways:
+    1. From model-generated text stored in the document's model outputs container,
+       accessed using the configured source and task
+    2. From static content provided during initialization
+
+    The generated text can optionally be split into multiple cards using a delimiter.
+    Each piece of text is formatted using the configured template into a CDS Hooks card
+    and added to the document's CDS container.
+
+    Args:
+        doc (Document): Document containing model outputs and CDS container
+
+    Returns:
+        Document: The input document with generated CDS cards added to its CDS container
+
+    Raises:
+        ValueError: If neither model configuration (source and task) nor static content
+            is provided for card creation
+    """
+    if self.source and self.task:
+        generated_text = doc.models.get_generated_text(self.source, self.task)
+        if not generated_text:
+            logger.warning(
+                f"No generated text for {self.source}/{self.task} found for CDS card creation!"
+            )
+            return doc
+    elif self.static_content:
+        generated_text = [self.static_content]
+    else:
+        raise ValueError(
+            "Either model output (source and task) or content need to be provided for CDS card creation!"
+        )
+
+    # Create card from model output
+    cards = []
+    for text in generated_text:
+        texts = [text] if not self.delimiter else text.split(self.delimiter)
+        for t in texts:
+            try:
+                cards.append(self.create_card(t))
+            except Exception as e:
+                logger.warning(f"Error creating card: {str(e)}")
+
+    if cards:
+        doc.add_cds_cards(cards)
+
+    return doc
+
+
+
+ +
+ +
+ + +

+ create_card(content) + +

+ + +
+ +

Creates a CDS Card using the template and model output.

+ +
+ Source code in healthchain/pipeline/components/cdscardcreator.py +
def create_card(self, content: str) -> Card:
+    """Creates a CDS Card using the template and model output."""
+    try:
+        # Clean and escape the content
+        # TODO: format to html that can be rendered in card
+        content = content.replace("\n", " ").replace("\r", " ").strip()
+        content = content.replace('"', '\\"')  # Escape double quotes
+
+        try:
+            card_json = self.template.render(
+                model_output=content, default_source=self.default_source
+            )
+        except Exception as e:
+            raise ValueError(f"Error rendering template: {str(e)}")
+
+        # Parse the rendered JSON into card fields
+        card_fields = json.loads(card_json)
+
+        return Card(
+            summary=card_fields["summary"][:140],  # Enforce max length
+            indicator=IndicatorEnum(card_fields["indicator"]),
+            source=Source(**card_fields["source"]),
+            detail=card_fields.get("detail"),
+            suggestions=card_fields.get("suggestions"),
+            selectionBehavior=card_fields.get("selectionBehavior"),
+            overrideReasons=card_fields.get("overrideReasons"),
+            links=card_fields.get("links"),
+        )
+    except Exception as e:
+        raise ValueError(
+            f"Error creating CDS card: Failed to render template or parse card fields: {str(e)}"
+        )
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/connectors/index.html b/api/connectors/index.html new file mode 100644 index 0000000..53ef2c0 --- /dev/null +++ b/api/connectors/index.html @@ -0,0 +1,4025 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Connectors - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Connectors

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ BaseConnector + + +

+ + +
+

+ Bases: Generic[T], ABC

+ + +

Abstract base class for all connectors in the pipeline.

+

This class should be subclassed to create specific connectors. +Subclasses must implement the input and output methods.

+ + + + + + +
+ Source code in healthchain/io/base.py +
 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
class BaseConnector(Generic[T], ABC):
+    """
+    Abstract base class for all connectors in the pipeline.
+
+    This class should be subclassed to create specific connectors.
+    Subclasses must implement the input and output methods.
+    """
+
+    @abstractmethod
+    def input(self, data: DataContainer[T]) -> DataContainer[T]:
+        """
+        Convert input data to the pipeline's internal format.
+
+        Args:
+            data (DataContainer[T]): The input data to be converted.
+
+        Returns:
+            DataContainer[T]: The converted data.
+        """
+        pass
+
+    @abstractmethod
+    def output(self, data: DataContainer[T]) -> DataContainer[T]:
+        """
+        Convert pipeline's internal format to output data.
+
+        Args:
+            data (DataContainer[T]): The data to be converted for output.
+
+        Returns:
+            DataContainer[T]: The converted output data.
+        """
+        pass
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ input(data) + + + abstractmethod + + +

+ + +
+ +

Convert input data to the pipeline's internal format.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ data + +
+

The input data to be converted.

+
+

+ + TYPE: + DataContainer[T] + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + DataContainer[T] + + +
+

DataContainer[T]: The converted data.

+
+
+ +
+ Source code in healthchain/io/base.py +
16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
@abstractmethod
+def input(self, data: DataContainer[T]) -> DataContainer[T]:
+    """
+    Convert input data to the pipeline's internal format.
+
+    Args:
+        data (DataContainer[T]): The input data to be converted.
+
+    Returns:
+        DataContainer[T]: The converted data.
+    """
+    pass
+
+
+
+ +
+ +
+ + +

+ output(data) + + + abstractmethod + + +

+ + +
+ +

Convert pipeline's internal format to output data.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ data + +
+

The data to be converted for output.

+
+

+ + TYPE: + DataContainer[T] + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + DataContainer[T] + + +
+

DataContainer[T]: The converted output data.

+
+
+ +
+ Source code in healthchain/io/base.py +
29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
@abstractmethod
+def output(self, data: DataContainer[T]) -> DataContainer[T]:
+    """
+    Convert pipeline's internal format to output data.
+
+    Args:
+        data (DataContainer[T]): The data to be converted for output.
+
+    Returns:
+        DataContainer[T]: The converted output data.
+    """
+    pass
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdaConnector + + +

+ + +
+

+ Bases: BaseConnector

+ + +

CDAConnector class for handling CDA (Clinical Document Architecture) documents.

+

This connector is responsible for parsing CDA documents, extracting relevant +clinical data, and updating the document with new information. It serves as +both an input and output connector in the pipeline.

+ + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
overwrite +
+

Flag to determine if existing data should be overwritten + when updating the CDA document.

+
+

+ + TYPE: + bool + +

+
cda_doc +
+

The parsed CDA document.

+
+

+ + TYPE: + CdaAnnotator + +

+
+ + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
input +
+

Parses the input CDA document and extracts clinical data.

+
+
output +
+

Updates the CDA document with new data and returns the response.

+
+
+ + + + + + +
+ Source code in healthchain/io/cdaconnector.py +
class CdaConnector(BaseConnector):
+    """
+    CDAConnector class for handling CDA (Clinical Document Architecture) documents.
+
+    This connector is responsible for parsing CDA documents, extracting relevant
+    clinical data, and updating the document with new information. It serves as
+    both an input and output connector in the pipeline.
+
+    Attributes:
+        overwrite (bool): Flag to determine if existing data should be overwritten
+                          when updating the CDA document.
+        cda_doc (CdaAnnotator): The parsed CDA document.
+
+    Methods:
+        input: Parses the input CDA document and extracts clinical data.
+        output: Updates the CDA document with new data and returns the response.
+    """
+
+    def __init__(self, overwrite: bool = False):
+        self.overwrite = overwrite
+        self.cda_doc = None
+
+    def input(self, in_data: CdaRequest) -> Document:
+        """
+        Parse the input CDA document and extract clinical data.
+
+        This method takes a CdaRequest object containing the CDA document as input,
+        parses it using the CdaAnnotator, and extracts relevant clinical data.
+        The extracted data is then used to create a CcdData object and a healthchain
+        Document object, which is returned.
+
+        Args:
+            in_data (CdaRequest): The input request containing the CDA document.
+
+        Returns:
+            Document: A Document object containing the extracted clinical data
+                      and the original note text.
+
+        """
+        self.cda_doc = CdaAnnotator.from_xml(in_data.document)
+
+        # TODO: Temporary fix for the note section, this might be more of a concern for the Annotator class
+        if isinstance(self.cda_doc.note, dict):
+            note_text = " ".join(str(value) for value in self.cda_doc.note.values())
+        elif isinstance(self.cda_doc.note, str):
+            note_text = self.cda_doc.note
+        else:
+            log.warning("Note section is not a string or dictionary")
+            note_text = ""
+
+        ccd_data = CcdData(
+            concepts=ConceptLists(
+                problems=self.cda_doc.problem_list,
+                medications=self.cda_doc.medication_list,
+                allergies=self.cda_doc.allergy_list,
+            ),
+            note=note_text,
+        )
+
+        doc = Document(data=ccd_data.note)
+        doc.hl7.set_ccd_data(ccd_data)
+
+        return doc
+
+    def output(self, out_data: Document) -> CdaResponse:
+        """
+        Update the CDA document with new data and return the response.
+
+        This method takes a Document object containing updated clinical data,
+        updates the CDA document with this new information, and returns a
+        CdaResponse object with the updated CDA document.
+
+        Args:
+            out_data (Document): A Document object containing the updated
+                                 clinical data (problems, allergies, medications).
+
+        Returns:
+            CdaResponse: A response object containing the updated CDA document.
+
+        Note:
+            The method updates the CDA document with new problems, allergies,
+            and medications if they are present in the input Document object.
+            The update behavior (overwrite or append) is determined by the
+            `overwrite` attribute of the CdaConnector instance.
+        """
+        # TODO: check what to do with overwrite
+        updated_ccd_data = out_data.generate_ccd(overwrite=self.overwrite)
+
+        # Update the CDA document with the results
+
+        if updated_ccd_data.concepts.problems:
+            log.debug(
+                f"Updating CDA document with {len(updated_ccd_data.concepts.problems)} problem(s)."
+            )
+            self.cda_doc.add_to_problem_list(
+                updated_ccd_data.concepts.problems, overwrite=self.overwrite
+            )
+        if updated_ccd_data.concepts.allergies:
+            log.debug(
+                f"Updating CDA document with {len(updated_ccd_data.concepts.allergies)} allergy(ies)."
+            )
+            self.cda_doc.add_to_allergy_list(
+                updated_ccd_data.concepts.allergies, overwrite=self.overwrite
+            )
+        if updated_ccd_data.concepts.medications:
+            log.debug(
+                f"Updating CDA document with {len(updated_ccd_data.concepts.medications)} medication(s)."
+            )
+            self.cda_doc.add_to_medication_list(
+                updated_ccd_data.concepts.medications, overwrite=self.overwrite
+            )
+
+        # Export the updated CDA document
+        response_document = self.cda_doc.export()
+
+        return CdaResponse(document=response_document)
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ input(in_data) + +

+ + +
+ +

Parse the input CDA document and extract clinical data.

+

This method takes a CdaRequest object containing the CDA document as input, +parses it using the CdaAnnotator, and extracts relevant clinical data. +The extracted data is then used to create a CcdData object and a healthchain +Document object, which is returned.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ in_data + +
+

The input request containing the CDA document.

+
+

+ + TYPE: + CdaRequest + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ Document + +
+

A Document object containing the extracted clinical data + and the original note text.

+
+

+ + TYPE: + Document + +

+
+ +
+ Source code in healthchain/io/cdaconnector.py +
35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
def input(self, in_data: CdaRequest) -> Document:
+    """
+    Parse the input CDA document and extract clinical data.
+
+    This method takes a CdaRequest object containing the CDA document as input,
+    parses it using the CdaAnnotator, and extracts relevant clinical data.
+    The extracted data is then used to create a CcdData object and a healthchain
+    Document object, which is returned.
+
+    Args:
+        in_data (CdaRequest): The input request containing the CDA document.
+
+    Returns:
+        Document: A Document object containing the extracted clinical data
+                  and the original note text.
+
+    """
+    self.cda_doc = CdaAnnotator.from_xml(in_data.document)
+
+    # TODO: Temporary fix for the note section, this might be more of a concern for the Annotator class
+    if isinstance(self.cda_doc.note, dict):
+        note_text = " ".join(str(value) for value in self.cda_doc.note.values())
+    elif isinstance(self.cda_doc.note, str):
+        note_text = self.cda_doc.note
+    else:
+        log.warning("Note section is not a string or dictionary")
+        note_text = ""
+
+    ccd_data = CcdData(
+        concepts=ConceptLists(
+            problems=self.cda_doc.problem_list,
+            medications=self.cda_doc.medication_list,
+            allergies=self.cda_doc.allergy_list,
+        ),
+        note=note_text,
+    )
+
+    doc = Document(data=ccd_data.note)
+    doc.hl7.set_ccd_data(ccd_data)
+
+    return doc
+
+
+
+ +
+ +
+ + +

+ output(out_data) + +

+ + +
+ +

Update the CDA document with new data and return the response.

+

This method takes a Document object containing updated clinical data, +updates the CDA document with this new information, and returns a +CdaResponse object with the updated CDA document.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ out_data + +
+

A Document object containing the updated + clinical data (problems, allergies, medications).

+
+

+ + TYPE: + Document + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CdaResponse + +
+

A response object containing the updated CDA document.

+
+

+ + TYPE: + CdaResponse + +

+
+ + +
+ Note +

The method updates the CDA document with new problems, allergies, +and medications if they are present in the input Document object. +The update behavior (overwrite or append) is determined by the +overwrite attribute of the CdaConnector instance.

+
+
+ Source code in healthchain/io/cdaconnector.py +
def output(self, out_data: Document) -> CdaResponse:
+    """
+    Update the CDA document with new data and return the response.
+
+    This method takes a Document object containing updated clinical data,
+    updates the CDA document with this new information, and returns a
+    CdaResponse object with the updated CDA document.
+
+    Args:
+        out_data (Document): A Document object containing the updated
+                             clinical data (problems, allergies, medications).
+
+    Returns:
+        CdaResponse: A response object containing the updated CDA document.
+
+    Note:
+        The method updates the CDA document with new problems, allergies,
+        and medications if they are present in the input Document object.
+        The update behavior (overwrite or append) is determined by the
+        `overwrite` attribute of the CdaConnector instance.
+    """
+    # TODO: check what to do with overwrite
+    updated_ccd_data = out_data.generate_ccd(overwrite=self.overwrite)
+
+    # Update the CDA document with the results
+
+    if updated_ccd_data.concepts.problems:
+        log.debug(
+            f"Updating CDA document with {len(updated_ccd_data.concepts.problems)} problem(s)."
+        )
+        self.cda_doc.add_to_problem_list(
+            updated_ccd_data.concepts.problems, overwrite=self.overwrite
+        )
+    if updated_ccd_data.concepts.allergies:
+        log.debug(
+            f"Updating CDA document with {len(updated_ccd_data.concepts.allergies)} allergy(ies)."
+        )
+        self.cda_doc.add_to_allergy_list(
+            updated_ccd_data.concepts.allergies, overwrite=self.overwrite
+        )
+    if updated_ccd_data.concepts.medications:
+        log.debug(
+            f"Updating CDA document with {len(updated_ccd_data.concepts.medications)} medication(s)."
+        )
+        self.cda_doc.add_to_medication_list(
+            updated_ccd_data.concepts.medications, overwrite=self.overwrite
+        )
+
+    # Export the updated CDA document
+    response_document = self.cda_doc.export()
+
+    return CdaResponse(document=response_document)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdsFhirConnector + + +

+ + +
+

+ Bases: BaseConnector

+ + +

CdsFhirConnector class for handling FHIR (Fast Healthcare Interoperability Resources) documents +for CDS Hooks.

+

This connector facilitates the conversion between CDSRequest objects and Document objects, +as well as the creation of CDSResponse objects from processed Documents.

+ + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
hook_name +
+

The name of the CDS Hook being used.

+
+

+ + TYPE: + str + +

+
+ + + + + + +
+ Source code in healthchain/io/cdsfhirconnector.py +
class CdsFhirConnector(BaseConnector):
+    """
+    CdsFhirConnector class for handling FHIR (Fast Healthcare Interoperability Resources) documents
+    for CDS Hooks.
+
+    This connector facilitates the conversion between CDSRequest objects and Document objects,
+    as well as the creation of CDSResponse objects from processed Documents.
+
+    Attributes:
+        hook_name (str): The name of the CDS Hook being used.
+    """
+
+    def __init__(self, hook_name: str):
+        self.hook_name = hook_name
+
+    def input(self, in_data: CDSRequest) -> Document:
+        """
+        Converts a CDSRequest object into a Document object containing FHIR resources.
+
+        This method takes a CDSRequest object as input, extracts the context and prefetch data,
+        and creates a CdsFhirData object. It then returns a Document object containing the FHIR data
+        and any extracted text content from DocumentReference resources.
+
+        Args:
+            in_data (CDSRequest): The input CDSRequest object containing context and prefetch data.
+
+        Returns:
+            Document: A Document object with the following attributes:
+                - data: Either a string representation of the prefetch data, or if a DocumentReference
+                  is present, the text content from that resource
+                - hl7: Contains the CdsFhirData object with context and prefetch data
+
+        Raises:
+            ValueError: If neither prefetch nor fhirServer is provided in the input data
+            NotImplementedError: If fhirServer is provided (not yet implemented)
+            ValueError: If the provided prefetch data is invalid
+
+        Note:
+            The method currently only supports prefetch data and does not handle FHIR server interactions.
+            When a DocumentReference resource is present in the prefetch data, its text content will be
+            extracted and stored as the Document's data field.
+        """
+        if in_data.prefetch is None and in_data.fhirServer is None:
+            raise ValueError(
+                "Either prefetch or fhirServer must be provided to extract FHIR data!"
+            )
+
+        if in_data.fhirServer is not None:
+            raise NotImplementedError("FHIR server is not implemented yet!")
+
+        try:
+            cds_fhir_data = CdsFhirData.create(
+                context=in_data.context.model_dump(), prefetch=in_data.prefetch
+            )
+        except Exception as e:
+            raise ValueError("Invalid prefetch data provided: {e}!") from e
+
+        doc = Document(data=str(cds_fhir_data.model_dump_prefetch()))
+
+        # Extract text from DocumentReference resources if present
+        for entry in cds_fhir_data.prefetch.entry_field:
+            if entry.resource_field.resourceType == "DocumentReference":
+                doc.data = entry.resource_field.text_field.div_field
+
+        doc.hl7.set_fhir_data(cds_fhir_data)
+
+        return doc
+
+    def output(self, out_data: Document) -> CDSResponse:
+        """
+        Generates a CDSResponse object from a processed Document object.
+
+        This method takes a Document object that has been processed and potentially
+        contains CDS cards and system actions. It creates and returns a CDSResponse
+        object based on the contents of the Document.
+
+        Args:
+            out_data (Document): A Document object potentially containing CDS cards
+                                 and system actions.
+
+        Returns:
+            CDSResponse: A response object containing CDS cards and optional system actions.
+                         If no cards are found in the Document, an empty list of cards is returned.
+
+        Note:
+            - If out_data.cds_cards is None, a warning is logged and an empty list of cards is returned.
+            - System actions (out_data.cds_actions) are included in the response if present.
+        """
+        if out_data.cds.get_cards() is None:
+            log.warning("No CDS cards found in Document, returning empty list of cards")
+            return CDSResponse(cards=[])
+
+        return CDSResponse(
+            cards=out_data.cds.get_cards(), systemActions=out_data.cds.get_actions()
+        )
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ input(in_data) + +

+ + +
+ +

Converts a CDSRequest object into a Document object containing FHIR resources.

+

This method takes a CDSRequest object as input, extracts the context and prefetch data, +and creates a CdsFhirData object. It then returns a Document object containing the FHIR data +and any extracted text content from DocumentReference resources.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ in_data + +
+

The input CDSRequest object containing context and prefetch data.

+
+

+ + TYPE: + CDSRequest + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ Document + +
+

A Document object with the following attributes: +- data: Either a string representation of the prefetch data, or if a DocumentReference + is present, the text content from that resource +- hl7: Contains the CdsFhirData object with context and prefetch data

+
+

+ + TYPE: + Document + +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If neither prefetch nor fhirServer is provided in the input data

+
+
+ + NotImplementedError + + +
+

If fhirServer is provided (not yet implemented)

+
+
+ + ValueError + + +
+

If the provided prefetch data is invalid

+
+
+ + +
+ Note +

The method currently only supports prefetch data and does not handle FHIR server interactions. +When a DocumentReference resource is present in the prefetch data, its text content will be +extracted and stored as the Document's data field.

+
+
+ Source code in healthchain/io/cdsfhirconnector.py +
28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
def input(self, in_data: CDSRequest) -> Document:
+    """
+    Converts a CDSRequest object into a Document object containing FHIR resources.
+
+    This method takes a CDSRequest object as input, extracts the context and prefetch data,
+    and creates a CdsFhirData object. It then returns a Document object containing the FHIR data
+    and any extracted text content from DocumentReference resources.
+
+    Args:
+        in_data (CDSRequest): The input CDSRequest object containing context and prefetch data.
+
+    Returns:
+        Document: A Document object with the following attributes:
+            - data: Either a string representation of the prefetch data, or if a DocumentReference
+              is present, the text content from that resource
+            - hl7: Contains the CdsFhirData object with context and prefetch data
+
+    Raises:
+        ValueError: If neither prefetch nor fhirServer is provided in the input data
+        NotImplementedError: If fhirServer is provided (not yet implemented)
+        ValueError: If the provided prefetch data is invalid
+
+    Note:
+        The method currently only supports prefetch data and does not handle FHIR server interactions.
+        When a DocumentReference resource is present in the prefetch data, its text content will be
+        extracted and stored as the Document's data field.
+    """
+    if in_data.prefetch is None and in_data.fhirServer is None:
+        raise ValueError(
+            "Either prefetch or fhirServer must be provided to extract FHIR data!"
+        )
+
+    if in_data.fhirServer is not None:
+        raise NotImplementedError("FHIR server is not implemented yet!")
+
+    try:
+        cds_fhir_data = CdsFhirData.create(
+            context=in_data.context.model_dump(), prefetch=in_data.prefetch
+        )
+    except Exception as e:
+        raise ValueError("Invalid prefetch data provided: {e}!") from e
+
+    doc = Document(data=str(cds_fhir_data.model_dump_prefetch()))
+
+    # Extract text from DocumentReference resources if present
+    for entry in cds_fhir_data.prefetch.entry_field:
+        if entry.resource_field.resourceType == "DocumentReference":
+            doc.data = entry.resource_field.text_field.div_field
+
+    doc.hl7.set_fhir_data(cds_fhir_data)
+
+    return doc
+
+
+
+ +
+ +
+ + +

+ output(out_data) + +

+ + +
+ +

Generates a CDSResponse object from a processed Document object.

+

This method takes a Document object that has been processed and potentially +contains CDS cards and system actions. It creates and returns a CDSResponse +object based on the contents of the Document.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ out_data + +
+

A Document object potentially containing CDS cards + and system actions.

+
+

+ + TYPE: + Document + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CDSResponse + +
+

A response object containing CDS cards and optional system actions. + If no cards are found in the Document, an empty list of cards is returned.

+
+

+ + TYPE: + CDSResponse + +

+
+ + +
+ Note +
    +
  • If out_data.cds_cards is None, a warning is logged and an empty list of cards is returned.
  • +
  • System actions (out_data.cds_actions) are included in the response if present.
  • +
+
+
+ Source code in healthchain/io/cdsfhirconnector.py +
def output(self, out_data: Document) -> CDSResponse:
+    """
+    Generates a CDSResponse object from a processed Document object.
+
+    This method takes a Document object that has been processed and potentially
+    contains CDS cards and system actions. It creates and returns a CDSResponse
+    object based on the contents of the Document.
+
+    Args:
+        out_data (Document): A Document object potentially containing CDS cards
+                             and system actions.
+
+    Returns:
+        CDSResponse: A response object containing CDS cards and optional system actions.
+                     If no cards are found in the Document, an empty list of cards is returned.
+
+    Note:
+        - If out_data.cds_cards is None, a warning is logged and an empty list of cards is returned.
+        - System actions (out_data.cds_actions) are included in the response if present.
+    """
+    if out_data.cds.get_cards() is None:
+        log.warning("No CDS cards found in Document, returning empty list of cards")
+        return CDSResponse(cards=[])
+
+    return CDSResponse(
+        cards=out_data.cds.get_cards(), systemActions=out_data.cds.get_actions()
+    )
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/containers/index.html b/api/containers/index.html new file mode 100644 index 0000000..df8a229 --- /dev/null +++ b/api/containers/index.html @@ -0,0 +1,7235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Containers - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Containers

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ BaseDocument + + + + dataclass + + +

+ + +
+

+ Bases: DataContainer[str]

+ + +

Base document container for raw text content.

+ + + + + + +
+ Source code in healthchain/io/containers/base.py +
50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
@dataclass
+class BaseDocument(DataContainer[str]):
+    """Base document container for raw text content."""
+
+    data: str
+    text: str = field(init=False)
+
+    def __post_init__(self):
+        self.text = self.data
+
+    def char_count(self) -> int:
+        return len(self.text)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ DataContainer + + + + dataclass + + +

+ + +
+

+ Bases: Generic[T]

+ + +

A generic container for data.

+

This class represents a container for data with a specific type T.

+ + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
data +
+

The data stored in the container.

+
+

+ + TYPE: + T + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
to_dict +
+

Converts the container's data to a dictionary.

+
+
to_json +
+

Converts the container's data to a JSON string.

+
+
from_dict +
+

Dict[str, Any]) -> "DataContainer": +Creates a DataContainer instance from a dictionary.

+
+
from_json +
+

str) -> "DataContainer": +Creates a DataContainer instance from a JSON string.

+
+
+ + + + + + +
+ Source code in healthchain/io/containers/base.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
@dataclass
+class DataContainer(Generic[T]):
+    """
+    A generic container for data.
+
+    This class represents a container for data with a specific type T.
+
+    Attributes:
+        data (T): The data stored in the container.
+
+    Methods:
+        to_dict() -> Dict[str, Any]:
+            Converts the container's data to a dictionary.
+
+        to_json() -> str:
+            Converts the container's data to a JSON string.
+
+        from_dict(cls, data: Dict[str, Any]) -> "DataContainer":
+            Creates a DataContainer instance from a dictionary.
+
+        from_json(cls, json_str: str) -> "DataContainer":
+            Creates a DataContainer instance from a JSON string.
+    """
+
+    data: T
+
+    def to_dict(self) -> Dict[str, Any]:
+        return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
+
+    def to_json(self) -> str:
+        return json.dumps(self.to_dict())
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]) -> "DataContainer":
+        return cls(**data)
+
+    @classmethod
+    def from_json(cls, json_str: str) -> "DataContainer":
+        return cls.from_dict(json.loads(json_str))
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ Document + + + + dataclass + + +

+ + +
+

+ Bases: BaseDocument

+ + +

A document container that extends BaseDocument with rich annotation capabilities.

+

This class extends BaseDocument to handle textual document data and annotations from +various sources. It serves as the main data structure passed through processing pipelines, +accumulating annotations and analysis results at each step.

+

The Document class provides a comprehensive representation that can include: +- Raw text and basic tokenization +- NLP annotations (tokens, entities, embeddings, spaCy docs) +- Clinical concepts (problems, medications, allergies) +- Structured clinical documents (CCD, FHIR) +- Clinical decision support results (cards, actions) +- ML model outputs (Hugging Face, LangChain)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
nlp +
+

Container for NLP-related annotations like tokens and entities

+
+

+ + TYPE: + NlpAnnotations + +

+
concepts +
+

Container for extracted medical concepts

+
+

+ + TYPE: + ConceptLists + +

+
hl7 +
+

Container for structured clinical documents (CCD, FHIR)

+
+

+ + TYPE: + StructuredData + +

+
cds +
+

Container for clinical decision support results

+
+

+ + TYPE: + CdsAnnotations + +

+
models +
+

Container for ML model outputs

+
+

+ + TYPE: + ModelOutputs + +

+
+

The class provides methods to: +- Add and access medical concepts +- Generate structured clinical documents +- Get basic text statistics +- Iterate over tokens +- Access raw text

+ + +
+ Inherits from +

BaseDocument: Provides base document functionality and raw text storage

+
+ + + + + +
+ Source code in healthchain/io/containers/document.py +
@dataclass
+class Document(BaseDocument):
+    """
+    A document container that extends BaseDocument with rich annotation capabilities.
+
+    This class extends BaseDocument to handle textual document data and annotations from
+    various sources. It serves as the main data structure passed through processing pipelines,
+    accumulating annotations and analysis results at each step.
+
+    The Document class provides a comprehensive representation that can include:
+    - Raw text and basic tokenization
+    - NLP annotations (tokens, entities, embeddings, spaCy docs)
+    - Clinical concepts (problems, medications, allergies)
+    - Structured clinical documents (CCD, FHIR)
+    - Clinical decision support results (cards, actions)
+    - ML model outputs (Hugging Face, LangChain)
+
+    Attributes:
+        nlp (NlpAnnotations): Container for NLP-related annotations like tokens and entities
+        concepts (ConceptLists): Container for extracted medical concepts
+        hl7 (StructuredData): Container for structured clinical documents (CCD, FHIR)
+        cds (CdsAnnotations): Container for clinical decision support results
+        models (ModelOutputs): Container for ML model outputs
+
+    The class provides methods to:
+    - Add and access medical concepts
+    - Generate structured clinical documents
+    - Get basic text statistics
+    - Iterate over tokens
+    - Access raw text
+
+    Inherits from:
+        BaseDocument: Provides base document functionality and raw text storage
+    """
+
+    _nlp: NlpAnnotations = field(default_factory=NlpAnnotations)
+    _concepts: ConceptLists = field(default_factory=ConceptLists)
+    _hl7: HL7Data = field(default_factory=HL7Data)
+    _cds: CdsAnnotations = field(default_factory=CdsAnnotations)
+    _models: ModelOutputs = field(default_factory=ModelOutputs)
+
+    @property
+    def nlp(self) -> NlpAnnotations:
+        return self._nlp
+
+    @property
+    def concepts(self) -> ConceptLists:
+        return self._concepts
+
+    @property
+    def hl7(self) -> HL7Data:
+        return self._hl7
+
+    @property
+    def cds(self) -> CdsAnnotations:
+        return self._cds
+
+    @property
+    def models(self) -> ModelOutputs:
+        return self._models
+
+    def __post_init__(self):
+        """Initialize the document with basic tokenization if needed."""
+        super().__post_init__()
+        self.text = self.data
+        if not self._nlp._tokens:
+            self._nlp._tokens = self.text.split()  # Basic tokenization if not provided
+
+    def add_concepts(
+        self,
+        problems: List[ProblemConcept] = None,
+        medications: List[MedicationConcept] = None,
+        allergies: List[AllergyConcept] = None,
+    ):
+        """
+        Add extracted medical concepts to the document.
+
+        This method adds medical concepts (problems, medications, allergies) to their
+        respective lists in the document's concepts container. Each concept type is
+        optional and will only be added if provided.
+
+        Args:
+            problems (List[ProblemConcept], optional): List of medical problems/conditions
+                to add to the document. Defaults to None.
+            medications (List[MedicationConcept], optional): List of medications
+                to add to the document. Defaults to None.
+            allergies (List[AllergyConcept], optional): List of allergies
+                to add to the document. Defaults to None.
+
+        Example:
+            >>> doc.add_concepts(
+            ...     problems=[ProblemConcept(display_name="Hypertension")],
+            ...     medications=[MedicationConcept(display_name="Aspirin")]
+            ... )
+        """
+        if problems:
+            self._concepts.problems.extend(problems)
+        if medications:
+            self._concepts.medications.extend(medications)
+        if allergies:
+            self._concepts.allergies.extend(allergies)
+
+    def add_cds_cards(
+        self, cards: Union[List[Card], List[Dict[str, Any]]]
+    ) -> List[Card]:
+        if not cards:
+            raise ValueError("Cards must be provided as a list!")
+
+        try:
+            if isinstance(cards[0], dict):
+                cards = [Card(**card) for card in cards]
+            elif not isinstance(cards[0], Card):
+                raise TypeError("Cards must be either Card objects or dictionaries")
+        except (IndexError, KeyError) as e:
+            raise ValueError("Invalid card format") from e
+
+        return self._cds.set_cards(cards)
+
+    def add_cds_actions(
+        self, actions: Union[List[Action], List[Dict[str, Any]]]
+    ) -> List[Action]:
+        if not actions:
+            raise ValueError("Actions must be provided as a list!")
+
+        try:
+            if isinstance(actions[0], dict):
+                actions = [Action(**action) for action in actions]
+            elif not isinstance(actions[0], Action):
+                raise TypeError("Actions must be either Action objects or dictionaries")
+        except (IndexError, KeyError) as e:
+            raise ValueError("Invalid action format") from e
+
+        return self._cds.set_actions(actions)
+
+    def generate_ccd(self, overwrite: bool = False) -> CcdData:
+        """
+        Generate a CCD (Continuity of Care Document) from the current medical concepts.
+
+        This method creates or updates a CCD in the hl7 container using the
+        medical concepts (problems, medications, allergies) currently stored in the document.
+        The CCD is a standard format for exchanging clinical information.
+
+        Args:
+            overwrite (bool, optional): If True, overwrites any existing CCD data.
+                If False, merges with existing CCD data. Defaults to False.
+
+        Returns:
+            CcdData: The generated CCD data.
+
+        Example:
+            >>> doc.add_concepts(problems=[ProblemConcept(display_name="Hypertension")])
+            >>> doc.generate_ccd()  # Creates CCD with the hypertension problem
+        """
+        return self._hl7.update_ccd_from_concepts(self._concepts, overwrite)
+
+    def word_count(self) -> int:
+        """
+        Get the word count from the document's text.
+        """
+        return len(self._nlp._tokens)
+
+    def __iter__(self) -> Iterator[str]:
+        return iter(self._nlp._tokens)
+
+    def __len__(self) -> int:
+        return len(self.text)
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __post_init__() + +

+ + +
+ +

Initialize the document with basic tokenization if needed.

+ +
+ Source code in healthchain/io/containers/document.py +
def __post_init__(self):
+    """Initialize the document with basic tokenization if needed."""
+    super().__post_init__()
+    self.text = self.data
+    if not self._nlp._tokens:
+        self._nlp._tokens = self.text.split()  # Basic tokenization if not provided
+
+
+
+ +
+ +
+ + +

+ add_concepts(problems=None, medications=None, allergies=None) + +

+ + +
+ +

Add extracted medical concepts to the document.

+

This method adds medical concepts (problems, medications, allergies) to their +respective lists in the document's concepts container. Each concept type is +optional and will only be added if provided.

+ + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ problems + +
+

List of medical problems/conditions +to add to the document. Defaults to None.

+
+

+ + TYPE: + List[ProblemConcept] + + + DEFAULT: + None + +

+
+ medications + +
+

List of medications +to add to the document. Defaults to None.

+
+

+ + TYPE: + List[MedicationConcept] + + + DEFAULT: + None + +

+
+ allergies + +
+

List of allergies +to add to the document. Defaults to None.

+
+

+ + TYPE: + List[AllergyConcept] + + + DEFAULT: + None + +

+
+ + +
+ Example +
+
+
+

doc.add_concepts( +... problems=[ProblemConcept(display_name="Hypertension")], +... medications=[MedicationConcept(display_name="Aspirin")] +... )

+
+
+
+
+
+ Source code in healthchain/io/containers/document.py +
def add_concepts(
+    self,
+    problems: List[ProblemConcept] = None,
+    medications: List[MedicationConcept] = None,
+    allergies: List[AllergyConcept] = None,
+):
+    """
+    Add extracted medical concepts to the document.
+
+    This method adds medical concepts (problems, medications, allergies) to their
+    respective lists in the document's concepts container. Each concept type is
+    optional and will only be added if provided.
+
+    Args:
+        problems (List[ProblemConcept], optional): List of medical problems/conditions
+            to add to the document. Defaults to None.
+        medications (List[MedicationConcept], optional): List of medications
+            to add to the document. Defaults to None.
+        allergies (List[AllergyConcept], optional): List of allergies
+            to add to the document. Defaults to None.
+
+    Example:
+        >>> doc.add_concepts(
+        ...     problems=[ProblemConcept(display_name="Hypertension")],
+        ...     medications=[MedicationConcept(display_name="Aspirin")]
+        ... )
+    """
+    if problems:
+        self._concepts.problems.extend(problems)
+    if medications:
+        self._concepts.medications.extend(medications)
+    if allergies:
+        self._concepts.allergies.extend(allergies)
+
+
+
+ +
+ +
+ + +

+ generate_ccd(overwrite=False) + +

+ + +
+ +

Generate a CCD (Continuity of Care Document) from the current medical concepts.

+

This method creates or updates a CCD in the hl7 container using the +medical concepts (problems, medications, allergies) currently stored in the document. +The CCD is a standard format for exchanging clinical information.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ overwrite + +
+

If True, overwrites any existing CCD data. +If False, merges with existing CCD data. Defaults to False.

+
+

+ + TYPE: + bool + + + DEFAULT: + False + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CcdData + +
+

The generated CCD data.

+
+

+ + TYPE: + CcdData + +

+
+ + +
+ Example +
+
+
+

doc.add_concepts(problems=[ProblemConcept(display_name="Hypertension")]) +doc.generate_ccd() # Creates CCD with the hypertension problem

+
+
+
+
+
+ Source code in healthchain/io/containers/document.py +
def generate_ccd(self, overwrite: bool = False) -> CcdData:
+    """
+    Generate a CCD (Continuity of Care Document) from the current medical concepts.
+
+    This method creates or updates a CCD in the hl7 container using the
+    medical concepts (problems, medications, allergies) currently stored in the document.
+    The CCD is a standard format for exchanging clinical information.
+
+    Args:
+        overwrite (bool, optional): If True, overwrites any existing CCD data.
+            If False, merges with existing CCD data. Defaults to False.
+
+    Returns:
+        CcdData: The generated CCD data.
+
+    Example:
+        >>> doc.add_concepts(problems=[ProblemConcept(display_name="Hypertension")])
+        >>> doc.generate_ccd()  # Creates CCD with the hypertension problem
+    """
+    return self._hl7.update_ccd_from_concepts(self._concepts, overwrite)
+
+
+
+ +
+ +
+ + +

+ word_count() + +

+ + +
+ +

Get the word count from the document's text.

+ +
+ Source code in healthchain/io/containers/document.py +
def word_count(self) -> int:
+    """
+    Get the word count from the document's text.
+    """
+    return len(self._nlp._tokens)
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ Tabular + + + + dataclass + + +

+ + +
+

+ Bases: DataContainer[DataFrame]

+ + +

A container for tabular data, wrapping a pandas DataFrame.

+ + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
data +
+

The pandas DataFrame containing the tabular data.

+
+

+ + TYPE: + DataFrame + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
__post_init__ +
+

Validates that the data is a pandas DataFrame.

+
+
columns +
+

Property that returns a list of column names.

+
+
index +
+

Property that returns the DataFrame's index.

+
+
dtypes +
+

Property that returns a dictionary of column names and their data types.

+
+
column_count +
+

Returns the number of columns in the DataFrame.

+
+
row_count +
+

Returns the number of rows in the DataFrame.

+
+
get_dtype +
+

str): Returns the data type of a specific column.

+
+
__iter__ +
+

Returns an iterator over the column names.

+
+
__len__ +
+

Returns the number of rows in the DataFrame.

+
+
describe +
+

Returns a string description of the tabular data.

+
+
remove_column +
+

str): Removes a column from the DataFrame.

+
+
from_csv +
+

str, **kwargs): Class method to create a Tabular object from a CSV file.

+
+
from_dict +
+

Dict[str, Any]): Class method to create a Tabular object from a dictionary.

+
+
to_csv +
+

str, **kwargs): Saves the DataFrame to a CSV file.

+
+
+ + + + + + +
+ Source code in healthchain/io/containers/tabular.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
@dataclass
+class Tabular(DataContainer[pd.DataFrame]):
+    """
+    A container for tabular data, wrapping a pandas DataFrame.
+
+    Attributes:
+        data (pd.DataFrame): The pandas DataFrame containing the tabular data.
+
+    Methods:
+        __post_init__(): Validates that the data is a pandas DataFrame.
+        columns: Property that returns a list of column names.
+        index: Property that returns the DataFrame's index.
+        dtypes: Property that returns a dictionary of column names and their data types.
+        column_count(): Returns the number of columns in the DataFrame.
+        row_count(): Returns the number of rows in the DataFrame.
+        get_dtype(column: str): Returns the data type of a specific column.
+        __iter__(): Returns an iterator over the column names.
+        __len__(): Returns the number of rows in the DataFrame.
+        describe(): Returns a string description of the tabular data.
+        remove_column(name: str): Removes a column from the DataFrame.
+        from_csv(path: str, **kwargs): Class method to create a Tabular object from a CSV file.
+        from_dict(data: Dict[str, Any]): Class method to create a Tabular object from a dictionary.
+        to_csv(path: str, **kwargs): Saves the DataFrame to a CSV file.
+    """
+
+    def __post_init__(self):
+        if not isinstance(self.data, pd.DataFrame):
+            raise TypeError("data must be a pandas DataFrame")
+
+    @property
+    def columns(self) -> List[str]:
+        return list(self.data.columns)
+
+    @property
+    def index(self) -> pd.Index:
+        return self.data.index
+
+    @property
+    def dtypes(self) -> Dict[str, str]:
+        return {col: str(dtype) for col, dtype in self.data.dtypes.items()}
+
+    def column_count(self) -> int:
+        return len(self.columns)
+
+    def row_count(self) -> int:
+        return len(self.data)
+
+    def get_dtype(self, column: str) -> str:
+        return str(self.data[column].dtype)
+
+    def __iter__(self) -> Iterator[str]:
+        return iter(self.columns)
+
+    def __len__(self) -> int:
+        return self.row_count()
+
+    def describe(self) -> str:
+        return f"Tabular data with {self.column_count()} columns and {self.row_count()} rows"
+
+    def remove_column(self, name: str) -> None:
+        self.data.drop(columns=[name], inplace=True)
+
+    @classmethod
+    def from_csv(cls, path: str, **kwargs) -> "Tabular":
+        return cls(pd.read_csv(path, **kwargs))
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]) -> "Tabular":
+        df = pd.DataFrame(**data["data"])
+        return cls(df)
+
+    def to_csv(self, path: str, **kwargs) -> None:
+        self.data.to_csv(path, **kwargs)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + +
+ + + +

+ base + + +

+ +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ BaseDocument + + + + dataclass + + +

+ + +
+

+ Bases: DataContainer[str]

+ + +

Base document container for raw text content.

+ + + + + + +
+ Source code in healthchain/io/containers/base.py +
50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
@dataclass
+class BaseDocument(DataContainer[str]):
+    """Base document container for raw text content."""
+
+    data: str
+    text: str = field(init=False)
+
+    def __post_init__(self):
+        self.text = self.data
+
+    def char_count(self) -> int:
+        return len(self.text)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ DataContainer + + + + dataclass + + +

+ + +
+

+ Bases: Generic[T]

+ + +

A generic container for data.

+

This class represents a container for data with a specific type T.

+ + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
data +
+

The data stored in the container.

+
+

+ + TYPE: + T + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
to_dict +
+

Converts the container's data to a dictionary.

+
+
to_json +
+

Converts the container's data to a JSON string.

+
+
from_dict +
+

Dict[str, Any]) -> "DataContainer": +Creates a DataContainer instance from a dictionary.

+
+
from_json +
+

str) -> "DataContainer": +Creates a DataContainer instance from a JSON string.

+
+
+ + + + + + +
+ Source code in healthchain/io/containers/base.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
@dataclass
+class DataContainer(Generic[T]):
+    """
+    A generic container for data.
+
+    This class represents a container for data with a specific type T.
+
+    Attributes:
+        data (T): The data stored in the container.
+
+    Methods:
+        to_dict() -> Dict[str, Any]:
+            Converts the container's data to a dictionary.
+
+        to_json() -> str:
+            Converts the container's data to a JSON string.
+
+        from_dict(cls, data: Dict[str, Any]) -> "DataContainer":
+            Creates a DataContainer instance from a dictionary.
+
+        from_json(cls, json_str: str) -> "DataContainer":
+            Creates a DataContainer instance from a JSON string.
+    """
+
+    data: T
+
+    def to_dict(self) -> Dict[str, Any]:
+        return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
+
+    def to_json(self) -> str:
+        return json.dumps(self.to_dict())
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]) -> "DataContainer":
+        return cls(**data)
+
+    @classmethod
+    def from_json(cls, json_str: str) -> "DataContainer":
+        return cls.from_dict(json.loads(json_str))
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + +

+ document + + +

+ +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdsAnnotations + + + + dataclass + + +

+ + +
+ + +

Container for Clinical Decision Support (CDS) results.

+

This class stores the outputs from clinical decision support systems, +including CDS Hooks cards and suggested clinical actions. The cards contain +recommendations, warnings, and other decision support content that can be +displayed to clinicians. Actions represent specific clinical tasks or +interventions that are suggested based on the analysis.

+ + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
_cards +
+

Internal storage for CDS Hooks cards containing +clinical recommendations, warnings, or other decision support content.

+
+

+ + TYPE: + Optional[List[Card]] + +

+
_actions +
+

Internal storage for suggested clinical actions +that could be taken based on the CDS analysis.

+
+

+ + TYPE: + Optional[List[Action]] + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
set_cards +
+

List[Card]): Sets the list of CDS Hooks cards

+
+
get_cards +
+

Returns the current list of cards if any exist

+
+
set_actions +
+

List[Action]): Sets the list of suggested clinical actions

+
+
get_actions +
+

Returns the current list of actions if any exist

+
+
+ + + + + + +
+ Source code in healthchain/io/containers/document.py +
@dataclass
+class CdsAnnotations:
+    """
+    Container for Clinical Decision Support (CDS) results.
+
+    This class stores the outputs from clinical decision support systems,
+    including CDS Hooks cards and suggested clinical actions. The cards contain
+    recommendations, warnings, and other decision support content that can be
+    displayed to clinicians. Actions represent specific clinical tasks or
+    interventions that are suggested based on the analysis.
+
+    Attributes:
+        _cards (Optional[List[Card]]): Internal storage for CDS Hooks cards containing
+            clinical recommendations, warnings, or other decision support content.
+        _actions (Optional[List[Action]]): Internal storage for suggested clinical actions
+            that could be taken based on the CDS analysis.
+
+    Methods:
+        set_cards(cards: List[Card]): Sets the list of CDS Hooks cards
+        get_cards() -> Optional[List[Card]]: Returns the current list of cards if any exist
+        set_actions(actions: List[Action]): Sets the list of suggested clinical actions
+        get_actions() -> Optional[List[Action]]: Returns the current list of actions if any exist
+    """
+
+    _cards: Optional[List[Card]] = None
+    _actions: Optional[List[Action]] = None
+
+    def set_cards(self, cards: List[Card]):
+        self._cards = cards
+
+    def get_cards(self) -> Optional[List[Card]]:
+        return self._cards
+
+    def set_actions(self, actions: List[Action]):
+        self._actions = actions
+
+    def get_actions(self) -> Optional[List[Action]]:
+        return self._actions
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ Document + + + + dataclass + + +

+ + +
+

+ Bases: BaseDocument

+ + +

A document container that extends BaseDocument with rich annotation capabilities.

+

This class extends BaseDocument to handle textual document data and annotations from +various sources. It serves as the main data structure passed through processing pipelines, +accumulating annotations and analysis results at each step.

+

The Document class provides a comprehensive representation that can include: +- Raw text and basic tokenization +- NLP annotations (tokens, entities, embeddings, spaCy docs) +- Clinical concepts (problems, medications, allergies) +- Structured clinical documents (CCD, FHIR) +- Clinical decision support results (cards, actions) +- ML model outputs (Hugging Face, LangChain)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
nlp +
+

Container for NLP-related annotations like tokens and entities

+
+

+ + TYPE: + NlpAnnotations + +

+
concepts +
+

Container for extracted medical concepts

+
+

+ + TYPE: + ConceptLists + +

+
hl7 +
+

Container for structured clinical documents (CCD, FHIR)

+
+

+ + TYPE: + StructuredData + +

+
cds +
+

Container for clinical decision support results

+
+

+ + TYPE: + CdsAnnotations + +

+
models +
+

Container for ML model outputs

+
+

+ + TYPE: + ModelOutputs + +

+
+

The class provides methods to: +- Add and access medical concepts +- Generate structured clinical documents +- Get basic text statistics +- Iterate over tokens +- Access raw text

+ + +
+ Inherits from +

BaseDocument: Provides base document functionality and raw text storage

+
+ + + + + +
+ Source code in healthchain/io/containers/document.py +
@dataclass
+class Document(BaseDocument):
+    """
+    A document container that extends BaseDocument with rich annotation capabilities.
+
+    This class extends BaseDocument to handle textual document data and annotations from
+    various sources. It serves as the main data structure passed through processing pipelines,
+    accumulating annotations and analysis results at each step.
+
+    The Document class provides a comprehensive representation that can include:
+    - Raw text and basic tokenization
+    - NLP annotations (tokens, entities, embeddings, spaCy docs)
+    - Clinical concepts (problems, medications, allergies)
+    - Structured clinical documents (CCD, FHIR)
+    - Clinical decision support results (cards, actions)
+    - ML model outputs (Hugging Face, LangChain)
+
+    Attributes:
+        nlp (NlpAnnotations): Container for NLP-related annotations like tokens and entities
+        concepts (ConceptLists): Container for extracted medical concepts
+        hl7 (StructuredData): Container for structured clinical documents (CCD, FHIR)
+        cds (CdsAnnotations): Container for clinical decision support results
+        models (ModelOutputs): Container for ML model outputs
+
+    The class provides methods to:
+    - Add and access medical concepts
+    - Generate structured clinical documents
+    - Get basic text statistics
+    - Iterate over tokens
+    - Access raw text
+
+    Inherits from:
+        BaseDocument: Provides base document functionality and raw text storage
+    """
+
+    _nlp: NlpAnnotations = field(default_factory=NlpAnnotations)
+    _concepts: ConceptLists = field(default_factory=ConceptLists)
+    _hl7: HL7Data = field(default_factory=HL7Data)
+    _cds: CdsAnnotations = field(default_factory=CdsAnnotations)
+    _models: ModelOutputs = field(default_factory=ModelOutputs)
+
+    @property
+    def nlp(self) -> NlpAnnotations:
+        return self._nlp
+
+    @property
+    def concepts(self) -> ConceptLists:
+        return self._concepts
+
+    @property
+    def hl7(self) -> HL7Data:
+        return self._hl7
+
+    @property
+    def cds(self) -> CdsAnnotations:
+        return self._cds
+
+    @property
+    def models(self) -> ModelOutputs:
+        return self._models
+
+    def __post_init__(self):
+        """Initialize the document with basic tokenization if needed."""
+        super().__post_init__()
+        self.text = self.data
+        if not self._nlp._tokens:
+            self._nlp._tokens = self.text.split()  # Basic tokenization if not provided
+
+    def add_concepts(
+        self,
+        problems: List[ProblemConcept] = None,
+        medications: List[MedicationConcept] = None,
+        allergies: List[AllergyConcept] = None,
+    ):
+        """
+        Add extracted medical concepts to the document.
+
+        This method adds medical concepts (problems, medications, allergies) to their
+        respective lists in the document's concepts container. Each concept type is
+        optional and will only be added if provided.
+
+        Args:
+            problems (List[ProblemConcept], optional): List of medical problems/conditions
+                to add to the document. Defaults to None.
+            medications (List[MedicationConcept], optional): List of medications
+                to add to the document. Defaults to None.
+            allergies (List[AllergyConcept], optional): List of allergies
+                to add to the document. Defaults to None.
+
+        Example:
+            >>> doc.add_concepts(
+            ...     problems=[ProblemConcept(display_name="Hypertension")],
+            ...     medications=[MedicationConcept(display_name="Aspirin")]
+            ... )
+        """
+        if problems:
+            self._concepts.problems.extend(problems)
+        if medications:
+            self._concepts.medications.extend(medications)
+        if allergies:
+            self._concepts.allergies.extend(allergies)
+
+    def add_cds_cards(
+        self, cards: Union[List[Card], List[Dict[str, Any]]]
+    ) -> List[Card]:
+        if not cards:
+            raise ValueError("Cards must be provided as a list!")
+
+        try:
+            if isinstance(cards[0], dict):
+                cards = [Card(**card) for card in cards]
+            elif not isinstance(cards[0], Card):
+                raise TypeError("Cards must be either Card objects or dictionaries")
+        except (IndexError, KeyError) as e:
+            raise ValueError("Invalid card format") from e
+
+        return self._cds.set_cards(cards)
+
+    def add_cds_actions(
+        self, actions: Union[List[Action], List[Dict[str, Any]]]
+    ) -> List[Action]:
+        if not actions:
+            raise ValueError("Actions must be provided as a list!")
+
+        try:
+            if isinstance(actions[0], dict):
+                actions = [Action(**action) for action in actions]
+            elif not isinstance(actions[0], Action):
+                raise TypeError("Actions must be either Action objects or dictionaries")
+        except (IndexError, KeyError) as e:
+            raise ValueError("Invalid action format") from e
+
+        return self._cds.set_actions(actions)
+
+    def generate_ccd(self, overwrite: bool = False) -> CcdData:
+        """
+        Generate a CCD (Continuity of Care Document) from the current medical concepts.
+
+        This method creates or updates a CCD in the hl7 container using the
+        medical concepts (problems, medications, allergies) currently stored in the document.
+        The CCD is a standard format for exchanging clinical information.
+
+        Args:
+            overwrite (bool, optional): If True, overwrites any existing CCD data.
+                If False, merges with existing CCD data. Defaults to False.
+
+        Returns:
+            CcdData: The generated CCD data.
+
+        Example:
+            >>> doc.add_concepts(problems=[ProblemConcept(display_name="Hypertension")])
+            >>> doc.generate_ccd()  # Creates CCD with the hypertension problem
+        """
+        return self._hl7.update_ccd_from_concepts(self._concepts, overwrite)
+
+    def word_count(self) -> int:
+        """
+        Get the word count from the document's text.
+        """
+        return len(self._nlp._tokens)
+
+    def __iter__(self) -> Iterator[str]:
+        return iter(self._nlp._tokens)
+
+    def __len__(self) -> int:
+        return len(self.text)
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ __post_init__() + +

+ + +
+ +

Initialize the document with basic tokenization if needed.

+ +
+ Source code in healthchain/io/containers/document.py +
def __post_init__(self):
+    """Initialize the document with basic tokenization if needed."""
+    super().__post_init__()
+    self.text = self.data
+    if not self._nlp._tokens:
+        self._nlp._tokens = self.text.split()  # Basic tokenization if not provided
+
+
+
+ +
+ +
+ + +

+ add_concepts(problems=None, medications=None, allergies=None) + +

+ + +
+ +

Add extracted medical concepts to the document.

+

This method adds medical concepts (problems, medications, allergies) to their +respective lists in the document's concepts container. Each concept type is +optional and will only be added if provided.

+ + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ problems + +
+

List of medical problems/conditions +to add to the document. Defaults to None.

+
+

+ + TYPE: + List[ProblemConcept] + + + DEFAULT: + None + +

+
+ medications + +
+

List of medications +to add to the document. Defaults to None.

+
+

+ + TYPE: + List[MedicationConcept] + + + DEFAULT: + None + +

+
+ allergies + +
+

List of allergies +to add to the document. Defaults to None.

+
+

+ + TYPE: + List[AllergyConcept] + + + DEFAULT: + None + +

+
+ + +
+ Example +
+
+
+

doc.add_concepts( +... problems=[ProblemConcept(display_name="Hypertension")], +... medications=[MedicationConcept(display_name="Aspirin")] +... )

+
+
+
+
+
+ Source code in healthchain/io/containers/document.py +
def add_concepts(
+    self,
+    problems: List[ProblemConcept] = None,
+    medications: List[MedicationConcept] = None,
+    allergies: List[AllergyConcept] = None,
+):
+    """
+    Add extracted medical concepts to the document.
+
+    This method adds medical concepts (problems, medications, allergies) to their
+    respective lists in the document's concepts container. Each concept type is
+    optional and will only be added if provided.
+
+    Args:
+        problems (List[ProblemConcept], optional): List of medical problems/conditions
+            to add to the document. Defaults to None.
+        medications (List[MedicationConcept], optional): List of medications
+            to add to the document. Defaults to None.
+        allergies (List[AllergyConcept], optional): List of allergies
+            to add to the document. Defaults to None.
+
+    Example:
+        >>> doc.add_concepts(
+        ...     problems=[ProblemConcept(display_name="Hypertension")],
+        ...     medications=[MedicationConcept(display_name="Aspirin")]
+        ... )
+    """
+    if problems:
+        self._concepts.problems.extend(problems)
+    if medications:
+        self._concepts.medications.extend(medications)
+    if allergies:
+        self._concepts.allergies.extend(allergies)
+
+
+
+ +
+ +
+ + +

+ generate_ccd(overwrite=False) + +

+ + +
+ +

Generate a CCD (Continuity of Care Document) from the current medical concepts.

+

This method creates or updates a CCD in the hl7 container using the +medical concepts (problems, medications, allergies) currently stored in the document. +The CCD is a standard format for exchanging clinical information.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ overwrite + +
+

If True, overwrites any existing CCD data. +If False, merges with existing CCD data. Defaults to False.

+
+

+ + TYPE: + bool + + + DEFAULT: + False + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CcdData + +
+

The generated CCD data.

+
+

+ + TYPE: + CcdData + +

+
+ + +
+ Example +
+
+
+

doc.add_concepts(problems=[ProblemConcept(display_name="Hypertension")]) +doc.generate_ccd() # Creates CCD with the hypertension problem

+
+
+
+
+
+ Source code in healthchain/io/containers/document.py +
def generate_ccd(self, overwrite: bool = False) -> CcdData:
+    """
+    Generate a CCD (Continuity of Care Document) from the current medical concepts.
+
+    This method creates or updates a CCD in the hl7 container using the
+    medical concepts (problems, medications, allergies) currently stored in the document.
+    The CCD is a standard format for exchanging clinical information.
+
+    Args:
+        overwrite (bool, optional): If True, overwrites any existing CCD data.
+            If False, merges with existing CCD data. Defaults to False.
+
+    Returns:
+        CcdData: The generated CCD data.
+
+    Example:
+        >>> doc.add_concepts(problems=[ProblemConcept(display_name="Hypertension")])
+        >>> doc.generate_ccd()  # Creates CCD with the hypertension problem
+    """
+    return self._hl7.update_ccd_from_concepts(self._concepts, overwrite)
+
+
+
+ +
+ +
+ + +

+ word_count() + +

+ + +
+ +

Get the word count from the document's text.

+ +
+ Source code in healthchain/io/containers/document.py +
def word_count(self) -> int:
+    """
+    Get the word count from the document's text.
+    """
+    return len(self._nlp._tokens)
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ HL7Data + + + + dataclass + + +

+ + +
+ + +

Container for structured clinical document formats.

+

This class stores and manages structured clinical data in different formats, +including CCD (Continuity of Care Document) and FHIR (Fast Healthcare +Interoperability Resources).

+ + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
_ccd_data +
+

Clinical data in CCD format, containing +problems, medications, allergies and other clinical information.

+
+

+ + TYPE: + Optional[CcdData] + +

+
_fhir_data +
+

Clinical data in FHIR format for +clinical decision support.

+
+

+ + TYPE: + Optional[CdsFhirData] + +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
update_ccd_from_concepts +
+

ConceptLists, overwrite: bool = False) -> CcdData: +Updates the CCD data with new clinical concepts, either by overwriting or extending +existing data.

+
+
get_fhir_data +
+

Returns the FHIR format clinical data if it exists, otherwise None.

+
+
get_ccd_data +
+

Returns the CCD format clinical data if it exists, otherwise None.

+
+
+ + + + + + +
+ Source code in healthchain/io/containers/document.py +
@dataclass
+class HL7Data:
+    """
+    Container for structured clinical document formats.
+
+    This class stores and manages structured clinical data in different formats,
+    including CCD (Continuity of Care Document) and FHIR (Fast Healthcare
+    Interoperability Resources).
+
+    Attributes:
+        _ccd_data (Optional[CcdData]): Clinical data in CCD format, containing
+            problems, medications, allergies and other clinical information.
+        _fhir_data (Optional[CdsFhirData]): Clinical data in FHIR format for
+            clinical decision support.
+
+    Methods:
+        update_ccd_from_concepts(concepts: ConceptLists, overwrite: bool = False) -> CcdData:
+            Updates the CCD data with new clinical concepts, either by overwriting or extending
+            existing data.
+        get_fhir_data() -> Optional[CdsFhirData]:
+            Returns the FHIR format clinical data if it exists, otherwise None.
+        get_ccd_data() -> Optional[CcdData]:
+            Returns the CCD format clinical data if it exists, otherwise None.
+    """
+
+    _ccd_data: Optional[CcdData] = None
+    _fhir_data: Optional[CdsFhirData] = None
+
+    def update_ccd_from_concepts(
+        self, concepts: ConceptLists, overwrite: bool = False
+    ) -> CcdData:
+        """
+        Updates the CCD data with new clinical concepts.
+
+        This method takes a ConceptLists object containing problems, medications,
+        and allergies, and updates the internal CCD data accordingly. If no CCD
+        data exists, it creates a new CcdData instance.
+
+        Args:
+            concepts (ConceptLists): The new clinical concepts to add, containing
+                problems, medications, and allergies lists.
+            overwrite (bool, optional): If True, replaces existing concepts.
+                If False, extends the existing lists. Defaults to False.
+
+        Returns:
+            CcdData: The updated CCD data.
+        """
+        if self._ccd_data is None:
+            self._ccd_data = CcdData()
+
+        if overwrite:
+            self._ccd_data.concepts.problems = concepts.problems
+            self._ccd_data.concepts.medications = concepts.medications
+            self._ccd_data.concepts.allergies = concepts.allergies
+        else:
+            self._ccd_data.concepts.problems.extend(concepts.problems)
+            self._ccd_data.concepts.medications.extend(concepts.medications)
+            self._ccd_data.concepts.allergies.extend(concepts.allergies)
+
+        return self._ccd_data
+
+    def get_fhir_data(self) -> Optional[CdsFhirData]:
+        return self._fhir_data
+
+    def set_fhir_data(self, fhir_data: CdsFhirData):
+        self._fhir_data = fhir_data
+
+    def get_ccd_data(self) -> Optional[CcdData]:
+        return self._ccd_data
+
+    def set_ccd_data(self, ccd_data: CcdData):
+        self._ccd_data = ccd_data
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ update_ccd_from_concepts(concepts, overwrite=False) + +

+ + +
+ +

Updates the CCD data with new clinical concepts.

+

This method takes a ConceptLists object containing problems, medications, +and allergies, and updates the internal CCD data accordingly. If no CCD +data exists, it creates a new CcdData instance.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ concepts + +
+

The new clinical concepts to add, containing +problems, medications, and allergies lists.

+
+

+ + TYPE: + ConceptLists + +

+
+ overwrite + +
+

If True, replaces existing concepts. +If False, extends the existing lists. Defaults to False.

+
+

+ + TYPE: + bool + + + DEFAULT: + False + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CcdData + +
+

The updated CCD data.

+
+

+ + TYPE: + CcdData + +

+
+ +
+ Source code in healthchain/io/containers/document.py +
def update_ccd_from_concepts(
+    self, concepts: ConceptLists, overwrite: bool = False
+) -> CcdData:
+    """
+    Updates the CCD data with new clinical concepts.
+
+    This method takes a ConceptLists object containing problems, medications,
+    and allergies, and updates the internal CCD data accordingly. If no CCD
+    data exists, it creates a new CcdData instance.
+
+    Args:
+        concepts (ConceptLists): The new clinical concepts to add, containing
+            problems, medications, and allergies lists.
+        overwrite (bool, optional): If True, replaces existing concepts.
+            If False, extends the existing lists. Defaults to False.
+
+    Returns:
+        CcdData: The updated CCD data.
+    """
+    if self._ccd_data is None:
+        self._ccd_data = CcdData()
+
+    if overwrite:
+        self._ccd_data.concepts.problems = concepts.problems
+        self._ccd_data.concepts.medications = concepts.medications
+        self._ccd_data.concepts.allergies = concepts.allergies
+    else:
+        self._ccd_data.concepts.problems.extend(concepts.problems)
+        self._ccd_data.concepts.medications.extend(concepts.medications)
+        self._ccd_data.concepts.allergies.extend(concepts.allergies)
+
+    return self._ccd_data
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ ModelOutputs + + + + dataclass + + +

+ + +
+ + +

Container for storing and managing third-party integration model outputs.

+

This class stores outputs from different NLP/ML frameworks like Hugging Face +and LangChain, organizing them by task type. It also maintains a list of +generated text outputs across frameworks.

+ + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
_huggingface_results +
+

Dictionary storing Hugging Face model +outputs, keyed by task name.

+
+

+ + TYPE: + Dict[str, Any] + +

+
_langchain_results +
+

Dictionary storing LangChain outputs, +keyed by task name.

+
+

+ + TYPE: + Dict[str, Any] + +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
add_output +
+

str, task: str, output: Any): Adds a model output for a +specific source and task. For text generation tasks, also extracts and +stores the generated text.

+
+
get_output +
+

str, task: str, default: Any = None) -> Any: Gets the model +output for a specific source and task. Returns default if not found.

+
+
get_generated_text +
+

Returns the list of generated text outputs

+
+
+ + + + + + +
+ Source code in healthchain/io/containers/document.py +
@dataclass
+class ModelOutputs:
+    """
+    Container for storing and managing third-party integration model outputs.
+
+    This class stores outputs from different NLP/ML frameworks like Hugging Face
+    and LangChain, organizing them by task type. It also maintains a list of
+    generated text outputs across frameworks.
+
+    Attributes:
+        _huggingface_results (Dict[str, Any]): Dictionary storing Hugging Face model
+            outputs, keyed by task name.
+        _langchain_results (Dict[str, Any]): Dictionary storing LangChain outputs,
+            keyed by task name.
+
+    Methods:
+        add_output(source: str, task: str, output: Any): Adds a model output for a
+            specific source and task. For text generation tasks, also extracts and
+            stores the generated text.
+        get_output(source: str, task: str, default: Any = None) -> Any: Gets the model
+            output for a specific source and task. Returns default if not found.
+        get_generated_text() -> List[str]: Returns the list of generated text outputs
+    """
+
+    _huggingface_results: Dict[str, Any] = field(default_factory=dict)
+    _langchain_results: Dict[str, Any] = field(default_factory=dict)
+
+    def add_output(self, source: str, task: str, output: Any):
+        if source == "huggingface":
+            self._huggingface_results[task] = output
+        elif source == "langchain":
+            self._langchain_results[task] = output
+        else:
+            raise ValueError(f"Unknown source: {source}")
+
+    def get_output(self, source: str, task: str) -> Any:
+        if source == "huggingface":
+            return self._huggingface_results.get(task, {})
+        elif source == "langchain":
+            return self._langchain_results.get(task, {})
+        raise ValueError(f"Unknown source: {source}")
+
+    def get_generated_text(self, source: str, task: str) -> List[str]:
+        """
+        Returns generated text outputs for a given source and task.
+
+        Handles different output formats for Hugging Face and LangChain. For
+        Hugging Face, it extracts the last message content from chat-style
+        outputs and common keys like "generated_text", "summary_text", and
+        "translation". For LangChain, it converts JSON outputs to strings, and returns
+        the output as is if it is already a string.
+
+        Args:
+            source (str): Framework name (e.g., "huggingface", "langchain").
+            task (str): Task name for retrieving generated text.
+
+        Returns:
+            List[str]: List of generated text outputs, or an empty list if none.
+        """
+        generated_text = []
+
+        if source == "huggingface":
+            # Handle chat-style output format
+            output = self._huggingface_results.get(task)
+            if isinstance(output, list):
+                for entry in output:
+                    text = entry.get("generated_text")
+                    if isinstance(text, list):
+                        last_msg = text[-1]
+                        if isinstance(last_msg, dict) and "content" in last_msg:
+                            generated_text.append(last_msg["content"])
+                    # Otherwise get common huggingface output keys
+                    elif any(
+                        key in entry
+                        for key in ["generated_text", "summary_text", "translation"]
+                    ):
+                        generated_text.append(
+                            text
+                            or entry.get("summary_text")
+                            or entry.get("translation")
+                        )
+            else:
+                logger.warning("HuggingFace output is not a list of dictionaries. ")
+        elif source == "langchain":
+            output = self._langchain_results.get(task)
+            # Check if output is a string
+            if isinstance(output, str):
+                generated_text.append(output)
+            # Try to convert JSON to string
+            elif isinstance(output, dict):
+                try:
+                    import json
+
+                    output_str = json.dumps(output)
+                    generated_text.append(output_str)
+                except Exception:
+                    logger.warning(
+                        "LangChain output is not a string and could not be converted to JSON string. "
+                        "Chains should output either a string or a JSON object."
+                    )
+            else:
+                logger.warning(
+                    "LangChain output is not a string. Chains should output either a string or a JSON object."
+                )
+
+        return generated_text
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ get_generated_text(source, task) + +

+ + +
+ +

Returns generated text outputs for a given source and task.

+

Handles different output formats for Hugging Face and LangChain. For +Hugging Face, it extracts the last message content from chat-style +outputs and common keys like "generated_text", "summary_text", and +"translation". For LangChain, it converts JSON outputs to strings, and returns +the output as is if it is already a string.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ source + +
+

Framework name (e.g., "huggingface", "langchain").

+
+

+ + TYPE: + str + +

+
+ task + +
+

Task name for retrieving generated text.

+
+

+ + TYPE: + str + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + List[str] + + +
+

List[str]: List of generated text outputs, or an empty list if none.

+
+
+ +
+ Source code in healthchain/io/containers/document.py +
def get_generated_text(self, source: str, task: str) -> List[str]:
+    """
+    Returns generated text outputs for a given source and task.
+
+    Handles different output formats for Hugging Face and LangChain. For
+    Hugging Face, it extracts the last message content from chat-style
+    outputs and common keys like "generated_text", "summary_text", and
+    "translation". For LangChain, it converts JSON outputs to strings, and returns
+    the output as is if it is already a string.
+
+    Args:
+        source (str): Framework name (e.g., "huggingface", "langchain").
+        task (str): Task name for retrieving generated text.
+
+    Returns:
+        List[str]: List of generated text outputs, or an empty list if none.
+    """
+    generated_text = []
+
+    if source == "huggingface":
+        # Handle chat-style output format
+        output = self._huggingface_results.get(task)
+        if isinstance(output, list):
+            for entry in output:
+                text = entry.get("generated_text")
+                if isinstance(text, list):
+                    last_msg = text[-1]
+                    if isinstance(last_msg, dict) and "content" in last_msg:
+                        generated_text.append(last_msg["content"])
+                # Otherwise get common huggingface output keys
+                elif any(
+                    key in entry
+                    for key in ["generated_text", "summary_text", "translation"]
+                ):
+                    generated_text.append(
+                        text
+                        or entry.get("summary_text")
+                        or entry.get("translation")
+                    )
+        else:
+            logger.warning("HuggingFace output is not a list of dictionaries. ")
+    elif source == "langchain":
+        output = self._langchain_results.get(task)
+        # Check if output is a string
+        if isinstance(output, str):
+            generated_text.append(output)
+        # Try to convert JSON to string
+        elif isinstance(output, dict):
+            try:
+                import json
+
+                output_str = json.dumps(output)
+                generated_text.append(output_str)
+            except Exception:
+                logger.warning(
+                    "LangChain output is not a string and could not be converted to JSON string. "
+                    "Chains should output either a string or a JSON object."
+                )
+        else:
+            logger.warning(
+                "LangChain output is not a string. Chains should output either a string or a JSON object."
+            )
+
+    return generated_text
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ NlpAnnotations + + + + dataclass + + +

+ + +
+ + +

Container for NLP-specific annotations and results.

+

This class stores various NLP annotations and processing results from text analysis, +including preprocessed text, tokens, named entities, embeddings and spaCy documents.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
_preprocessed_text +
+

The preprocessed version of the input text.

+
+

+ + TYPE: + str + +

+
_tokens +
+

List of tokenized words from the text.

+
+

+ + TYPE: + List[str] + +

+
_entities +
+

Named entities extracted from the text, with their labels and positions.

+
+

+ + TYPE: + List[Dict[str, Any]] + +

+
_embeddings +
+

Vector embeddings generated from the text.

+
+

+ + TYPE: + Optional[List[float]] + +

+
_spacy_doc +
+

The processed spaCy Doc object.

+
+

+ + TYPE: + Optional[Doc] + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
add_spacy_doc +
+

SpacyDoc): Processes a spaCy Doc to extract tokens and entities.

+
+
get_spacy_doc +
+

Returns the stored spaCy Doc object.

+
+
get_tokens +
+

Returns the list of tokens.

+
+
set_tokens +
+

List[str]): Sets the token list.

+
+
set_entities +
+

List[Dict[str, Any]]): Sets the named entities list.

+
+
get_entities +
+

Returns the list of named entities.

+
+
get_embeddings +
+

Returns the vector embeddings.

+
+
set_embeddings +
+

List[float]): Sets the vector embeddings.

+
+
+ + + + + + +
+ Source code in healthchain/io/containers/document.py +
19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
@dataclass
+class NlpAnnotations:
+    """
+    Container for NLP-specific annotations and results.
+
+    This class stores various NLP annotations and processing results from text analysis,
+    including preprocessed text, tokens, named entities, embeddings and spaCy documents.
+
+    Attributes:
+        _preprocessed_text (str): The preprocessed version of the input text.
+        _tokens (List[str]): List of tokenized words from the text.
+        _entities (List[Dict[str, Any]]): Named entities extracted from the text, with their labels and positions.
+        _embeddings (Optional[List[float]]): Vector embeddings generated from the text.
+        _spacy_doc (Optional[SpacyDoc]): The processed spaCy Doc object.
+
+    Methods:
+        add_spacy_doc(doc: SpacyDoc): Processes a spaCy Doc to extract tokens and entities.
+        get_spacy_doc() -> Optional[SpacyDoc]: Returns the stored spaCy Doc object.
+        get_tokens() -> List[str]: Returns the list of tokens.
+        set_tokens(tokens: List[str]): Sets the token list.
+        set_entities(entities: List[Dict[str, Any]]): Sets the named entities list.
+        get_entities() -> List[Dict[str, Any]]: Returns the list of named entities.
+        get_embeddings() -> Optional[List[float]]: Returns the vector embeddings.
+        set_embeddings(embeddings: List[float]): Sets the vector embeddings.
+    """
+
+    _preprocessed_text: str = ""
+    _tokens: List[str] = field(default_factory=list)
+    _entities: List[Dict[str, Any]] = field(default_factory=list)
+    _embeddings: Optional[List[float]] = None
+    _spacy_doc: Optional[SpacyDoc] = None
+
+    def add_spacy_doc(self, doc: SpacyDoc):
+        self._spacy_doc = doc
+        self._tokens = [token.text for token in doc]
+        self._entities = [
+            {
+                "text": ent.text,
+                "label": ent.label_,
+                "start": ent.start_char,
+                "end": ent.end_char,
+            }
+            for ent in doc.ents
+        ]
+
+    def get_spacy_doc(self) -> Optional[SpacyDoc]:
+        return self._spacy_doc
+
+    def get_tokens(self) -> List[str]:
+        return self._tokens
+
+    def set_tokens(self, tokens: List[str]):
+        self._tokens = tokens
+
+    def set_entities(self, entities: List[Dict[str, Any]]):
+        self._entities = entities
+
+    def get_entities(self) -> List[Dict[str, Any]]:
+        return self._entities
+
+    def get_embeddings(self) -> Optional[List[float]]:
+        return self._embeddings
+
+    def set_embeddings(self, embeddings: List[float]):
+        self._embeddings = embeddings
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + +

+ tabular + + +

+ +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ Tabular + + + + dataclass + + +

+ + +
+

+ Bases: DataContainer[DataFrame]

+ + +

A container for tabular data, wrapping a pandas DataFrame.

+ + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
data +
+

The pandas DataFrame containing the tabular data.

+
+

+ + TYPE: + DataFrame + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
__post_init__ +
+

Validates that the data is a pandas DataFrame.

+
+
columns +
+

Property that returns a list of column names.

+
+
index +
+

Property that returns the DataFrame's index.

+
+
dtypes +
+

Property that returns a dictionary of column names and their data types.

+
+
column_count +
+

Returns the number of columns in the DataFrame.

+
+
row_count +
+

Returns the number of rows in the DataFrame.

+
+
get_dtype +
+

str): Returns the data type of a specific column.

+
+
__iter__ +
+

Returns an iterator over the column names.

+
+
__len__ +
+

Returns the number of rows in the DataFrame.

+
+
describe +
+

Returns a string description of the tabular data.

+
+
remove_column +
+

str): Removes a column from the DataFrame.

+
+
from_csv +
+

str, **kwargs): Class method to create a Tabular object from a CSV file.

+
+
from_dict +
+

Dict[str, Any]): Class method to create a Tabular object from a dictionary.

+
+
to_csv +
+

str, **kwargs): Saves the DataFrame to a CSV file.

+
+
+ + + + + + +
+ Source code in healthchain/io/containers/tabular.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
@dataclass
+class Tabular(DataContainer[pd.DataFrame]):
+    """
+    A container for tabular data, wrapping a pandas DataFrame.
+
+    Attributes:
+        data (pd.DataFrame): The pandas DataFrame containing the tabular data.
+
+    Methods:
+        __post_init__(): Validates that the data is a pandas DataFrame.
+        columns: Property that returns a list of column names.
+        index: Property that returns the DataFrame's index.
+        dtypes: Property that returns a dictionary of column names and their data types.
+        column_count(): Returns the number of columns in the DataFrame.
+        row_count(): Returns the number of rows in the DataFrame.
+        get_dtype(column: str): Returns the data type of a specific column.
+        __iter__(): Returns an iterator over the column names.
+        __len__(): Returns the number of rows in the DataFrame.
+        describe(): Returns a string description of the tabular data.
+        remove_column(name: str): Removes a column from the DataFrame.
+        from_csv(path: str, **kwargs): Class method to create a Tabular object from a CSV file.
+        from_dict(data: Dict[str, Any]): Class method to create a Tabular object from a dictionary.
+        to_csv(path: str, **kwargs): Saves the DataFrame to a CSV file.
+    """
+
+    def __post_init__(self):
+        if not isinstance(self.data, pd.DataFrame):
+            raise TypeError("data must be a pandas DataFrame")
+
+    @property
+    def columns(self) -> List[str]:
+        return list(self.data.columns)
+
+    @property
+    def index(self) -> pd.Index:
+        return self.data.index
+
+    @property
+    def dtypes(self) -> Dict[str, str]:
+        return {col: str(dtype) for col, dtype in self.data.dtypes.items()}
+
+    def column_count(self) -> int:
+        return len(self.columns)
+
+    def row_count(self) -> int:
+        return len(self.data)
+
+    def get_dtype(self, column: str) -> str:
+        return str(self.data[column].dtype)
+
+    def __iter__(self) -> Iterator[str]:
+        return iter(self.columns)
+
+    def __len__(self) -> int:
+        return self.row_count()
+
+    def describe(self) -> str:
+        return f"Tabular data with {self.column_count()} columns and {self.row_count()} rows"
+
+    def remove_column(self, name: str) -> None:
+        self.data.drop(columns=[name], inplace=True)
+
+    @classmethod
+    def from_csv(cls, path: str, **kwargs) -> "Tabular":
+        return cls(pd.read_csv(path, **kwargs))
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]) -> "Tabular":
+        df = pd.DataFrame(**data["data"])
+        return cls(df)
+
+    def to_csv(self, path: str, **kwargs) -> None:
+        self.data.to_csv(path, **kwargs)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/data_generators/index.html b/api/data_generators/index.html new file mode 100644 index 0000000..7b9eb03 --- /dev/null +++ b/api/data_generators/index.html @@ -0,0 +1,3958 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Data Generators - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Data Generators

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdsDataGenerator + + +

+ + +
+ + +

A class to generate CDS (Clinical Decision Support) data based on specified workflows and constraints.

+ + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
registry +
+

A registry of data generators.

+
+

+ + TYPE: + dict + +

+
mappings +
+

A mapping of workflows to their respective data generators.

+
+

+ + TYPE: + dict + +

+
data +
+

The generated CDS FHIR data.

+
+

+ + TYPE: + CdsFhirData + +

+
+ + + + + + +
+ Source code in healthchain/data_generators/cdsdatagenerator.py +
class CdsDataGenerator:
+    """
+    A class to generate CDS (Clinical Decision Support) data based on specified workflows and constraints.
+
+    Attributes:
+        registry (dict): A registry of data generators.
+        mappings (dict): A mapping of workflows to their respective data generators.
+        data (CdsFhirData): The generated CDS FHIR data.
+    """
+
+    # TODO: Add ordering and logic so that patient/encounter IDs are passed to subsequent generators
+    # TODO: Some of the resources should be allowed to be multiplied
+
+    default_workflow_mappings = {
+        Workflow.encounter_discharge: [
+            {"generator": "EncounterGenerator"},
+            {"generator": "ConditionGenerator"},
+            {"generator": "ProcedureGenerator"},
+            {"generator": "MedicationRequestGenerator"},
+        ],
+        Workflow.patient_view: [
+            {"generator": "PatientGenerator"},
+            {"generator": "EncounterGenerator"},
+            {"generator": "ConditionGenerator"},
+        ],
+    }
+
+    def __init__(self):
+        self.registry = generator_registry
+        self.mappings = self.default_workflow_mappings
+        self.data: CdsFhirData = None
+
+    def fetch_generator(self, generator_name: str) -> Callable:
+        """
+        Fetches a data generator function by its name from the registry.
+
+        Parameters:
+            generator_name (str): The name of the data generator to fetch.
+
+        Returns:
+            Callable: The data generator function.
+        """
+        return self.registry.get(generator_name)
+
+    def set_workflow(self, workflow: str) -> None:
+        """
+        Sets the current workflow to be used for data generation.
+
+        Parameters:
+            workflow (str): The name of the workflow to set.
+        """
+        self.workflow = workflow
+
+    def generate(
+        self,
+        constraints: Optional[list] = None,
+        free_text_path: Optional[str] = None,
+        column_name: Optional[str] = None,
+        random_seed: Optional[int] = None,
+    ) -> BaseModel:
+        """
+        Generates CDS data based on the current workflow, constraints, and optional free text data.
+
+        Parameters:
+            constraints (Optional[list]): A list of constraints to apply to the data generation.
+            free_text_path (Optional[str]): The path to a CSV file containing free text data.
+            column_name (Optional[str]): The column name in the CSV file to use for free text data.
+            random_seed (Optional[int]): The random seed to use for reproducible data generation.
+
+        Returns:
+            BaseModel: The generated CDS FHIR data.
+        """
+        results = []
+
+        if self.workflow not in self.mappings.keys():
+            raise ValueError(f"Workflow {self.workflow} not found in mappings")
+
+        for resource in self.mappings[self.workflow]:
+            generator_name = resource["generator"]
+            generator = self.fetch_generator(generator_name)
+            result = generator.generate(
+                constraints=constraints, random_seed=random_seed
+            )
+
+            results.append(BundleEntry(resource=result))
+
+        parsed_free_text = (
+            self.free_text_parser(free_text_path, column_name)
+            if free_text_path
+            else None
+        )
+        if parsed_free_text:
+            results.append(BundleEntry(resource=random.choice(parsed_free_text)))
+
+        output = CdsFhirData(prefetch=Bundle(resourceType="Bundle", entry=results))
+        self.data = output
+        return output
+
+    def free_text_parser(self, path_to_csv: str, column_name: str) -> Dict:
+        """
+        Parses free text data from a CSV file and converts it into a list of DocumentReference models.
+
+        Parameters:
+            path_to_csv (str): The path to the CSV file containing free text data.
+            column_name (str): The column name in the CSV file to use for free text data.
+
+        Returns:
+            dict: A dictionary of parsed free text data converted into DocumentReference models.
+        """
+        column_data = []
+
+        # Check that path_to_csv is a valid path with pathlib
+        path = Path(path_to_csv)
+        if not path.is_file():
+            raise FileNotFoundError(
+                f"The file {path_to_csv} does not exist or is not a file."
+            )
+
+        try:
+            with path.open(mode="r", newline="") as file:
+                reader = csv.DictReader(file)
+                if column_name is not None:
+                    for row in reader:
+                        column_data.append(row[column_name])
+                else:
+                    raise ValueError(
+                        "Column name must be provided when header is True."
+                    )
+        except Exception as ex:
+            logger.error(f"An error occurred: {ex}")
+
+        document_list = []
+
+        for x in column_data:
+            # First parse x in to documentreferencemodel format
+            text = Narrative(
+                status="generated",
+                div=f'<div xmlns="http://www.w3.org/1999/xhtml">{x}</div>',
+            )
+            doc = DocumentReference(resourceType="DocumentReference", text=text)
+            document_list.append(doc)
+
+        return document_list
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ fetch_generator(generator_name) + +

+ + +
+ +

Fetches a data generator function by its name from the registry.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ generator_name + +
+

The name of the data generator to fetch.

+
+

+ + TYPE: + str + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ Callable + +
+

The data generator function.

+
+

+ + TYPE: + Callable + +

+
+ +
+ Source code in healthchain/data_generators/cdsdatagenerator.py +
51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
def fetch_generator(self, generator_name: str) -> Callable:
+    """
+    Fetches a data generator function by its name from the registry.
+
+    Parameters:
+        generator_name (str): The name of the data generator to fetch.
+
+    Returns:
+        Callable: The data generator function.
+    """
+    return self.registry.get(generator_name)
+
+
+
+ +
+ +
+ + +

+ free_text_parser(path_to_csv, column_name) + +

+ + +
+ +

Parses free text data from a CSV file and converts it into a list of DocumentReference models.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ path_to_csv + +
+

The path to the CSV file containing free text data.

+
+

+ + TYPE: + str + +

+
+ column_name + +
+

The column name in the CSV file to use for free text data.

+
+

+ + TYPE: + str + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ dict + +
+

A dictionary of parsed free text data converted into DocumentReference models.

+
+

+ + TYPE: + Dict + +

+
+ +
+ Source code in healthchain/data_generators/cdsdatagenerator.py +
def free_text_parser(self, path_to_csv: str, column_name: str) -> Dict:
+    """
+    Parses free text data from a CSV file and converts it into a list of DocumentReference models.
+
+    Parameters:
+        path_to_csv (str): The path to the CSV file containing free text data.
+        column_name (str): The column name in the CSV file to use for free text data.
+
+    Returns:
+        dict: A dictionary of parsed free text data converted into DocumentReference models.
+    """
+    column_data = []
+
+    # Check that path_to_csv is a valid path with pathlib
+    path = Path(path_to_csv)
+    if not path.is_file():
+        raise FileNotFoundError(
+            f"The file {path_to_csv} does not exist or is not a file."
+        )
+
+    try:
+        with path.open(mode="r", newline="") as file:
+            reader = csv.DictReader(file)
+            if column_name is not None:
+                for row in reader:
+                    column_data.append(row[column_name])
+            else:
+                raise ValueError(
+                    "Column name must be provided when header is True."
+                )
+    except Exception as ex:
+        logger.error(f"An error occurred: {ex}")
+
+    document_list = []
+
+    for x in column_data:
+        # First parse x in to documentreferencemodel format
+        text = Narrative(
+            status="generated",
+            div=f'<div xmlns="http://www.w3.org/1999/xhtml">{x}</div>',
+        )
+        doc = DocumentReference(resourceType="DocumentReference", text=text)
+        document_list.append(doc)
+
+    return document_list
+
+
+
+ +
+ +
+ + +

+ generate(constraints=None, free_text_path=None, column_name=None, random_seed=None) + +

+ + +
+ +

Generates CDS data based on the current workflow, constraints, and optional free text data.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ constraints + +
+

A list of constraints to apply to the data generation.

+
+

+ + TYPE: + Optional[list] + + + DEFAULT: + None + +

+
+ free_text_path + +
+

The path to a CSV file containing free text data.

+
+

+ + TYPE: + Optional[str] + + + DEFAULT: + None + +

+
+ column_name + +
+

The column name in the CSV file to use for free text data.

+
+

+ + TYPE: + Optional[str] + + + DEFAULT: + None + +

+
+ random_seed + +
+

The random seed to use for reproducible data generation.

+
+

+ + TYPE: + Optional[int] + + + DEFAULT: + None + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ BaseModel + +
+

The generated CDS FHIR data.

+
+

+ + TYPE: + BaseModel + +

+
+ +
+ Source code in healthchain/data_generators/cdsdatagenerator.py +
def generate(
+    self,
+    constraints: Optional[list] = None,
+    free_text_path: Optional[str] = None,
+    column_name: Optional[str] = None,
+    random_seed: Optional[int] = None,
+) -> BaseModel:
+    """
+    Generates CDS data based on the current workflow, constraints, and optional free text data.
+
+    Parameters:
+        constraints (Optional[list]): A list of constraints to apply to the data generation.
+        free_text_path (Optional[str]): The path to a CSV file containing free text data.
+        column_name (Optional[str]): The column name in the CSV file to use for free text data.
+        random_seed (Optional[int]): The random seed to use for reproducible data generation.
+
+    Returns:
+        BaseModel: The generated CDS FHIR data.
+    """
+    results = []
+
+    if self.workflow not in self.mappings.keys():
+        raise ValueError(f"Workflow {self.workflow} not found in mappings")
+
+    for resource in self.mappings[self.workflow]:
+        generator_name = resource["generator"]
+        generator = self.fetch_generator(generator_name)
+        result = generator.generate(
+            constraints=constraints, random_seed=random_seed
+        )
+
+        results.append(BundleEntry(resource=result))
+
+    parsed_free_text = (
+        self.free_text_parser(free_text_path, column_name)
+        if free_text_path
+        else None
+    )
+    if parsed_free_text:
+        results.append(BundleEntry(resource=random.choice(parsed_free_text)))
+
+    output = CdsFhirData(prefetch=Bundle(resourceType="Bundle", entry=results))
+    self.data = output
+    return output
+
+
+
+ +
+ +
+ + +

+ set_workflow(workflow) + +

+ + +
+ +

Sets the current workflow to be used for data generation.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ workflow + +
+

The name of the workflow to set.

+
+

+ + TYPE: + str + +

+
+ +
+ Source code in healthchain/data_generators/cdsdatagenerator.py +
63
+64
+65
+66
+67
+68
+69
+70
def set_workflow(self, workflow: str) -> None:
+    """
+    Sets the current workflow to be used for data generation.
+
+    Parameters:
+        workflow (str): The name of the workflow to set.
+    """
+    self.workflow = workflow
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ ClassGenerator + + +

+ + +
+

+ Bases: BaseGenerator

+ + +

A generator class for creating FHIR Class resources.

+ + + + + + + + + + + + + + + +
METHODDESCRIPTION
generate +
+

Generates a FHIR Class resource.

+
+
+ + + + + + +
+ Source code in healthchain/data_generators/encountergenerators.py +
46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
@register_generator
+class ClassGenerator(BaseGenerator):
+    """
+    A generator class for creating FHIR Class resources.
+
+    Methods:
+        generate() -> CodeableConcept:
+            Generates a FHIR Class resource.
+    """
+
+    @staticmethod
+    def generate() -> CodeableConcept:
+        patient_class_mapping = {"IMP": "inpatient", "AMB": "ambulatory"}
+        patient_class = faker.random_element(elements=("IMP", "AMB"))
+        return CodeableConcept(
+            coding=[
+                Coding(
+                    system="http://terminology.hl7.org/CodeSystem/v3-ActCode",
+                    code=patient_class,
+                    display=patient_class_mapping.get(patient_class),
+                )
+            ]
+        )
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ EncounterGenerator + + +

+ + +
+

+ Bases: BaseGenerator

+ + +

A generator class for creating FHIR Encounter resources.

+ + + + + + + + + + + + + + + +
METHODDESCRIPTION
generate +
+

Optional[list] = None, random_seed: Optional[int] = None) -> Encounter: +Generates a FHIR Encounter resource with optional constraints and random_seed.

+
+
+ + + + + + +
+ Source code in healthchain/data_generators/encountergenerators.py +
@register_generator
+class EncounterGenerator(BaseGenerator):
+    """
+    A generator class for creating FHIR Encounter resources.
+
+    Methods:
+        generate(constraints: Optional[list] = None, random_seed: Optional[int] = None) -> Encounter:
+            Generates a FHIR Encounter resource with optional constraints and random_seed.
+    """
+
+    @staticmethod
+    def generate(
+        constraints: Optional[list] = None,
+        random_seed: Optional[int] = None,
+    ) -> Encounter:
+        Faker.seed(random_seed)
+        patient_reference = "Patient/123"
+        return Encounter(
+            resourceType="Encounter",
+            id=generator_registry.get("IdGenerator").generate(),
+            status=faker.random_element(
+                elements=(
+                    "planned",
+                    "in-progress",
+                    "on-hold",
+                    "discharged",
+                    "cancelled",
+                )
+            ),
+            class_field=[generator_registry.get("ClassGenerator").generate()],
+            priority=generator_registry.get("EncounterPriorityGenerator").generate(),
+            type_field=[generator_registry.get("EncounterTypeGenerator").generate()],
+            subject={"reference": patient_reference, "display": patient_reference},
+            actualPeriod=generator_registry.get("PeriodGenerator").generate(),
+            location=[generator_registry.get("EncounterLocationGenerator").generate()],
+        )
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ EncounterLocationGenerator + + +

+ + +
+

+ Bases: BaseGenerator

+ + +

A generator class for creating FHIR EncounterLocation resources.

+ + + + + + + + + + + + + + + +
METHODDESCRIPTION
generate +
+

Generates a FHIR EncounterLocation resource.

+
+
+ + + + + + +
+ Source code in healthchain/data_generators/encountergenerators.py +
@register_generator
+class EncounterLocationGenerator(BaseGenerator):
+    """
+    A generator class for creating FHIR EncounterLocation resources.
+
+    Methods:
+        generate() -> EncounterLocation:
+            Generates a FHIR EncounterLocation resource.
+    """
+
+    @staticmethod
+    def generate() -> EncounterLocation:
+        return EncounterLocation(
+            location=Reference(reference="Location/123"),
+            status=faker.random_element(elements=("active", "completed")),
+            period=generator_registry.get("PeriodGenerator").generate(),
+        )
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ EncounterPriorityGenerator + + +

+ + +
+

+ Bases: BaseGenerator

+ + +

A generator class for creating FHIR EncounterPriority resources.

+ + + + + + + + + + + + + + + +
METHODDESCRIPTION
generate +
+

Generates a FHIR EncounterPriority resource.

+
+
+ + + + + + +
+ Source code in healthchain/data_generators/encountergenerators.py +
@register_generator
+class EncounterPriorityGenerator(BaseGenerator):
+    """
+    A generator class for creating FHIR EncounterPriority resources.
+
+    Methods:
+        generate() -> CodeableConcept:
+            Generates a FHIR EncounterPriority resource.
+    """
+
+    @staticmethod
+    def generate() -> CodeableConcept:
+        encounter_priority_mapping = {"17621005": "normal", "24484000": "critical"}
+        encounter_priority = faker.random_element(elements=("17621005", "24484000"))
+        return CodeableConcept(
+            coding=[
+                Coding(
+                    system="http://snomed.info/sct",
+                    code=encounter_priority,
+                    display=encounter_priority_mapping.get(encounter_priority),
+                )
+            ]
+        )
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ EncounterTypeGenerator + + +

+ + +
+

+ Bases: BaseGenerator

+ + +

A generator class for creating FHIR EncounterType resources.

+ + + + + + + + + + + + + + + +
METHODDESCRIPTION
generate +
+

Generates a FHIR EncounterType resource.

+
+
+ + + + + + +
+ Source code in healthchain/data_generators/encountergenerators.py +
71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
@register_generator
+class EncounterTypeGenerator(BaseGenerator):
+    """
+    A generator class for creating FHIR EncounterType resources.
+
+    Methods:
+        generate() -> CodeableConcept:
+            Generates a FHIR EncounterType resource.
+    """
+
+    @staticmethod
+    def generate() -> CodeableConcept:
+        encounter_type_mapping = {"11429006": "consultation", "50849002": "emergency"}
+        encounter_type = faker.random_element(elements=("11429006", "50849002"))
+        return CodeableConcept(
+            coding=[
+                Coding(
+                    system="http://snomed.info/sct",
+                    code=encounter_type,
+                    display=encounter_type_mapping.get(encounter_type),
+                )
+            ]
+        )
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ PeriodGenerator + + +

+ + +
+

+ Bases: BaseGenerator

+ + +

A generator class for creating FHIR Period resources.

+ + + + + + + + + + + + + + + +
METHODDESCRIPTION
generate +
+

Generates a FHIR Period resource with random start and end times.

+
+
+ + + + + + +
+ Source code in healthchain/data_generators/encountergenerators.py +
25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
@register_generator
+class PeriodGenerator(BaseGenerator):
+    """
+    A generator class for creating FHIR Period resources.
+
+    Methods:
+        generate() -> Period:
+            Generates a FHIR Period resource with random start and end times.
+    """
+
+    @staticmethod
+    def generate():
+        start = faker.date_time()
+        end = faker.date_time_between(start_date=start).isoformat()
+        start = start.isoformat()
+        return Period(
+            start=dateTimeModel(start),
+            end=dateTimeModel(end),
+        )
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/data_models/index.html b/api/data_models/index.html new file mode 100644 index 0000000..62f41c7 --- /dev/null +++ b/api/data_models/index.html @@ -0,0 +1,3071 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Data Models - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Data Models

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CcdData + + +

+ + +
+

+ Bases: BaseModel

+ + +

Data model for CCD (Continuity of Care Document) that can be converted to CDA.

+ + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
concepts +
+

Container for medical concepts (problems, allergies, medications) +extracted from the document. Defaults to empty ConceptLists.

+
+

+ + TYPE: + ConceptLists + +

+
note +
+

The clinical note text, either as a string or +dictionary of sections. Defaults to None.

+
+

+ + TYPE: + Optional[Union[Dict, str]] + +

+
cda_xml +
+

The raw CDA XML string representation of the document. +Defaults to None.

+
+

+ + TYPE: + Optional[str] + +

+
+ + + + + + +
+ Source code in healthchain/models/data/ccddata.py +
 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
class CcdData(BaseModel):
+    """
+    Data model for CCD (Continuity of Care Document) that can be converted to CDA.
+
+    Attributes:
+        concepts (ConceptLists): Container for medical concepts (problems, allergies, medications)
+            extracted from the document. Defaults to empty ConceptLists.
+        note (Optional[Union[Dict, str]]): The clinical note text, either as a string or
+            dictionary of sections. Defaults to None.
+        cda_xml (Optional[str]): The raw CDA XML string representation of the document.
+            Defaults to None.
+    """
+
+    concepts: ConceptLists = ConceptLists()
+    note: Optional[Union[Dict, str]] = None
+    cda_xml: Optional[str] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdsFhirData + + +

+ + +
+

+ Bases: BaseModel

+ + +

Data model for CDS FHIR data, matching the expected fields in CDSRequests.

+ + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
context +
+

A dictionary containing contextual information for the CDS request.

+
+

+ + TYPE: + Dict + +

+
prefetch +
+

A Bundle object containing prefetched FHIR resources.

+
+

+ + TYPE: + Bundle + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
METHODDESCRIPTION
create +
+

Dict, prefetch: Dict): Class method to create a CdsFhirData instance.

+
+
model_dump +
+

Returns a dictionary representation of the model.

+
+
model_dump_json +
+

Returns a JSON string representation of the model.

+
+
model_dump_prefetch +
+

Returns a dictionary representation of the prefetch Bundle.

+
+
+ + + + + + +
+ Source code in healthchain/models/data/cdsfhirdata.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
class CdsFhirData(BaseModel):
+    """
+    Data model for CDS FHIR data, matching the expected fields in CDSRequests.
+
+    Attributes:
+        context (Dict): A dictionary containing contextual information for the CDS request.
+        prefetch (Bundle): A Bundle object containing prefetched FHIR resources.
+
+    Methods:
+        create(cls, context: Dict, prefetch: Dict): Class method to create a CdsFhirData instance.
+        model_dump(*args, **kwargs): Returns a dictionary representation of the model.
+        model_dump_json(*args, **kwargs): Returns a JSON string representation of the model.
+        model_dump_prefetch(*args, **kwargs): Returns a dictionary representation of the prefetch Bundle.
+    """
+
+    context: Dict = Field(default={})
+    prefetch: Bundle
+
+    @classmethod
+    def create(cls, context: Dict, prefetch: Dict):
+        # deep copy to avoid modifying the original prefetch data
+        prefetch_copy = copy.deepcopy(prefetch)
+        bundle = Bundle(**prefetch_copy)
+        return cls(context=context, prefetch=bundle)
+
+    def model_dump(self, *args, **kwargs):
+        kwargs.setdefault("exclude_unset", True)
+        kwargs.setdefault("exclude_defaults", False)
+        kwargs.setdefault("exclude_none", True)
+        kwargs.setdefault("by_alias", True)
+
+        return super().model_dump(*args, **kwargs)
+
+    def model_dump_json(self, *args, **kwargs):
+        kwargs.setdefault("exclude_unset", True)
+        kwargs.setdefault("exclude_defaults", False)
+        kwargs.setdefault("exclude_none", True)
+        kwargs.setdefault("by_alias", True)
+
+        return super().model_dump_json(*args, **kwargs)
+
+    def model_dump_prefetch(self, *args, **kwargs):
+        kwargs.setdefault("exclude_unset", True)
+        kwargs.setdefault("exclude_defaults", False)
+        kwargs.setdefault("exclude_none", True)
+        kwargs.setdefault("by_alias", True)
+
+        return self.prefetch.model_dump(*args, **kwargs)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ AllergyConcept + + +

+ + +
+

+ Bases: Concept

+ + +

Contains allergy specific fields

+

Defaults allergy type to propensity to adverse reactions in SNOMED CT

+ + + + + + +
+ Source code in healthchain/models/data/concept.py +
class AllergyConcept(Concept):
+    """
+    Contains allergy specific fields
+
+    Defaults allergy type to propensity to adverse reactions in SNOMED CT
+    """
+
+    allergy_type: Optional[Concept] = Field(
+        default=Concept(
+            code="420134006",
+            code_system="2.16.840.1.113883.6.96",
+            code_system_name="SNOMED CT",
+            display_name="Propensity to adverse reactions",
+        )
+    )
+    severity: Optional[Concept] = None
+    reaction: Optional[Concept] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ Concept + + +

+ + +
+

+ Bases: BaseModel

+ + +

A more lenient, system agnostic representation of a concept e.g. problems, medications, allergies +that can be converted to CDA or FHIR

+ + + + + + +
+ Source code in healthchain/models/data/concept.py +
58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
class Concept(BaseModel):
+    """
+    A more lenient, system agnostic representation of a concept e.g. problems, medications, allergies
+    that can be converted to CDA or FHIR
+    """
+
+    _standard: Optional[Standard] = None
+    code: Optional[str] = None
+    code_system: Optional[str] = None
+    code_system_name: Optional[str] = None
+    display_name: Optional[str] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ ConceptLists + + +

+ + +
+

+ Bases: BaseModel

+ + +

Container for lists of medical concepts extracted from text.

+ + + + + + +
+ Source code in healthchain/models/data/concept.py +
class ConceptLists(BaseModel):
+    """Container for lists of medical concepts extracted from text."""
+
+    problems: List[ProblemConcept] = []
+    allergies: List[AllergyConcept] = []
+    medications: List[MedicationConcept] = []
+
+    class Config:
+        arbitrary_types_allowed = True
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ DataType + + +

+ + +
+

+ Bases: BaseModel

+ + +

Base class for all data types

+ + + + + + +
+ Source code in healthchain/models/data/concept.py +
11
+12
+13
+14
+15
+16
class DataType(BaseModel):
+    """
+    Base class for all data types
+    """
+
+    _source: Optional[Dict] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ MedicationConcept + + +

+ + +
+

+ Bases: Concept

+ + +

Contains medication specific fields

+ + + + + + +
+ Source code in healthchain/models/data/concept.py +
82
+83
+84
+85
+86
+87
+88
+89
+90
+91
class MedicationConcept(Concept):
+    """
+    Contains medication specific fields
+    """
+
+    dosage: Optional[Quantity] = None
+    route: Optional[Concept] = None
+    frequency: Optional[TimeInterval] = None
+    duration: Optional[Range] = None
+    precondition: Optional[Dict] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ ProblemConcept + + +

+ + +
+

+ Bases: Concept

+ + +

Contains problem/condition specific fields

+ + + + + + +
+ Source code in healthchain/models/data/concept.py +
71
+72
+73
+74
+75
+76
+77
+78
+79
class ProblemConcept(Concept):
+    """
+    Contains problem/condition specific fields
+    """
+
+    onset_date: Optional[str] = None
+    abatement_date: Optional[str] = None
+    status: Optional[str] = None
+    recorded_date: Optional[str] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/index.html b/api/index.html new file mode 100644 index 0000000..06dc18c --- /dev/null +++ b/api/index.html @@ -0,0 +1,1967 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + API Reference - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

API Reference

+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/pipeline/index.html b/api/pipeline/index.html new file mode 100644 index 0000000..8d8f441 --- /dev/null +++ b/api/pipeline/index.html @@ -0,0 +1,7034 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Pipeline - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Pipeline

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ BasePipeline + + +

+ + +
+

+ Bases: Generic[T], ABC

+ + +

Abstract base class for creating and managing data processing pipelines.

+

The BasePipeline class provides a framework for building modular data processing pipelines +by allowing users to add, remove, and configure components with defined dependencies and +execution order. Components can be added at specific positions, grouped into stages, and +connected via input/output connectors.

+

This is an abstract base class that should be subclassed to create specific pipeline +implementations.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
_components +
+

Ordered list of pipeline components

+
+

+ + TYPE: + List[PipelineNode[T]] + +

+
_stages +
+

Components grouped by processing stage

+
+

+ + TYPE: + Dict[str, List[Callable]] + +

+
_built_pipeline +
+

Compiled pipeline function

+
+

+ + TYPE: + Optional[Callable] + +

+
_input_connector +
+

Connector for processing input data

+
+

+ + TYPE: + Optional[BaseConnector[T]] + +

+
_output_connector +
+

Connector for processing output data

+
+

+ + TYPE: + Optional[BaseConnector[T]] + +

+
_output_template +
+

Template string for formatting pipeline outputs

+
+

+ + TYPE: + Optional[str] + +

+
_model_config +
+

Configuration for the pipeline model

+
+

+ + TYPE: + Optional[ModelConfig] + +

+
+ + +
+ Example +
+
+
+

class MyPipeline(BasePipeline[str]): +... def configure_pipeline(self, config: ModelConfig) -> None: +... self.add_node(preprocess, stage="preprocessing") +... self.add_node(process, stage="processing") +... self.add_node(postprocess, stage="postprocessing") +... +pipeline = MyPipeline() +result = pipeline("input text")

+
+
+
+
+ + + + + +
+ Source code in healthchain/pipeline/base.py +
 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
class BasePipeline(Generic[T], ABC):
+    """
+    Abstract base class for creating and managing data processing pipelines.
+
+    The BasePipeline class provides a framework for building modular data processing pipelines
+    by allowing users to add, remove, and configure components with defined dependencies and
+    execution order. Components can be added at specific positions, grouped into stages, and
+    connected via input/output connectors.
+
+    This is an abstract base class that should be subclassed to create specific pipeline
+    implementations.
+
+    Attributes:
+        _components (List[PipelineNode[T]]): Ordered list of pipeline components
+        _stages (Dict[str, List[Callable]]): Components grouped by processing stage
+        _built_pipeline (Optional[Callable]): Compiled pipeline function
+        _input_connector (Optional[BaseConnector[T]]): Connector for processing input data
+        _output_connector (Optional[BaseConnector[T]]): Connector for processing output data
+        _output_template (Optional[str]): Template string for formatting pipeline outputs
+        _model_config (Optional[ModelConfig]): Configuration for the pipeline model
+
+    Example:
+        >>> class MyPipeline(BasePipeline[str]):
+        ...     def configure_pipeline(self, config: ModelConfig) -> None:
+        ...         self.add_node(preprocess, stage="preprocessing")
+        ...         self.add_node(process, stage="processing")
+        ...         self.add_node(postprocess, stage="postprocessing")
+        ...
+        >>> pipeline = MyPipeline()
+        >>> result = pipeline("input text")
+    """
+
+    def __init__(self):
+        self._components: List[PipelineNode[T]] = []
+        self._stages: Dict[str, List[Callable]] = {}
+        self._built_pipeline: Optional[Callable] = None
+        self._input_connector: Optional[BaseConnector[T]] = None
+        self._output_connector: Optional[BaseConnector[T]] = None
+        self._output_template: Optional[str] = None
+        self._output_template_path: Optional[Path] = None
+
+    def __repr__(self) -> str:
+        components_repr = ", ".join(
+            [f'"{component.name}"' for component in self._components]
+        )
+        return f"[{components_repr}]"
+
+    def _configure_output_templates(
+        self,
+        template: Optional[str] = None,
+        template_path: Optional[Union[str, Path]] = None,
+    ) -> None:
+        """
+        Configure template settings for the pipeline.
+
+        Args:
+            template (Optional[str]): Template string for formatting outputs.
+                Defaults to None.
+            template_path (Optional[Union[str, Path]]): Path to template file.
+                Defaults to None.
+        """
+        self._output_template = template
+        self._output_template_path = Path(template_path) if template_path else None
+
+    @classmethod
+    def load(
+        cls,
+        pipeline: Callable,
+        source: str,
+        task: Optional[str] = "text-generation",
+        template: Optional[str] = None,
+        template_path: Optional[Union[str, Path]] = None,
+        **kwargs: Any,
+    ) -> "BasePipeline":
+        """
+        Load a pipeline from a pre-built pipeline object (e.g. LangChain chain or HuggingFace pipeline).
+
+        Args:
+            pipeline (Callable): A callable pipeline object (e.g. LangChain chain, HuggingFace pipeline)
+            source (str): Source of the pipeline. Can be "langchain" or "huggingface".
+            task (Optional[str]): Task identifier used to retrieve model outputs.
+                Defaults to "text-generation".
+            template (Optional[str]): Template string for formatting outputs.
+                Defaults to None.
+            template_path (Optional[Union[str, Path]]): Path to template file.
+                Defaults to None.
+            **kwargs: Additional configuration options passed to the pipeline.
+
+        Returns:
+            BasePipeline: Configured pipeline instance.
+
+        Raises:
+            ValueError: If pipeline is not callable or source is invalid.
+
+        Examples:
+            >>> # Load LangChain pipeline
+            >>> from langchain_core.prompts import ChatPromptTemplate
+            >>> from langchain_openai import ChatOpenAI
+            >>> chain = ChatPromptTemplate.from_template("What is {input}?") | ChatOpenAI()
+            >>> pipeline = Pipeline.load(chain, source="langchain", temperature=0.7)
+            >>>
+            >>> # Load HuggingFace pipeline
+            >>> from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
+            >>> tokenizer = AutoTokenizer.from_pretrained("gpt2")
+            >>> model = AutoModelForCausalLM.from_pretrained("gpt2")
+            >>> pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=10)
+            >>> pipeline = Pipeline.load(pipe, source="huggingface")
+        """
+        if not (hasattr(pipeline, "__call__") or hasattr(pipeline, "invoke")):
+            raise ValueError("Pipeline must be a callable object")
+
+        # Validate source
+        source = source.lower()
+        if source not in ["langchain", "huggingface"]:
+            raise ValueError(
+                "Source must be either 'langchain' or 'huggingface' for direct pipeline loading"
+            )
+
+        # For HuggingFace pipelines, try to infer task if not provided
+        if source == "huggingface" and hasattr(pipeline, "task") and not task:
+            task = pipeline.task
+
+        instance = cls()
+        instance._configure_output_templates(template, template_path)
+
+        config = ModelConfig(
+            source=ModelSource(source),
+            pipeline_object=pipeline,
+            task=task,
+            kwargs=kwargs,
+        )
+
+        instance._model_config = config
+        instance.configure_pipeline(config)
+
+        return instance
+
+    @classmethod
+    def from_model_id(
+        cls,
+        model_id: str,
+        source: Union[str, ModelSource] = "huggingface",
+        task: Optional[str] = "text-generation",
+        template: Optional[str] = None,
+        template_path: Optional[Union[str, Path]] = None,
+        **kwargs: Any,
+    ) -> "BasePipeline":
+        """
+        Load pipeline from a model identifier.
+
+        Args:
+            model_id (str): Model identifier (e.g. HuggingFace model ID, SpaCy model name)
+            source (Union[str, ModelSource]): Model source. Defaults to "huggingface".
+                Can be "huggingface", "spacy".
+            task (Optional[str]): Task identifier for the model. Defaults to "text-generation".
+            template (Optional[str]): Optional template string for formatting model output.
+            template_path (Optional[Union[str, Path]]): Optional path to template file for formatting model output.
+            **kwargs: Additional configuration options passed to the model. e.g. temperature, max_length, etc.
+
+        Returns:
+            BasePipeline: Configured pipeline instance.
+
+        Raises:
+            ValueError: If source is not a valid ModelSource.
+
+        Examples:
+            >>> # Load HuggingFace model
+            >>> pipeline = Pipeline.from_model_id(
+            ...     "facebook/bart-large-cnn",
+            ...     task="summarization",
+            ...     temperature=0.7
+            ... )
+            >>>
+            >>> # Load SpaCy model
+            >>> pipeline = Pipeline.from_model_id(
+            ...     "en_core_sci_md",
+            ...     source="spacy",
+            ...     disable=["parser"]
+            ... )
+            >>>
+            >>> # Load with output template
+            >>> template = '''{"summary": "{{ model_output }}"}'''
+            >>> pipeline = Pipeline.from_model_id(
+            ...     "gpt-3.5-turbo",
+            ...     source="huggingface",
+            ...     template=template
+            ... )
+        """
+        pipeline = cls()
+        pipeline._configure_output_templates(template, template_path)
+
+        config = ModelConfig(
+            source=ModelSource(source.lower()),
+            model_id=model_id,
+            task=task,
+            kwargs=kwargs,
+        )
+        pipeline._model_config = config
+        pipeline.configure_pipeline(config)
+
+        return pipeline
+
+    @classmethod
+    def from_local_model(
+        cls,
+        path: Union[str, Path],
+        source: Union[str, ModelSource],
+        task: Optional[str] = None,
+        template: Optional[str] = None,
+        template_path: Optional[Union[str, Path]] = None,
+        **kwargs: Any,
+    ) -> "BasePipeline":
+        """Load pipeline from a local model path.
+
+        Args:
+            path (Union[str, Path]): Path to local model files/directory
+            source (Union[str, ModelSource]): Model source (e.g. "huggingface", "spacy")
+            task (Optional[str]): Task identifier for the model. Defaults to None.
+            template (Optional[str]): Optional template string for formatting model output.
+            template_path (Optional[Union[str, Path]]): Optional path to template file for formatting model output.
+            **kwargs: Additional configuration options passed to the model. e.g. temperature, max_length, etc.
+
+        Returns:
+            BasePipeline: Configured pipeline instance.
+
+        Raises:
+            ValueError: If source is not a valid ModelSource.
+
+        Examples:
+            >>> # Load local HuggingFace model
+            >>> pipeline = Pipeline.from_local_model(
+            ...     "models/my_summarizer",
+            ...     source="huggingface",
+            ...     task="summarization",
+            ...     temperature=0.7
+            ... )
+            >>>
+            >>> # Load local SpaCy model
+            >>> pipeline = Pipeline.from_local_model(
+            ...     "models/en_core_sci_md",
+            ...     source="spacy",
+            ...     disable=["parser"]
+            ... )
+            >>>
+            >>> # Load with output template
+            >>> template = '''{"summary": "{{ model_output }}"}'''
+            >>> pipeline = Pipeline.from_local_model(
+            ...     "models/gpt_model",
+            ...     source="huggingface",
+            ...     template=template
+            ... )
+        """
+        pipeline = cls()
+        pipeline._configure_output_templates(template, template_path)
+
+        path = Path(path)
+        config = ModelConfig(
+            source=ModelSource(source.lower()),
+            model_id=path.name,
+            path=path,
+            task=task,
+            kwargs=kwargs,
+        )
+        pipeline._model_config = config
+        pipeline.configure_pipeline(config)
+
+        return pipeline
+
+    @abstractmethod
+    def configure_pipeline(self, model_config: ModelConfig) -> None:
+        """
+        Configure the pipeline based on the provided model configuration.
+
+        This method should be implemented by subclasses to add specific components
+        and configure the pipeline according to the given model configuration.
+        The configuration typically involves:
+        1. Setting up input/output connectors
+        2. Adding model components based on the model source
+        3. Adding any additional processing nodes
+        4. Configuring the pipeline stages and execution order
+
+        Args:
+            model_config (ModelConfig): Configuration object containing:
+                - source: Model source (e.g. huggingface, spacy, langchain)
+                - model: Model identifier or path
+                - task: Optional task name (e.g. summarization, ner)
+                - path: Optional local path to model files
+                - kwargs: Additional model configuration parameters
+
+        Returns:
+            None
+
+        Raises:
+            NotImplementedError: If the method is not implemented by a subclass.
+
+        Example:
+            >>> def configure_pipeline(self, config: ModelConfig):
+            ...     # Add FHIR connector for input/output
+            ...     connector = FhirConnector()
+            ...     self.add_input(connector)
+            ...
+            ...     # Add model component
+            ...     model = self.get_model_component(config)
+            ...     self.add_node(model, stage="processing")
+            ...
+            ...     # Add output formatting
+            ...     self.add_node(OutputFormatter(), stage="formatting")
+            ...     self.add_output(connector)
+        """
+        raise NotImplementedError("This method must be implemented by subclasses.")
+
+    @property
+    def stages(self):
+        """
+        Returns a human-readable representation of the pipeline stages.
+        """
+        output = ["Pipeline Stages:"]
+        for stage, components in self._stages.items():
+            output.append(f"  {stage}:")
+            for component in components:
+                component_name = (
+                    component.__name__
+                    if hasattr(component, "__name__")
+                    else (
+                        component.__class__.__name__
+                        if hasattr(component, "__class__")
+                        else str(component)
+                    )
+                )
+                output.append(f"    - {component_name}")
+        if not self._stages:
+            output.append("  No stages defined.")
+        return "\n".join(output)
+
+    @stages.setter
+    def stages(self, new_stages: Dict[str, List[Callable]]):
+        """
+        Sets the stages of the pipeline.
+
+        Args:
+            new_stages (Dict[str, List[Callable]]): A dictionary where keys are stage names
+                                                    and values are lists of callable components.
+        """
+        self._stages = new_stages
+
+    def add_input(self, connector: BaseConnector[T]) -> None:
+        """
+        Adds an input connector to the pipeline.
+
+        This method sets the input connector for the pipeline, which is responsible
+        for processing the input data before it's passed to the pipeline components.
+
+        Args:
+            connector (Connector[T]): The input connector to be added to the pipeline.
+
+        Returns:
+            None
+
+        Note:
+            Only one input connector can be set for the pipeline. If this method is
+            called multiple times, the last connector will overwrite the previous ones.
+        """
+        self._input_connector = connector
+
+    def add_output(self, connector: BaseConnector[T]) -> None:
+        """
+        Adds an output connector to the pipeline.
+
+        This method sets the output connector for the pipeline, which is responsible
+        for processing the output data after it has passed through all pipeline components.
+
+        Args:
+            connector (Connector[T]): The output connector to be added to the pipeline.
+
+        Returns:
+            None
+
+        Note:
+            Only one output connector can be set for the pipeline. If this method is
+            called multiple times, the last connector will overwrite the previous ones.
+        """
+        self._output_connector = connector
+
+    def add_node(
+        self,
+        component: Union[
+            BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]
+        ] = None,
+        *,
+        position: PositionType = "default",
+        reference: str = None,
+        stage: str = None,
+        name: str = None,
+        input_model: Type[BaseModel] = None,
+        output_model: Type[BaseModel] = None,
+        dependencies: List[str] = [],
+    ) -> None:
+        """
+        Adds a component node to the pipeline.
+
+        Args:
+            component (Union[BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]], optional):
+                The component to be added. It can be either a BaseComponent object or a callable function.
+                Defaults to None.
+            position (PositionType, optional):
+                The position at which the component should be added in the pipeline.
+                Valid values are "default", "first", "last", "after", and "before".
+                Defaults to "default".
+            reference (str, optional):
+                The name of the component after or before which the new component should be added.
+                Only applicable when position is "after" or "before".
+                Defaults to None.
+            stage (str, optional):
+                The stage to which the component belongs.
+                Defaults to None.
+            name (str, optional):
+                The name of the component.
+                Defaults to None, in which case the name of the function will be used.
+            input_model (Type[BaseModel], optional):
+                The input Pydantic model class for validating the input data.
+                Defaults to None.
+            output_model (Type[BaseModel], optional):
+                The output Pydantic model class for validating the output data.
+                Defaults to None.
+            dependencies (List[str], optional):
+                The list of component names that this component depends on.
+                Defaults to an empty list.
+
+        Returns:
+            The original component if component is None, otherwise the wrapper function.
+
+        """
+
+        def wrapper(func):
+            def validated_component(data: DataContainer[T]) -> DataContainer[T]:
+                # Validate input if input_model is provided
+                if input_model:
+                    input_model(**data.__dict__)
+
+                # Run the component
+                result = func(data)
+
+                # Validate output if output_model is provided
+                if output_model:
+                    output_model(**result.__dict__)
+
+                return result
+
+            component_func = (
+                validated_component if input_model or output_model else func
+            )
+            new_component = PipelineNode(
+                func=component_func,
+                position=position,
+                reference=reference,
+                stage=stage,
+                name=name
+                if name is not None
+                else (
+                    component_func.__name__
+                    if hasattr(component_func, "__name__")
+                    else (
+                        component_func.__class__.__name__
+                        if hasattr(component_func, "__class__")
+                        else str(component_func)
+                    )
+                ),
+                dependencies=dependencies,
+            )
+            try:
+                self._add_component_at_position(new_component, position, reference)
+            except Exception as e:
+                raise ValueError(f"Error adding component: {str(e)}")
+
+            if stage:
+                if stage not in self._stages:
+                    self._stages[stage] = []
+                self._stages[stage].append(func)
+                logger.debug(
+                    f"Successfully added component '{new_component.name}' to stage '{stage}'."
+                )
+
+            return func
+
+        if component is None:
+            return wrapper
+        if callable(component):
+            return wrapper(component)
+        else:
+            raise ValueError("Component must be callable")
+
+    def _add_component_at_position(self, new_component, position, reference):
+        """
+        Add a new component to the pipeline at a specified position.
+
+        Args:
+            new_component (PipelineNode): The new component to be added to the pipeline.
+            position (str): The position where the component should be added.
+                            Valid values are 'first', 'last', 'after', 'before', or 'default'.
+            reference (str, optional): The name of the reference component when using 'after' or 'before' positions.
+
+        Raises:
+            ValueError: If an invalid position is provided or if a reference is required but not provided.
+
+        This method handles the insertion of a new component into the pipeline based on the specified position:
+        - 'first': Inserts the component at the beginning of the pipeline.
+        - 'last' or 'default': Appends the component to the end of the pipeline.
+        - 'after' or 'before': Inserts the component relative to a reference component.
+
+        For 'after' and 'before' positions, a reference component name must be provided.
+        """
+        if position == "first":
+            self._components.insert(0, new_component)
+        elif position in ["last", "default"]:
+            self._components.append(new_component)
+        elif position in ["after", "before"]:
+            if not reference:
+                raise ValueError(
+                    f"Reference must be provided for position '{position}'."
+                )
+            offset = 1 if position == "after" else 0
+            self._insert_relative_position(new_component, reference, offset)
+        else:
+            raise ValueError(
+                f"Invalid position '{position}'. Must be 'first', 'last', 'after', 'before', or 'default'."
+            )
+
+    def _insert_relative_position(self, component, reference, offset):
+        """
+        Insert a component relative to a reference component in the pipeline.
+
+        Args:
+            component (PipelineNode): The component to be inserted.
+            reference (str): The name of the reference component.
+            offset (int): The offset from the reference component (0 for before, 1 for after).
+
+        Raises:
+            ValueError: If the reference component is not found in the pipeline.
+        """
+        ref_index = next(
+            (i for i, c in enumerate(self._components) if c.name == reference), None
+        )
+        if ref_index is None:
+            raise ValueError(f"Reference component '{reference}' not found.")
+
+        self._components.insert(ref_index + offset, component)
+
+    def remove(self, component_name: str) -> None:
+        """
+        Removes a component from the pipeline.
+
+        Args:
+            component_name (str): The name of the component to be removed.
+
+        Raises:
+            ValueError: If the component is not found in the pipeline.
+
+        Returns:
+            None
+
+        Logs:
+            DEBUG: When the component is successfully removed.
+            WARNING: If the component fails to be removed after attempting to do so.
+        """
+        # Check if the component exists in the pipeline
+        if not any(c.name == component_name for c in self._components):
+            raise ValueError(f"Component '{component_name}' not found in the pipeline.")
+
+        # Remove the component from self.components
+        original_count = len(self._components)
+        self._components = [c for c in self._components if c.name != component_name]
+
+        # Remove the component from stages
+        for stage in self._stages.values():
+            stage[:] = [c for c in stage if c.__name__ != component_name]
+
+        # Validate that the component was removed
+        if len(self._components) == original_count or any(
+            c.__name__ == component_name
+            for stage in self._stages.values()
+            for c in stage
+        ):
+            logger.warning(
+                f"Failed to remove component '{component_name}' from the pipeline."
+            )
+        logger.debug(
+            f"Successfully removed component '{component_name}' from the pipeline."
+        )
+
+    def replace(
+        self,
+        old_component_name: str,
+        new_component: Union[
+            BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]
+        ],
+    ) -> None:
+        """
+        Replaces a component in the pipeline with a new component.
+
+        Args:
+            old_component_name (str): The name of the component to be replaced.
+            new_component (Union[BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]]):
+                The new component to replace the old component with.
+
+        Returns:
+            None
+
+        Raises:
+            ValueError: If the old component is not found in the pipeline.
+            ValueError: If the new component is not a BaseComponent or a callable.
+            ValueError: If the new component callable doesn't have the correct signature.
+
+        Logs:
+            DEBUG: When the component is successfully replaced.
+        """
+
+        if isinstance(new_component, BaseComponent):
+            # It's a valid BaseComponent, no further checks needed
+            pass
+        elif callable(new_component):
+            sig = signature(new_component)
+            param = list(sig.parameters.values())[0]
+            if len(sig.parameters) != 1 or not issubclass(
+                param.annotation, DataContainer
+            ):
+                raise ValueError(
+                    "New component callable must accept a single argument that is a subclass of DataContainer."
+                )
+        else:
+            raise ValueError("New component must be a BaseComponent or a callable.")
+
+        old_component_found = False
+
+        # Replace in self.components
+        for i, c in enumerate(self._components):
+            if c.name == old_component_name:
+                self._components[i] = PipelineNode(
+                    func=new_component,
+                    name=old_component_name,
+                    position=c.position,
+                    reference=c.reference,
+                    stage=c.stage,
+                    dependencies=c.dependencies,
+                )
+                old_component_found = True
+
+        # Replace in self.stages
+        for stage in self._stages.values():
+            for i, c in enumerate(stage):
+                if getattr(c, "name", c.__name__) == old_component_name:
+                    stage[i] = new_component
+                    old_component_found = True
+
+        if not old_component_found:
+            raise ValueError(
+                f"Component '{old_component_name}' not found in the pipeline."
+            )
+        else:
+            logger.debug(
+                f"Successfully replaced component '{old_component_name}' in the pipeline."
+            )
+
+    def __call__(self, data: Union[T, DataContainer[T]]) -> DataContainer[T]:
+        if self._built_pipeline is None:
+            self._built_pipeline = self.build()
+        return self._built_pipeline(data)
+
+    def build(self) -> Callable:
+        """
+        Builds and returns a pipeline function that applies a series of components to the input data.
+        Returns:
+            pipeline: A function that takes input data and applies the ordered components to it.
+        Raises:
+            ValueError: If a circular dependency is detected among the components.
+        """
+
+        def resolve_dependencies():
+            resolved = []
+            unresolved = self._components.copy()
+
+            while unresolved:
+                for component in unresolved:
+                    if all(
+                        dep in [c.name for c in resolved]
+                        for dep in component.dependencies
+                    ):
+                        resolved.append(component)
+                        unresolved.remove(component)
+                        break
+                else:
+                    raise ValueError("Circular dependency detected")
+
+            return [c.func for c in resolved]
+
+        ordered_components = resolve_dependencies()
+
+        def pipeline(data: Union[T, DataContainer[T]]) -> DataContainer[T]:
+            if self._input_connector:
+                data = self._input_connector.input(data)
+
+            if not isinstance(data, DataContainer):
+                data = DataContainer(data)
+
+            data = reduce(lambda d, comp: comp(d), ordered_components, data)
+            if self._output_connector:
+                data = self._output_connector.output(data)
+
+            return data
+
+        if self._built_pipeline is not pipeline:
+            self._built_pipeline = pipeline
+
+        return pipeline
+
+
+ + + +
+ + + + + + + +
+ + + +

+ stages + + + property + writable + + +

+ + +
+ +

Returns a human-readable representation of the pipeline stages.

+
+ +
+ + + +
+ + +

+ add_input(connector) + +

+ + +
+ +

Adds an input connector to the pipeline.

+

This method sets the input connector for the pipeline, which is responsible +for processing the input data before it's passed to the pipeline components.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ connector + +
+

The input connector to be added to the pipeline.

+
+

+ + TYPE: + Connector[T] + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + None + + +
+

None

+
+
+ + +
+ Note +

Only one input connector can be set for the pipeline. If this method is +called multiple times, the last connector will overwrite the previous ones.

+
+
+ Source code in healthchain/pipeline/base.py +
def add_input(self, connector: BaseConnector[T]) -> None:
+    """
+    Adds an input connector to the pipeline.
+
+    This method sets the input connector for the pipeline, which is responsible
+    for processing the input data before it's passed to the pipeline components.
+
+    Args:
+        connector (Connector[T]): The input connector to be added to the pipeline.
+
+    Returns:
+        None
+
+    Note:
+        Only one input connector can be set for the pipeline. If this method is
+        called multiple times, the last connector will overwrite the previous ones.
+    """
+    self._input_connector = connector
+
+
+
+ +
+ +
+ + +

+ add_node(component=None, *, position='default', reference=None, stage=None, name=None, input_model=None, output_model=None, dependencies=[]) + +

+ + +
+ +

Adds a component node to the pipeline.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ component + +
+

The component to be added. It can be either a BaseComponent object or a callable function. +Defaults to None.

+
+

+ + TYPE: + Union[BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]] + + + DEFAULT: + None + +

+
+ position + +
+

The position at which the component should be added in the pipeline. +Valid values are "default", "first", "last", "after", and "before". +Defaults to "default".

+
+

+ + TYPE: + PositionType + + + DEFAULT: + 'default' + +

+
+ reference + +
+

The name of the component after or before which the new component should be added. +Only applicable when position is "after" or "before". +Defaults to None.

+
+

+ + TYPE: + str + + + DEFAULT: + None + +

+
+ stage + +
+

The stage to which the component belongs. +Defaults to None.

+
+

+ + TYPE: + str + + + DEFAULT: + None + +

+
+ name + +
+

The name of the component. +Defaults to None, in which case the name of the function will be used.

+
+

+ + TYPE: + str + + + DEFAULT: + None + +

+
+ input_model + +
+

The input Pydantic model class for validating the input data. +Defaults to None.

+
+

+ + TYPE: + Type[BaseModel] + + + DEFAULT: + None + +

+
+ output_model + +
+

The output Pydantic model class for validating the output data. +Defaults to None.

+
+

+ + TYPE: + Type[BaseModel] + + + DEFAULT: + None + +

+
+ dependencies + +
+

The list of component names that this component depends on. +Defaults to an empty list.

+
+

+ + TYPE: + List[str] + + + DEFAULT: + [] + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + None + + +
+

The original component if component is None, otherwise the wrapper function.

+
+
+ +
+ Source code in healthchain/pipeline/base.py +
def add_node(
+    self,
+    component: Union[
+        BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]
+    ] = None,
+    *,
+    position: PositionType = "default",
+    reference: str = None,
+    stage: str = None,
+    name: str = None,
+    input_model: Type[BaseModel] = None,
+    output_model: Type[BaseModel] = None,
+    dependencies: List[str] = [],
+) -> None:
+    """
+    Adds a component node to the pipeline.
+
+    Args:
+        component (Union[BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]], optional):
+            The component to be added. It can be either a BaseComponent object or a callable function.
+            Defaults to None.
+        position (PositionType, optional):
+            The position at which the component should be added in the pipeline.
+            Valid values are "default", "first", "last", "after", and "before".
+            Defaults to "default".
+        reference (str, optional):
+            The name of the component after or before which the new component should be added.
+            Only applicable when position is "after" or "before".
+            Defaults to None.
+        stage (str, optional):
+            The stage to which the component belongs.
+            Defaults to None.
+        name (str, optional):
+            The name of the component.
+            Defaults to None, in which case the name of the function will be used.
+        input_model (Type[BaseModel], optional):
+            The input Pydantic model class for validating the input data.
+            Defaults to None.
+        output_model (Type[BaseModel], optional):
+            The output Pydantic model class for validating the output data.
+            Defaults to None.
+        dependencies (List[str], optional):
+            The list of component names that this component depends on.
+            Defaults to an empty list.
+
+    Returns:
+        The original component if component is None, otherwise the wrapper function.
+
+    """
+
+    def wrapper(func):
+        def validated_component(data: DataContainer[T]) -> DataContainer[T]:
+            # Validate input if input_model is provided
+            if input_model:
+                input_model(**data.__dict__)
+
+            # Run the component
+            result = func(data)
+
+            # Validate output if output_model is provided
+            if output_model:
+                output_model(**result.__dict__)
+
+            return result
+
+        component_func = (
+            validated_component if input_model or output_model else func
+        )
+        new_component = PipelineNode(
+            func=component_func,
+            position=position,
+            reference=reference,
+            stage=stage,
+            name=name
+            if name is not None
+            else (
+                component_func.__name__
+                if hasattr(component_func, "__name__")
+                else (
+                    component_func.__class__.__name__
+                    if hasattr(component_func, "__class__")
+                    else str(component_func)
+                )
+            ),
+            dependencies=dependencies,
+        )
+        try:
+            self._add_component_at_position(new_component, position, reference)
+        except Exception as e:
+            raise ValueError(f"Error adding component: {str(e)}")
+
+        if stage:
+            if stage not in self._stages:
+                self._stages[stage] = []
+            self._stages[stage].append(func)
+            logger.debug(
+                f"Successfully added component '{new_component.name}' to stage '{stage}'."
+            )
+
+        return func
+
+    if component is None:
+        return wrapper
+    if callable(component):
+        return wrapper(component)
+    else:
+        raise ValueError("Component must be callable")
+
+
+
+ +
+ +
+ + +

+ add_output(connector) + +

+ + +
+ +

Adds an output connector to the pipeline.

+

This method sets the output connector for the pipeline, which is responsible +for processing the output data after it has passed through all pipeline components.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ connector + +
+

The output connector to be added to the pipeline.

+
+

+ + TYPE: + Connector[T] + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + None + + +
+

None

+
+
+ + +
+ Note +

Only one output connector can be set for the pipeline. If this method is +called multiple times, the last connector will overwrite the previous ones.

+
+
+ Source code in healthchain/pipeline/base.py +
def add_output(self, connector: BaseConnector[T]) -> None:
+    """
+    Adds an output connector to the pipeline.
+
+    This method sets the output connector for the pipeline, which is responsible
+    for processing the output data after it has passed through all pipeline components.
+
+    Args:
+        connector (Connector[T]): The output connector to be added to the pipeline.
+
+    Returns:
+        None
+
+    Note:
+        Only one output connector can be set for the pipeline. If this method is
+        called multiple times, the last connector will overwrite the previous ones.
+    """
+    self._output_connector = connector
+
+
+
+ +
+ +
+ + +

+ build() + +

+ + +
+ +

Builds and returns a pipeline function that applies a series of components to the input data. +Returns: + pipeline: A function that takes input data and applies the ordered components to it. +Raises: + ValueError: If a circular dependency is detected among the components.

+ +
+ Source code in healthchain/pipeline/base.py +
def build(self) -> Callable:
+    """
+    Builds and returns a pipeline function that applies a series of components to the input data.
+    Returns:
+        pipeline: A function that takes input data and applies the ordered components to it.
+    Raises:
+        ValueError: If a circular dependency is detected among the components.
+    """
+
+    def resolve_dependencies():
+        resolved = []
+        unresolved = self._components.copy()
+
+        while unresolved:
+            for component in unresolved:
+                if all(
+                    dep in [c.name for c in resolved]
+                    for dep in component.dependencies
+                ):
+                    resolved.append(component)
+                    unresolved.remove(component)
+                    break
+            else:
+                raise ValueError("Circular dependency detected")
+
+        return [c.func for c in resolved]
+
+    ordered_components = resolve_dependencies()
+
+    def pipeline(data: Union[T, DataContainer[T]]) -> DataContainer[T]:
+        if self._input_connector:
+            data = self._input_connector.input(data)
+
+        if not isinstance(data, DataContainer):
+            data = DataContainer(data)
+
+        data = reduce(lambda d, comp: comp(d), ordered_components, data)
+        if self._output_connector:
+            data = self._output_connector.output(data)
+
+        return data
+
+    if self._built_pipeline is not pipeline:
+        self._built_pipeline = pipeline
+
+    return pipeline
+
+
+
+ +
+ +
+ + +

+ configure_pipeline(model_config) + + + abstractmethod + + +

+ + +
+ +

Configure the pipeline based on the provided model configuration.

+

This method should be implemented by subclasses to add specific components +and configure the pipeline according to the given model configuration. +The configuration typically involves: +1. Setting up input/output connectors +2. Adding model components based on the model source +3. Adding any additional processing nodes +4. Configuring the pipeline stages and execution order

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ model_config + +
+

Configuration object containing: +- source: Model source (e.g. huggingface, spacy, langchain) +- model: Model identifier or path +- task: Optional task name (e.g. summarization, ner) +- path: Optional local path to model files +- kwargs: Additional model configuration parameters

+
+

+ + TYPE: + ModelConfig + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + None + + +
+

None

+
+
+ + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + NotImplementedError + + +
+

If the method is not implemented by a subclass.

+
+
+ + +
+ Example +
+
+
+

def configure_pipeline(self, config: ModelConfig): +... # Add FHIR connector for input/output +... connector = FhirConnector() +... self.add_input(connector) +... +... # Add model component +... model = self.get_model_component(config) +... self.add_node(model, stage="processing") +... +... # Add output formatting +... self.add_node(OutputFormatter(), stage="formatting") +... self.add_output(connector)

+
+
+
+
+
+ Source code in healthchain/pipeline/base.py +
@abstractmethod
+def configure_pipeline(self, model_config: ModelConfig) -> None:
+    """
+    Configure the pipeline based on the provided model configuration.
+
+    This method should be implemented by subclasses to add specific components
+    and configure the pipeline according to the given model configuration.
+    The configuration typically involves:
+    1. Setting up input/output connectors
+    2. Adding model components based on the model source
+    3. Adding any additional processing nodes
+    4. Configuring the pipeline stages and execution order
+
+    Args:
+        model_config (ModelConfig): Configuration object containing:
+            - source: Model source (e.g. huggingface, spacy, langchain)
+            - model: Model identifier or path
+            - task: Optional task name (e.g. summarization, ner)
+            - path: Optional local path to model files
+            - kwargs: Additional model configuration parameters
+
+    Returns:
+        None
+
+    Raises:
+        NotImplementedError: If the method is not implemented by a subclass.
+
+    Example:
+        >>> def configure_pipeline(self, config: ModelConfig):
+        ...     # Add FHIR connector for input/output
+        ...     connector = FhirConnector()
+        ...     self.add_input(connector)
+        ...
+        ...     # Add model component
+        ...     model = self.get_model_component(config)
+        ...     self.add_node(model, stage="processing")
+        ...
+        ...     # Add output formatting
+        ...     self.add_node(OutputFormatter(), stage="formatting")
+        ...     self.add_output(connector)
+    """
+    raise NotImplementedError("This method must be implemented by subclasses.")
+
+
+
+ +
+ +
+ + +

+ from_local_model(path, source, task=None, template=None, template_path=None, **kwargs) + + + classmethod + + +

+ + +
+ +

Load pipeline from a local model path.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ path + +
+

Path to local model files/directory

+
+

+ + TYPE: + Union[str, Path] + +

+
+ source + +
+

Model source (e.g. "huggingface", "spacy")

+
+

+ + TYPE: + Union[str, ModelSource] + +

+
+ task + +
+

Task identifier for the model. Defaults to None.

+
+

+ + TYPE: + Optional[str] + + + DEFAULT: + None + +

+
+ template + +
+

Optional template string for formatting model output.

+
+

+ + TYPE: + Optional[str] + + + DEFAULT: + None + +

+
+ template_path + +
+

Optional path to template file for formatting model output.

+
+

+ + TYPE: + Optional[Union[str, Path]] + + + DEFAULT: + None + +

+
+ **kwargs + +
+

Additional configuration options passed to the model. e.g. temperature, max_length, etc.

+
+

+ + TYPE: + Any + + + DEFAULT: + {} + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ BasePipeline + +
+

Configured pipeline instance.

+
+

+ + TYPE: + BasePipeline + +

+
+ + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If source is not a valid ModelSource.

+
+
+ + +

Examples:

+
>>> # Load local HuggingFace model
+>>> pipeline = Pipeline.from_local_model(
+...     "models/my_summarizer",
+...     source="huggingface",
+...     task="summarization",
+...     temperature=0.7
+... )
+>>>
+>>> # Load local SpaCy model
+>>> pipeline = Pipeline.from_local_model(
+...     "models/en_core_sci_md",
+...     source="spacy",
+...     disable=["parser"]
+... )
+>>>
+>>> # Load with output template
+>>> template = '''{"summary": "{{ model_output }}"}'''
+>>> pipeline = Pipeline.from_local_model(
+...     "models/gpt_model",
+...     source="huggingface",
+...     template=template
+... )
+
+ +
+ Source code in healthchain/pipeline/base.py +
@classmethod
+def from_local_model(
+    cls,
+    path: Union[str, Path],
+    source: Union[str, ModelSource],
+    task: Optional[str] = None,
+    template: Optional[str] = None,
+    template_path: Optional[Union[str, Path]] = None,
+    **kwargs: Any,
+) -> "BasePipeline":
+    """Load pipeline from a local model path.
+
+    Args:
+        path (Union[str, Path]): Path to local model files/directory
+        source (Union[str, ModelSource]): Model source (e.g. "huggingface", "spacy")
+        task (Optional[str]): Task identifier for the model. Defaults to None.
+        template (Optional[str]): Optional template string for formatting model output.
+        template_path (Optional[Union[str, Path]]): Optional path to template file for formatting model output.
+        **kwargs: Additional configuration options passed to the model. e.g. temperature, max_length, etc.
+
+    Returns:
+        BasePipeline: Configured pipeline instance.
+
+    Raises:
+        ValueError: If source is not a valid ModelSource.
+
+    Examples:
+        >>> # Load local HuggingFace model
+        >>> pipeline = Pipeline.from_local_model(
+        ...     "models/my_summarizer",
+        ...     source="huggingface",
+        ...     task="summarization",
+        ...     temperature=0.7
+        ... )
+        >>>
+        >>> # Load local SpaCy model
+        >>> pipeline = Pipeline.from_local_model(
+        ...     "models/en_core_sci_md",
+        ...     source="spacy",
+        ...     disable=["parser"]
+        ... )
+        >>>
+        >>> # Load with output template
+        >>> template = '''{"summary": "{{ model_output }}"}'''
+        >>> pipeline = Pipeline.from_local_model(
+        ...     "models/gpt_model",
+        ...     source="huggingface",
+        ...     template=template
+        ... )
+    """
+    pipeline = cls()
+    pipeline._configure_output_templates(template, template_path)
+
+    path = Path(path)
+    config = ModelConfig(
+        source=ModelSource(source.lower()),
+        model_id=path.name,
+        path=path,
+        task=task,
+        kwargs=kwargs,
+    )
+    pipeline._model_config = config
+    pipeline.configure_pipeline(config)
+
+    return pipeline
+
+
+
+ +
+ +
+ + +

+ from_model_id(model_id, source='huggingface', task='text-generation', template=None, template_path=None, **kwargs) + + + classmethod + + +

+ + +
+ +

Load pipeline from a model identifier.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ model_id + +
+

Model identifier (e.g. HuggingFace model ID, SpaCy model name)

+
+

+ + TYPE: + str + +

+
+ source + +
+

Model source. Defaults to "huggingface". +Can be "huggingface", "spacy".

+
+

+ + TYPE: + Union[str, ModelSource] + + + DEFAULT: + 'huggingface' + +

+
+ task + +
+

Task identifier for the model. Defaults to "text-generation".

+
+

+ + TYPE: + Optional[str] + + + DEFAULT: + 'text-generation' + +

+
+ template + +
+

Optional template string for formatting model output.

+
+

+ + TYPE: + Optional[str] + + + DEFAULT: + None + +

+
+ template_path + +
+

Optional path to template file for formatting model output.

+
+

+ + TYPE: + Optional[Union[str, Path]] + + + DEFAULT: + None + +

+
+ **kwargs + +
+

Additional configuration options passed to the model. e.g. temperature, max_length, etc.

+
+

+ + TYPE: + Any + + + DEFAULT: + {} + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ BasePipeline + +
+

Configured pipeline instance.

+
+

+ + TYPE: + BasePipeline + +

+
+ + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If source is not a valid ModelSource.

+
+
+ + +

Examples:

+
>>> # Load HuggingFace model
+>>> pipeline = Pipeline.from_model_id(
+...     "facebook/bart-large-cnn",
+...     task="summarization",
+...     temperature=0.7
+... )
+>>>
+>>> # Load SpaCy model
+>>> pipeline = Pipeline.from_model_id(
+...     "en_core_sci_md",
+...     source="spacy",
+...     disable=["parser"]
+... )
+>>>
+>>> # Load with output template
+>>> template = '''{"summary": "{{ model_output }}"}'''
+>>> pipeline = Pipeline.from_model_id(
+...     "gpt-3.5-turbo",
+...     source="huggingface",
+...     template=template
+... )
+
+ +
+ Source code in healthchain/pipeline/base.py +
@classmethod
+def from_model_id(
+    cls,
+    model_id: str,
+    source: Union[str, ModelSource] = "huggingface",
+    task: Optional[str] = "text-generation",
+    template: Optional[str] = None,
+    template_path: Optional[Union[str, Path]] = None,
+    **kwargs: Any,
+) -> "BasePipeline":
+    """
+    Load pipeline from a model identifier.
+
+    Args:
+        model_id (str): Model identifier (e.g. HuggingFace model ID, SpaCy model name)
+        source (Union[str, ModelSource]): Model source. Defaults to "huggingface".
+            Can be "huggingface", "spacy".
+        task (Optional[str]): Task identifier for the model. Defaults to "text-generation".
+        template (Optional[str]): Optional template string for formatting model output.
+        template_path (Optional[Union[str, Path]]): Optional path to template file for formatting model output.
+        **kwargs: Additional configuration options passed to the model. e.g. temperature, max_length, etc.
+
+    Returns:
+        BasePipeline: Configured pipeline instance.
+
+    Raises:
+        ValueError: If source is not a valid ModelSource.
+
+    Examples:
+        >>> # Load HuggingFace model
+        >>> pipeline = Pipeline.from_model_id(
+        ...     "facebook/bart-large-cnn",
+        ...     task="summarization",
+        ...     temperature=0.7
+        ... )
+        >>>
+        >>> # Load SpaCy model
+        >>> pipeline = Pipeline.from_model_id(
+        ...     "en_core_sci_md",
+        ...     source="spacy",
+        ...     disable=["parser"]
+        ... )
+        >>>
+        >>> # Load with output template
+        >>> template = '''{"summary": "{{ model_output }}"}'''
+        >>> pipeline = Pipeline.from_model_id(
+        ...     "gpt-3.5-turbo",
+        ...     source="huggingface",
+        ...     template=template
+        ... )
+    """
+    pipeline = cls()
+    pipeline._configure_output_templates(template, template_path)
+
+    config = ModelConfig(
+        source=ModelSource(source.lower()),
+        model_id=model_id,
+        task=task,
+        kwargs=kwargs,
+    )
+    pipeline._model_config = config
+    pipeline.configure_pipeline(config)
+
+    return pipeline
+
+
+
+ +
+ +
+ + +

+ load(pipeline, source, task='text-generation', template=None, template_path=None, **kwargs) + + + classmethod + + +

+ + +
+ +

Load a pipeline from a pre-built pipeline object (e.g. LangChain chain or HuggingFace pipeline).

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ pipeline + +
+

A callable pipeline object (e.g. LangChain chain, HuggingFace pipeline)

+
+

+ + TYPE: + Callable + +

+
+ source + +
+

Source of the pipeline. Can be "langchain" or "huggingface".

+
+

+ + TYPE: + str + +

+
+ task + +
+

Task identifier used to retrieve model outputs. +Defaults to "text-generation".

+
+

+ + TYPE: + Optional[str] + + + DEFAULT: + 'text-generation' + +

+
+ template + +
+

Template string for formatting outputs. +Defaults to None.

+
+

+ + TYPE: + Optional[str] + + + DEFAULT: + None + +

+
+ template_path + +
+

Path to template file. +Defaults to None.

+
+

+ + TYPE: + Optional[Union[str, Path]] + + + DEFAULT: + None + +

+
+ **kwargs + +
+

Additional configuration options passed to the pipeline.

+
+

+ + TYPE: + Any + + + DEFAULT: + {} + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ BasePipeline + +
+

Configured pipeline instance.

+
+

+ + TYPE: + BasePipeline + +

+
+ + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If pipeline is not callable or source is invalid.

+
+
+ + +

Examples:

+
>>> # Load LangChain pipeline
+>>> from langchain_core.prompts import ChatPromptTemplate
+>>> from langchain_openai import ChatOpenAI
+>>> chain = ChatPromptTemplate.from_template("What is {input}?") | ChatOpenAI()
+>>> pipeline = Pipeline.load(chain, source="langchain", temperature=0.7)
+>>>
+>>> # Load HuggingFace pipeline
+>>> from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
+>>> tokenizer = AutoTokenizer.from_pretrained("gpt2")
+>>> model = AutoModelForCausalLM.from_pretrained("gpt2")
+>>> pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=10)
+>>> pipeline = Pipeline.load(pipe, source="huggingface")
+
+ +
+ Source code in healthchain/pipeline/base.py +
@classmethod
+def load(
+    cls,
+    pipeline: Callable,
+    source: str,
+    task: Optional[str] = "text-generation",
+    template: Optional[str] = None,
+    template_path: Optional[Union[str, Path]] = None,
+    **kwargs: Any,
+) -> "BasePipeline":
+    """
+    Load a pipeline from a pre-built pipeline object (e.g. LangChain chain or HuggingFace pipeline).
+
+    Args:
+        pipeline (Callable): A callable pipeline object (e.g. LangChain chain, HuggingFace pipeline)
+        source (str): Source of the pipeline. Can be "langchain" or "huggingface".
+        task (Optional[str]): Task identifier used to retrieve model outputs.
+            Defaults to "text-generation".
+        template (Optional[str]): Template string for formatting outputs.
+            Defaults to None.
+        template_path (Optional[Union[str, Path]]): Path to template file.
+            Defaults to None.
+        **kwargs: Additional configuration options passed to the pipeline.
+
+    Returns:
+        BasePipeline: Configured pipeline instance.
+
+    Raises:
+        ValueError: If pipeline is not callable or source is invalid.
+
+    Examples:
+        >>> # Load LangChain pipeline
+        >>> from langchain_core.prompts import ChatPromptTemplate
+        >>> from langchain_openai import ChatOpenAI
+        >>> chain = ChatPromptTemplate.from_template("What is {input}?") | ChatOpenAI()
+        >>> pipeline = Pipeline.load(chain, source="langchain", temperature=0.7)
+        >>>
+        >>> # Load HuggingFace pipeline
+        >>> from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
+        >>> tokenizer = AutoTokenizer.from_pretrained("gpt2")
+        >>> model = AutoModelForCausalLM.from_pretrained("gpt2")
+        >>> pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=10)
+        >>> pipeline = Pipeline.load(pipe, source="huggingface")
+    """
+    if not (hasattr(pipeline, "__call__") or hasattr(pipeline, "invoke")):
+        raise ValueError("Pipeline must be a callable object")
+
+    # Validate source
+    source = source.lower()
+    if source not in ["langchain", "huggingface"]:
+        raise ValueError(
+            "Source must be either 'langchain' or 'huggingface' for direct pipeline loading"
+        )
+
+    # For HuggingFace pipelines, try to infer task if not provided
+    if source == "huggingface" and hasattr(pipeline, "task") and not task:
+        task = pipeline.task
+
+    instance = cls()
+    instance._configure_output_templates(template, template_path)
+
+    config = ModelConfig(
+        source=ModelSource(source),
+        pipeline_object=pipeline,
+        task=task,
+        kwargs=kwargs,
+    )
+
+    instance._model_config = config
+    instance.configure_pipeline(config)
+
+    return instance
+
+
+
+ +
+ +
+ + +

+ remove(component_name) + +

+ + +
+ +

Removes a component from the pipeline.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ component_name + +
+

The name of the component to be removed.

+
+

+ + TYPE: + str + +

+
+ + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If the component is not found in the pipeline.

+
+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + None + + +
+

None

+
+
+ + +
+ Logs +

DEBUG: When the component is successfully removed. +WARNING: If the component fails to be removed after attempting to do so.

+
+
+ Source code in healthchain/pipeline/base.py +
def remove(self, component_name: str) -> None:
+    """
+    Removes a component from the pipeline.
+
+    Args:
+        component_name (str): The name of the component to be removed.
+
+    Raises:
+        ValueError: If the component is not found in the pipeline.
+
+    Returns:
+        None
+
+    Logs:
+        DEBUG: When the component is successfully removed.
+        WARNING: If the component fails to be removed after attempting to do so.
+    """
+    # Check if the component exists in the pipeline
+    if not any(c.name == component_name for c in self._components):
+        raise ValueError(f"Component '{component_name}' not found in the pipeline.")
+
+    # Remove the component from self.components
+    original_count = len(self._components)
+    self._components = [c for c in self._components if c.name != component_name]
+
+    # Remove the component from stages
+    for stage in self._stages.values():
+        stage[:] = [c for c in stage if c.__name__ != component_name]
+
+    # Validate that the component was removed
+    if len(self._components) == original_count or any(
+        c.__name__ == component_name
+        for stage in self._stages.values()
+        for c in stage
+    ):
+        logger.warning(
+            f"Failed to remove component '{component_name}' from the pipeline."
+        )
+    logger.debug(
+        f"Successfully removed component '{component_name}' from the pipeline."
+    )
+
+
+
+ +
+ +
+ + +

+ replace(old_component_name, new_component) + +

+ + +
+ +

Replaces a component in the pipeline with a new component.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ old_component_name + +
+

The name of the component to be replaced.

+
+

+ + TYPE: + str + +

+
+ new_component + +
+

The new component to replace the old component with.

+
+

+ + TYPE: + Union[BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]] + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ + None + + +
+

None

+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If the old component is not found in the pipeline.

+
+
+ + ValueError + + +
+

If the new component is not a BaseComponent or a callable.

+
+
+ + ValueError + + +
+

If the new component callable doesn't have the correct signature.

+
+
+ + +
+ Logs +

DEBUG: When the component is successfully replaced.

+
+
+ Source code in healthchain/pipeline/base.py +
def replace(
+    self,
+    old_component_name: str,
+    new_component: Union[
+        BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]
+    ],
+) -> None:
+    """
+    Replaces a component in the pipeline with a new component.
+
+    Args:
+        old_component_name (str): The name of the component to be replaced.
+        new_component (Union[BaseComponent[T], Callable[[DataContainer[T]], DataContainer[T]]]):
+            The new component to replace the old component with.
+
+    Returns:
+        None
+
+    Raises:
+        ValueError: If the old component is not found in the pipeline.
+        ValueError: If the new component is not a BaseComponent or a callable.
+        ValueError: If the new component callable doesn't have the correct signature.
+
+    Logs:
+        DEBUG: When the component is successfully replaced.
+    """
+
+    if isinstance(new_component, BaseComponent):
+        # It's a valid BaseComponent, no further checks needed
+        pass
+    elif callable(new_component):
+        sig = signature(new_component)
+        param = list(sig.parameters.values())[0]
+        if len(sig.parameters) != 1 or not issubclass(
+            param.annotation, DataContainer
+        ):
+            raise ValueError(
+                "New component callable must accept a single argument that is a subclass of DataContainer."
+            )
+    else:
+        raise ValueError("New component must be a BaseComponent or a callable.")
+
+    old_component_found = False
+
+    # Replace in self.components
+    for i, c in enumerate(self._components):
+        if c.name == old_component_name:
+            self._components[i] = PipelineNode(
+                func=new_component,
+                name=old_component_name,
+                position=c.position,
+                reference=c.reference,
+                stage=c.stage,
+                dependencies=c.dependencies,
+            )
+            old_component_found = True
+
+    # Replace in self.stages
+    for stage in self._stages.values():
+        for i, c in enumerate(stage):
+            if getattr(c, "name", c.__name__) == old_component_name:
+                stage[i] = new_component
+                old_component_found = True
+
+    if not old_component_found:
+        raise ValueError(
+            f"Component '{old_component_name}' not found in the pipeline."
+        )
+    else:
+        logger.debug(
+            f"Successfully replaced component '{old_component_name}' in the pipeline."
+        )
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ ModelConfig + + + + dataclass + + +

+ + +
+ + +

Configuration for model initialization

+ + + + + + +
+ Source code in healthchain/pipeline/base.py +
43
+44
+45
+46
+47
+48
+49
+50
+51
+52
@dataclass
+class ModelConfig:
+    """Configuration for model initialization"""
+
+    source: ModelSource
+    model_id: Optional[str] = None
+    pipeline_object: Optional[Any] = None
+    task: Optional[str] = None
+    path: Optional[Path] = None
+    kwargs: Dict[str, Any] = field(default_factory=dict)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ ModelSource + + +

+ + +
+

+ Bases: Enum

+ + +

Enumeration of supported model sources

+ + + + + + +
+ Source code in healthchain/pipeline/base.py +
35
+36
+37
+38
+39
+40
class ModelSource(Enum):
+    """Enumeration of supported model sources"""
+
+    SPACY = "spacy"
+    HUGGINGFACE = "huggingface"
+    LANGCHAIN = "langchain"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ Pipeline + + +

+ + +
+

+ Bases: BasePipeline, Generic[T]

+ + +

Default Pipeline class for creating a basic data processing pipeline. +This class inherits from BasePipeline and provides a default implementation +of the configure_pipeline method, which does not add any specific components.

+ + + + + + +
+ Source code in healthchain/pipeline/base.py +
class Pipeline(BasePipeline, Generic[T]):
+    """
+    Default Pipeline class for creating a basic data processing pipeline.
+    This class inherits from BasePipeline and provides a default implementation
+    of the configure_pipeline method, which does not add any specific components.
+    """
+
+    def configure_pipeline(self, model_path: str) -> None:
+        """
+        Configures the pipeline by adding components based on the provided model path.
+        This default implementation does not add any specific components.
+
+        Args:
+            model_path (str): The path to the model used for configuring the pipeline.
+        """
+        # Default implementation: No specific components added
+        pass
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ configure_pipeline(model_path) + +

+ + +
+ +

Configures the pipeline by adding components based on the provided model path. +This default implementation does not add any specific components.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ model_path + +
+

The path to the model used for configuring the pipeline.

+
+

+ + TYPE: + str + +

+
+ +
+ Source code in healthchain/pipeline/base.py +
def configure_pipeline(self, model_path: str) -> None:
+    """
+    Configures the pipeline by adding components based on the provided model path.
+    This default implementation does not add any specific components.
+
+    Args:
+        model_path (str): The path to the model used for configuring the pipeline.
+    """
+    # Default implementation: No specific components added
+    pass
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ PipelineNode + + + + dataclass + + +

+ + +
+

+ Bases: Generic[T]

+ + +

Represents a node in a pipeline.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
func +
+

The function to be applied to the data.

+
+

+ + TYPE: + Callable[[DataContainer[T]], DataContainer[T]] + +

+
position +
+

The position of the node in the pipeline. Defaults to "default".

+
+

+ + TYPE: + PositionType + +

+
reference +
+

The reference for the relative position of the node. Name should be the "name" attribute of another node. Defaults to None.

+
+

+ + TYPE: + str + +

+
stage +
+

The stage of the node in the pipeline. Group nodes by stage e.g. "preprocessing". Defaults to None.

+
+

+ + TYPE: + str + +

+
name +
+

The name of the node. Defaults to None.

+
+

+ + TYPE: + str + +

+
dependencies +
+

The list of dependencies for the node. Defaults to an empty list.

+
+

+ + TYPE: + List[str] + +

+
+ + + + + + +
+ Source code in healthchain/pipeline/base.py +
55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
@dataclass
+class PipelineNode(Generic[T]):
+    """
+    Represents a node in a pipeline.
+
+    Attributes:
+        func (Callable[[DataContainer[T]], DataContainer[T]]): The function to be applied to the data.
+        position (PositionType, optional): The position of the node in the pipeline. Defaults to "default".
+        reference (str, optional): The reference for the relative position of the node. Name should be the "name" attribute of another node. Defaults to None.
+        stage (str, optional): The stage of the node in the pipeline. Group nodes by stage e.g. "preprocessing". Defaults to None.
+        name (str, optional): The name of the node. Defaults to None.
+        dependencies (List[str], optional): The list of dependencies for the node. Defaults to an empty list.
+    """
+
+    func: Callable[[DataContainer[T]], DataContainer[T]]
+    position: PositionType = "default"
+    reference: str = None
+    stage: str = None
+    name: str = None
+    dependencies: List[str] = field(default_factory=list)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/service/index.html b/api/service/index.html new file mode 100644 index 0000000..8f3f90e --- /dev/null +++ b/api/service/index.html @@ -0,0 +1,2370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Service - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Service

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ Service + + +

+ + +
+ + +

A service wrapper which registers routes and starts a FastAPI service

+

Parameters: +endpoints (Dict[str, Enpoint]): the list of endpoints to register, must be a dictionary +of Endpoint objects. Should have human-readable keys e.g. ["info", "service_mount"]

+ + + + + + +
+ Source code in healthchain/service/service.py +
class Service:
+    """
+    A service wrapper which registers routes and starts a FastAPI service
+
+    Parameters:
+    endpoints (Dict[str, Enpoint]): the list of endpoints to register, must be a dictionary
+    of Endpoint objects. Should have human-readable keys e.g. ["info", "service_mount"]
+
+    """
+
+    def __init__(self, endpoints: Dict[str, Endpoint] = None):
+        self.app = FastAPI(lifespan=self.lifespan)
+        self.endpoints: Endpoint = endpoints
+
+        if self.endpoints is not None:
+            self._register_routes()
+
+        # Router to handle stopping the server
+        self.stop_router = APIRouter()
+        self.stop_router.add_api_route(
+            "/shutdown", self._shutdown, methods=["GET"], include_in_schema=False
+        )
+        self.app.include_router(self.stop_router)
+
+    def _register_routes(self) -> None:
+        # TODO: add kwargs
+        for endpoint in self.endpoints.values():
+            if endpoint.api_protocol == ApiProtocol.soap:
+                wsgi_app = start_wsgi(endpoint.function)
+                self.app.mount(endpoint.path, WSGIMiddleware(wsgi_app))
+            else:
+                self.app.add_api_route(
+                    endpoint.path,
+                    endpoint.function,
+                    methods=[endpoint.method],
+                    response_model_exclude_none=True,
+                )
+
+    @asynccontextmanager
+    async def lifespan(self, app: FastAPI):
+        self._startup()
+        yield
+        self._shutdown()
+
+    def _startup(self) -> None:
+        healthchain_ascii = r"""
+
+    __  __           ____  __    ________          _
+   / / / /__  ____ _/ / /_/ /_  / ____/ /_  ____ _(_)___
+  / /_/ / _ \/ __ `/ / __/ __ \/ /   / __ \/ __ `/ / __ \
+ / __  /  __/ /_/ / / /_/ / / / /___/ / / / /_/ / / / / /
+/_/ /_/\___/\__,_/_/\__/_/ /_/\____/_/ /_/\__,_/_/_/ /_/
+
+"""  # noqa: E501
+
+        colors = ["red", "yellow", "green", "cyan", "blue", "magenta"]
+        for i, line in enumerate(healthchain_ascii.split("\n")):
+            color = colors[i % len(colors)]
+            print(colored(line, color))
+        for endpoint in self.endpoints.values():
+            print(
+                f"{colored('HEALTHCHAIN', 'green')}: {endpoint.method} endpoint at {endpoint.path}/"
+            )
+        print(
+            f"{colored('HEALTHCHAIN', 'green')}: See more details at {colored(self.app.docs_url, 'magenta')}"
+        )
+
+    def _shutdown(self):
+        """
+        Shuts down server
+        """
+        os.kill(os.getpid(), signal.SIGTERM)
+        return JSONResponse(content={"message": "Server is shutting down..."})
+
+    def run(self, config: Dict = None) -> None:
+        """
+        Starts server on uvicorn.
+
+        Parameters:
+        config (Dict): kwargs to pass into uvicorn.
+
+        """
+        if config is None:
+            config = {}
+
+        uvicorn.run(self.app, **config)
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ run(config=None) + +

+ + +
+ +

Starts server on uvicorn.

+

Parameters: +config (Dict): kwargs to pass into uvicorn.

+ +
+ Source code in healthchain/service/service.py +
def run(self, config: Dict = None) -> None:
+    """
+    Starts server on uvicorn.
+
+    Parameters:
+    config (Dict): kwargs to pass into uvicorn.
+
+    """
+    if config is None:
+        config = {}
+
+    uvicorn.run(self.app, **config)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/use_cases/index.html b/api/use_cases/index.html new file mode 100644 index 0000000..9a01fa1 --- /dev/null +++ b/api/use_cases/index.html @@ -0,0 +1,6505 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Use Cases - HealthChain 💫 🏥 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Use Cases

+ + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ ClinicalDecisionSupport + + +

+ + +
+

+ Bases: BaseUseCase

+ + +

Implements EHR backend simulator for Clinical Decision Support (CDS)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ service_api + +
+

the function body to inject into the main service

+
+

+ + TYPE: + APIMethod + + + DEFAULT: + None + +

+
+ service_config + +
+

the config kwargs for the uvicorn server passed into service

+
+

+ + TYPE: + Dict + + + DEFAULT: + None + +

+
+ service + +
+

the service runner object

+
+

+ + TYPE: + Service + + + DEFAULT: + None + +

+
+ client + +
+

the client runner object

+
+

+ + TYPE: + BaseClient + + + DEFAULT: + None + +

+
+

See https://cds-hooks.org/ for specification

+ + + + + + +
+ Source code in healthchain/use_cases/cds.py +
class ClinicalDecisionSupport(BaseUseCase):
+    """
+    Implements EHR backend simulator for Clinical Decision Support (CDS)
+
+    Parameters:
+        service_api (APIMethod): the function body to inject into the main service
+        service_config (Dict): the config kwargs for the uvicorn server passed into service
+        service (Service): the service runner object
+        client (BaseClient): the client runner object
+
+    See https://cds-hooks.org/ for specification
+    """
+
+    def __init__(
+        self,
+        service_api: Optional[APIMethod] = None,
+        service_config: Optional[Dict] = None,
+        service: Optional[Service] = None,
+        client: Optional[BaseClient] = None,
+    ) -> None:
+        super().__init__(
+            service_api=service_api,
+            service_config=service_config,
+            service=service,
+            client=client,
+        )
+        self._type = UseCaseType.cds
+        self._strategy = ClinicalDecisionSupportStrategy()
+        # do we need keys? just in case
+        # TODO make configurable
+        self._endpoints = {
+            "info": Endpoint(
+                path="/cds-services",
+                method="GET",
+                function=self.cds_discovery,
+                api_protocol="REST",
+            ),
+            "service_mount": Endpoint(
+                path="/cds-services/{id}",
+                method="POST",
+                function=self.cds_service,
+                api_protocol="REST",
+            ),
+        }
+
+    @property
+    def description(self) -> str:
+        return "Clinical decision support (HL7 CDS specification)"
+
+    @property
+    def type(self) -> UseCaseType:
+        return self._type
+
+    @property
+    def strategy(self) -> BaseStrategy:
+        return self._strategy
+
+    @property
+    def endpoints(self) -> Dict[str, Endpoint]:
+        return self._endpoints
+
+    def cds_discovery(self) -> CDSServiceInformation:
+        """
+        CDS discovery endpoint for FastAPI app, should be mounted to /cds-services
+        """
+        if self._client is None:
+            log.warning("CDS 'client' not configured, check class init.")
+            return CDSServiceInformation(services=[])
+
+        service_info = CDSService(
+            hook=self._client.workflow.value,
+            description="A test CDS hook service.",
+            id="1",
+        )
+        return CDSServiceInformation(services=[service_info])
+
+    def cds_service(self, id: str, request: CDSRequest) -> CDSResponse:
+        """
+        CDS service endpoint for FastAPI app, mounted to /cds-services/{id}
+
+        This method handles the execution of a specific CDS service. It validates the
+        service configuration, checks the input parameters, executes the service
+        function, and ensures the correct response type is returned.
+
+        Args:
+            id (str): The unique identifier of the CDS service to be executed.
+            request (CDSRequest): The request object containing the input data for the CDS service.
+
+        Returns:
+            CDSResponse: The response object containing the cards generated by the CDS service.
+
+        Raises:
+            AssertionError: If the service function is not properly configured.
+            TypeError: If the input or output types do not match the expected types.
+
+        Note:
+            This method performs several checks to ensure the integrity of the service:
+            1. Verifies that the service API is configured.
+            2. Validates the signature of the service function.
+            3. Ensures the service function accepts a CDSRequest as its first argument.
+            4. Verifies that the service function returns a CDSResponse.
+        """
+        # TODO: can register multiple services and fetch with id
+
+        # Check service_api
+        if self._service_api is None:
+            log.warning("CDS 'service_api' not configured, check class init.")
+            return CDSResponse(cards=[])
+
+        # Check that the first argument of self._service_api.func is of type CDSRequest
+        func_signature = inspect.signature(self._service_api.func)
+        params = list(func_signature.parameters.values())
+        if len(params) < 2:  # Only 'self' parameter
+            raise AssertionError(
+                "Service function must have at least one parameter besides 'self'"
+            )
+        first_param = params[1]  # Skip 'self'
+        if first_param.annotation == inspect.Parameter.empty:
+            log.warning(
+                "Service function parameter has no type annotation. Expected CDSRequest."
+            )
+        elif first_param.annotation != CDSRequest:
+            raise TypeError(
+                f"Expected first argument of service function to be CDSRequest, but got {first_param.annotation}"
+            )
+
+        # Call the service function
+        response = self._service_api.func(self, request)
+
+        # Check that response is of type CDSResponse
+        if not isinstance(response, CDSResponse):
+            raise TypeError(f"Expected CDSResponse, but got {type(response).__name__}")
+
+        return response
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ cds_discovery() + +

+ + +
+ +

CDS discovery endpoint for FastAPI app, should be mounted to /cds-services

+ +
+ Source code in healthchain/use_cases/cds.py +
def cds_discovery(self) -> CDSServiceInformation:
+    """
+    CDS discovery endpoint for FastAPI app, should be mounted to /cds-services
+    """
+    if self._client is None:
+        log.warning("CDS 'client' not configured, check class init.")
+        return CDSServiceInformation(services=[])
+
+    service_info = CDSService(
+        hook=self._client.workflow.value,
+        description="A test CDS hook service.",
+        id="1",
+    )
+    return CDSServiceInformation(services=[service_info])
+
+
+
+ +
+ +
+ + +

+ cds_service(id, request) + +

+ + +
+ +

CDS service endpoint for FastAPI app, mounted to /cds-services/{id}

+

This method handles the execution of a specific CDS service. It validates the +service configuration, checks the input parameters, executes the service +function, and ensures the correct response type is returned.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ id + +
+

The unique identifier of the CDS service to be executed.

+
+

+ + TYPE: + str + +

+
+ request + +
+

The request object containing the input data for the CDS service.

+
+

+ + TYPE: + CDSRequest + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CDSResponse + +
+

The response object containing the cards generated by the CDS service.

+
+

+ + TYPE: + CDSResponse + +

+
+ + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + AssertionError + + +
+

If the service function is not properly configured.

+
+
+ + TypeError + + +
+

If the input or output types do not match the expected types.

+
+
+ + +
+ Note +

This method performs several checks to ensure the integrity of the service: +1. Verifies that the service API is configured. +2. Validates the signature of the service function. +3. Ensures the service function accepts a CDSRequest as its first argument. +4. Verifies that the service function returns a CDSResponse.

+
+
+ Source code in healthchain/use_cases/cds.py +
def cds_service(self, id: str, request: CDSRequest) -> CDSResponse:
+    """
+    CDS service endpoint for FastAPI app, mounted to /cds-services/{id}
+
+    This method handles the execution of a specific CDS service. It validates the
+    service configuration, checks the input parameters, executes the service
+    function, and ensures the correct response type is returned.
+
+    Args:
+        id (str): The unique identifier of the CDS service to be executed.
+        request (CDSRequest): The request object containing the input data for the CDS service.
+
+    Returns:
+        CDSResponse: The response object containing the cards generated by the CDS service.
+
+    Raises:
+        AssertionError: If the service function is not properly configured.
+        TypeError: If the input or output types do not match the expected types.
+
+    Note:
+        This method performs several checks to ensure the integrity of the service:
+        1. Verifies that the service API is configured.
+        2. Validates the signature of the service function.
+        3. Ensures the service function accepts a CDSRequest as its first argument.
+        4. Verifies that the service function returns a CDSResponse.
+    """
+    # TODO: can register multiple services and fetch with id
+
+    # Check service_api
+    if self._service_api is None:
+        log.warning("CDS 'service_api' not configured, check class init.")
+        return CDSResponse(cards=[])
+
+    # Check that the first argument of self._service_api.func is of type CDSRequest
+    func_signature = inspect.signature(self._service_api.func)
+    params = list(func_signature.parameters.values())
+    if len(params) < 2:  # Only 'self' parameter
+        raise AssertionError(
+            "Service function must have at least one parameter besides 'self'"
+        )
+    first_param = params[1]  # Skip 'self'
+    if first_param.annotation == inspect.Parameter.empty:
+        log.warning(
+            "Service function parameter has no type annotation. Expected CDSRequest."
+        )
+    elif first_param.annotation != CDSRequest:
+        raise TypeError(
+            f"Expected first argument of service function to be CDSRequest, but got {first_param.annotation}"
+        )
+
+    # Call the service function
+    response = self._service_api.func(self, request)
+
+    # Check that response is of type CDSResponse
+    if not isinstance(response, CDSResponse):
+        raise TypeError(f"Expected CDSResponse, but got {type(response).__name__}")
+
+    return response
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ ClinicalDecisionSupportStrategy + + +

+ + +
+

+ Bases: BaseStrategy

+ + +

Handles the request construction and validation

+ + + + + + +
+ Source code in healthchain/use_cases/cds.py +
34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
class ClinicalDecisionSupportStrategy(BaseStrategy):
+    """
+    Handles the request construction and validation
+    """
+
+    def __init__(self) -> None:
+        self.api_protocol = ApiProtocol.rest
+        self.context_mapping = {
+            Workflow.order_select: OrderSelectContext,
+            Workflow.order_sign: OrderSignContext,
+            Workflow.patient_view: PatientViewContext,
+            Workflow.encounter_discharge: EncounterDischargeContext,
+        }
+
+    @validate_workflow(UseCaseMapping.ClinicalDecisionSupport)
+    def construct_request(self, data: CdsFhirData, workflow: Workflow) -> CDSRequest:
+        """
+        Constructs a HL7-compliant CDS request based on workflow.
+
+        Parameters:
+            data: FHIR data to be injected in request.
+            workflow (Workflow): The CDS hook name, e.g. patient-view.
+
+        Returns:
+            CDSRequest: A Pydantic model that wraps a CDS request for REST
+
+        Raises:
+            ValueError: If the workflow is invalid or the data does not validate properly.
+        """
+        log.debug(f"Constructing CDS request for {workflow.value} from {data}")
+
+        context_model = self.context_mapping.get(workflow, None)
+        if context_model is None:
+            raise ValueError(
+                f"Invalid workflow {workflow.value} or workflow model not implemented."
+            )
+        if not isinstance(data, CdsFhirData):
+            raise TypeError(
+                f"CDS clients must return data of type CdsFhirData, not {type(data)}"
+            )
+
+        # i feel like theres a better way to do this
+        request = CDSRequest(
+            hook=workflow.value,
+            context=context_model(**data.context),
+            prefetch=data.model_dump_prefetch(),
+        )
+
+        return request
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ construct_request(data, workflow) + +

+ + +
+ +

Constructs a HL7-compliant CDS request based on workflow.

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ data + +
+

FHIR data to be injected in request.

+
+

+ + TYPE: + CdsFhirData + +

+
+ workflow + +
+

The CDS hook name, e.g. patient-view.

+
+

+ + TYPE: + Workflow + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CDSRequest + +
+

A Pydantic model that wraps a CDS request for REST

+
+

+ + TYPE: + CDSRequest + +

+
+ + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If the workflow is invalid or the data does not validate properly.

+
+
+ +
+ Source code in healthchain/use_cases/cds.py +
48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
@validate_workflow(UseCaseMapping.ClinicalDecisionSupport)
+def construct_request(self, data: CdsFhirData, workflow: Workflow) -> CDSRequest:
+    """
+    Constructs a HL7-compliant CDS request based on workflow.
+
+    Parameters:
+        data: FHIR data to be injected in request.
+        workflow (Workflow): The CDS hook name, e.g. patient-view.
+
+    Returns:
+        CDSRequest: A Pydantic model that wraps a CDS request for REST
+
+    Raises:
+        ValueError: If the workflow is invalid or the data does not validate properly.
+    """
+    log.debug(f"Constructing CDS request for {workflow.value} from {data}")
+
+    context_model = self.context_mapping.get(workflow, None)
+    if context_model is None:
+        raise ValueError(
+            f"Invalid workflow {workflow.value} or workflow model not implemented."
+        )
+    if not isinstance(data, CdsFhirData):
+        raise TypeError(
+            f"CDS clients must return data of type CdsFhirData, not {type(data)}"
+        )
+
+    # i feel like theres a better way to do this
+    request = CDSRequest(
+        hook=workflow.value,
+        context=context_model(**data.context),
+        prefetch=data.model_dump_prefetch(),
+    )
+
+    return request
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CDSRequest + + +

+ + +
+

+ Bases: BaseModel

+ + +

A model representing the data structure for a CDS service call, triggered by specific hooks +within a healthcare application.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
hook +
+

The hook that triggered this CDS Service call. For example, 'patient-view'.

+
+

+ + TYPE: + str + +

+
hookInstance +
+

A universally unique identifier for this particular hook call.

+
+

+ + TYPE: + UUID + +

+
fhirServer +
+

The base URL of the CDS Client's FHIR server. This field is required if fhirAuthorization is provided.

+
+

+ + TYPE: + HttpUrl + +

+
fhirAuthorization +
+

Optional authorization details providing a bearer access token for FHIR resources.

+
+

+ + TYPE: + Optional[FhirAuthorization] + +

+
context +
+

Hook-specific contextual data required by the CDS service.

+
+

+ + TYPE: + Dict[str, Any] + +

+
prefetch +
+

Optional FHIR data that was prefetched by the CDS Client.

+
+

+ + TYPE: + Optional[Dict[str, Any]] + +

+
+

Documentation: https://cds-hooks.org/specification/current/#http-request_1

+ + + + + + +
+ Source code in healthchain/models/requests/cdsrequest.py +
19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
class CDSRequest(BaseModel):
+    """
+    A model representing the data structure for a CDS service call, triggered by specific hooks
+    within a healthcare application.
+
+    Attributes:
+        hook (str): The hook that triggered this CDS Service call. For example, 'patient-view'.
+        hookInstance (UUID): A universally unique identifier for this particular hook call.
+        fhirServer (HttpUrl): The base URL of the CDS Client's FHIR server. This field is required if `fhirAuthorization` is provided.
+        fhirAuthorization (Optional[FhirAuthorization]): Optional authorization details providing a bearer access token for FHIR resources.
+        context (Dict[str, Any]): Hook-specific contextual data required by the CDS service.
+        prefetch (Optional[Dict[str, Any]]): Optional FHIR data that was prefetched by the CDS Client.
+
+    Documentation: https://cds-hooks.org/specification/current/#http-request_1
+    """
+
+    hook: str
+    hookInstance: str = Field(default_factory=id_generator.generate_random_uuid)
+    context: BaseHookContext
+    fhirServer: Optional[HttpUrl] = None
+    fhirAuthorization: Optional[FHIRAuthorization] = (
+        None  # TODO: note this is required if fhirserver is given
+    )
+    prefetch: Optional[Dict[str, Any]] = (
+        None  # fhir resource is passed either thru prefetched template of fhir server
+    )
+    extension: Optional[List[Dict[str, Any]]] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ Action + + +

+ + +
+

+ Bases: BaseModel

+ + +

Within a suggestion, all actions are logically AND'd together, such that a user selecting a +suggestion selects all of the actions within it. When a suggestion contains multiple actions, +the actions SHOULD be processed as per FHIR's rules for processing transactions with the CDS +Client's fhirServer as the base url for the inferred full URL of the transaction bundle entries.

+

https://cds-hooks.org/specification/current/#action

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
class Action(BaseModel):
+    """
+    Within a suggestion, all actions are logically AND'd together, such that a user selecting a
+    suggestion selects all of the actions within it. When a suggestion contains multiple actions,
+    the actions SHOULD be processed as per FHIR's rules for processing transactions with the CDS
+    Client's fhirServer as the base url for the inferred full URL of the transaction bundle entries.
+
+    https://cds-hooks.org/specification/current/#action
+    """
+
+    type: ActionTypeEnum
+    description: str
+    resource: Optional[Dict] = None
+    resourceId: Optional[str] = None
+
+    @model_validator(mode="after")
+    def validate_action_type(self) -> Self:
+        if self.type in [ActionTypeEnum.create, ActionTypeEnum.update]:
+            assert (
+                self.resource
+            ), f"'resource' must be provided when type is '{self.type.value}'"
+        else:
+            assert (
+                self.resourceId
+            ), f"'resourceId' must be provided when type is '{self.type.value}'"
+
+        return self
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ ActionTypeEnum + + +

+ + +
+

+ Bases: str, Enum

+ + +

The type of action being performed

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
33
+34
+35
+36
+37
+38
+39
+40
class ActionTypeEnum(str, Enum):
+    """
+    The type of action being performed
+    """
+
+    create = "create"
+    update = "update"
+    delete = "delete"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ CDSResponse + + +

+ + +
+

+ Bases: BaseModel

+ + +

Represents the response from a CDS service.

+

This class models the structure of a CDS Hooks response, which includes +cards for displaying information or suggestions to the user, and optional +system actions that can be executed automatically.

+ + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
cards +
+

A list of Card objects to be displayed to the end user. +Default is an empty list.

+
+

+ + TYPE: + List[Card] + +

+
systemActions +
+

A list of Action objects representing +actions that the CDS Client should execute as part of performing +the decision support requested. This field is optional.

+
+

+ + TYPE: + Optional[List[Action]] + +

+
+

For more information, see: +https://cds-hooks.org/specification/current/#cds-service-response

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
class CDSResponse(BaseModel):
+    """
+    Represents the response from a CDS service.
+
+    This class models the structure of a CDS Hooks response, which includes
+    cards for displaying information or suggestions to the user, and optional
+    system actions that can be executed automatically.
+
+    Attributes:
+        cards (List[Card]): A list of Card objects to be displayed to the end user.
+            Default is an empty list.
+        systemActions (Optional[List[Action]]): A list of Action objects representing
+            actions that the CDS Client should execute as part of performing
+            the decision support requested. This field is optional.
+
+    For more information, see:
+    https://cds-hooks.org/specification/current/#cds-service-response
+    """
+
+    cards: List[Card] = []
+    systemActions: Optional[List[Action]] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ Card + + +

+ + +
+

+ Bases: BaseModel

+ + +

Cards can provide a combination of information (for reading), suggested actions +(to be applied if a user selects them), and links (to launch an app if the user selects them). +The CDS Client decides how to display cards, but this specification recommends displaying suggestions +using buttons, and links using underlined text.

+

https://cds-hooks.org/specification/current/#card-attributes

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
class Card(BaseModel):
+    """
+    Cards can provide a combination of information (for reading), suggested actions
+    (to be applied if a user selects them), and links (to launch an app if the user selects them).
+    The CDS Client decides how to display cards, but this specification recommends displaying suggestions
+    using buttons, and links using underlined text.
+
+    https://cds-hooks.org/specification/current/#card-attributes
+    """
+
+    summary: str = Field(..., max_length=140)
+    indicator: IndicatorEnum
+    source: Source
+    uuid: Optional[str] = None
+    detail: Optional[str] = None
+    suggestions: Optional[List[Suggestion]] = None
+    selectionBehavior: Optional[SelectionBehaviorEnum] = None
+    overrideReasons: Optional[List[SimpleCoding]] = None
+    links: Optional[List[Link]] = None
+
+    @model_validator(mode="after")
+    def validate_suggestions(self) -> Self:
+        if self.suggestions is not None:
+            assert self.selectionBehavior, f"'selectionBehavior' must be given if 'suggestions' is present! Choose from {[v for v in SelectionBehaviorEnum.value]}"
+        return self
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ IndicatorEnum + + +

+ + +
+

+ Bases: str, Enum

+ + +

Urgency/importance of what Card conveys. +Allowed values, in order of increasing urgency, are: info, warning, critical. +The CDS Client MAY use this field to help make UI display decisions such as sort order or coloring.

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
class IndicatorEnum(str, Enum):
+    """
+    Urgency/importance of what Card conveys.
+    Allowed values, in order of increasing urgency, are: info, warning, critical.
+    The CDS Client MAY use this field to help make UI display decisions such as sort order or coloring.
+    """
+
+    info = "info"
+    warning = "warning"
+    critical = "critical"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + + + + +
+

+ Bases: BaseModel

+ + +
    +
  • +

    CDS Client support for appContext requires additional coordination with the authorization +server that is not described or specified in CDS Hooks nor SMART.

    +
  • +
  • +

    Autolaunchable is experimental

    +
  • +
+

https://cds-hooks.org/specification/current/#link

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
class Link(BaseModel):
+    """
+    * CDS Client support for appContext requires additional coordination with the authorization
+    server that is not described or specified in CDS Hooks nor SMART.
+
+    * Autolaunchable is experimental
+
+    https://cds-hooks.org/specification/current/#link
+    """
+
+    label: str
+    url: HttpUrl
+    type: LinkTypeEnum
+    appContext: Optional[str] = None
+    autoLaunchable: Optional[bool]
+
+    @model_validator(mode="after")
+    def validate_link(self) -> Self:
+        if self.appContext:
+            assert (
+                self.type == LinkTypeEnum.smart
+            ), "'type' must be 'smart' for appContext to be valued."
+
+        return self
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ LinkTypeEnum + + +

+ + +
+

+ Bases: str, Enum

+ + +

The type of the given URL. There are two possible values for this field. +A type of absolute indicates that the URL is absolute and should be treated as-is. +A type of smart indicates that the URL is a SMART app launch URL and the CDS Client +should ensure the SMART app launch URL is populated with the appropriate SMART +launch parameters.

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
class LinkTypeEnum(str, Enum):
+    """
+    The type of the given URL. There are two possible values for this field.
+    A type of absolute indicates that the URL is absolute and should be treated as-is.
+    A type of smart indicates that the URL is a SMART app launch URL and the CDS Client
+    should ensure the SMART app launch URL is populated with the appropriate SMART
+    launch parameters.
+    """
+
+    absolute = "absolute"
+    smart = "smart"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ SelectionBehaviorEnum + + +

+ + +
+

+ Bases: str, Enum

+ + +

Describes the intended selection behavior of the suggestions in the card. +Allowed values are: at-most-one, indicating that the user may choose none or +at most one of the suggestions; any, indicating that the end user may choose +any number of suggestions including none of them and all of them. +CDS Clients that do not understand the value MUST treat the card as an error.

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
class SelectionBehaviorEnum(str, Enum):
+    """
+    Describes the intended selection behavior of the suggestions in the card.
+    Allowed values are: at-most-one, indicating that the user may choose none or
+    at most one of the suggestions; any, indicating that the end user may choose
+    any number of suggestions including none of them and all of them.
+    CDS Clients that do not understand the value MUST treat the card as an error.
+    """
+
+    at_most_one = "at-most-one"
+    any = "any"
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ SimpleCoding + + +

+ + +
+

+ Bases: BaseModel

+ + +

The Coding data type captures the concept of a code. This coding type is a standalone data type +in CDS Hooks modeled after a trimmed down version of the FHIR Coding data type.

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
82
+83
+84
+85
+86
+87
+88
+89
+90
class SimpleCoding(BaseModel):
+    """
+    The Coding data type captures the concept of a code. This coding type is a standalone data type
+    in CDS Hooks modeled after a trimmed down version of the FHIR Coding data type.
+    """
+
+    code: str
+    system: str
+    display: Optional[str] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ Source + + +

+ + +
+

+ Bases: BaseModel

+ + +

Grouping structure for the Source of the information displayed on this card. +The source should be the primary source of guidance for the decision support Card represents.

+

https://cds-hooks.org/specification/current/#source

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
class Source(BaseModel):
+    """
+    Grouping structure for the Source of the information displayed on this card.
+    The source should be the primary source of guidance for the decision support Card represents.
+
+    https://cds-hooks.org/specification/current/#source
+    """
+
+    label: str
+    url: Optional[HttpUrl] = None
+    icon: Optional[HttpUrl] = None
+    topic: Optional[SimpleCoding] = None
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ Suggestion + + +

+ + +
+

+ Bases: BaseModel

+ + +

Allows a service to suggest a set of changes in the context of the current activity +(e.g. changing the dose of a medication currently being prescribed, for the order-sign activity). +If suggestions are present, selectionBehavior MUST also be provided.

+

https://cds-hooks.org/specification/current/#suggestion

+ + + + + + +
+ Source code in healthchain/models/responses/cdsresponse.py +
class Suggestion(BaseModel):
+    """
+    Allows a service to suggest a set of changes in the context of the current activity
+    (e.g. changing the dose of a medication currently being prescribed, for the order-sign activity).
+    If suggestions are present, selectionBehavior MUST also be provided.
+
+    https://cds-hooks.org/specification/current/#suggestion
+    """
+
+    label: str
+    uuid: Optional[str] = None
+    isRecommended: Optional[bool]
+    actions: Optional[List[Action]] = []
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ ClinicalDocumentation + + +

+ + +
+

+ Bases: BaseUseCase

+ + +

Implements EHR backend strategy for clinical documentation (NoteReader)

+

This class represents the backend strategy for clinical documentation using the NoteReader system. +It inherits from the BaseUseCase class and provides methods for processing NoteReader documents.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ATTRIBUTEDESCRIPTION
service_api +
+

The service API method to be used for processing the documents.

+
+

+ + TYPE: + Optional[APIMethod] + +

+
service_config +
+

The configuration for the service.

+
+

+ + TYPE: + Optional[Dict] + +

+
service +
+

The service to be used for processing the documents.

+
+

+ + TYPE: + Optional[Service] + +

+
client +
+

The client to be used for communication with the service.

+
+

+ + TYPE: + Optional[BaseClient] + +

+
overwrite +
+

Whether to overwrite existing data in the CDA document.

+
+

+ + TYPE: + bool + +

+
+ + + + + + +
+ Source code in healthchain/use_cases/clindoc.py +
class ClinicalDocumentation(BaseUseCase):
+    """
+    Implements EHR backend strategy for clinical documentation (NoteReader)
+
+    This class represents the backend strategy for clinical documentation using the NoteReader system.
+    It inherits from the `BaseUseCase` class and provides methods for processing NoteReader documents.
+
+    Attributes:
+        service_api (Optional[APIMethod]): The service API method to be used for processing the documents.
+        service_config (Optional[Dict]): The configuration for the service.
+        service (Optional[Service]): The service to be used for processing the documents.
+        client (Optional[BaseClient]): The client to be used for communication with the service.
+        overwrite (bool): Whether to overwrite existing data in the CDA document.
+
+    """
+
+    def __init__(
+        self,
+        service_api: Optional[APIMethod] = None,
+        service_config: Optional[Dict] = None,
+        service: Optional[Service] = None,
+        client: Optional[BaseClient] = None,
+    ) -> None:
+        super().__init__(
+            service_api=service_api,
+            service_config=service_config,
+            service=service,
+            client=client,
+        )
+        self._type = UseCaseType.clindoc
+        self._strategy = ClinicalDocumentationStrategy()
+        self._endpoints = {
+            "service_mount": Endpoint(
+                path="/notereader/",
+                method="POST",
+                function=self.process_notereader_document,
+                api_protocol="SOAP",
+            )
+        }
+        self.overwrite: bool = False
+
+    @property
+    def description(self) -> str:
+        return "Clinical documentation (NoteReader)"
+
+    @property
+    def type(self) -> UseCaseType:
+        return self._type
+
+    @property
+    def strategy(self) -> BaseStrategy:
+        return self._strategy
+
+    @property
+    def endpoints(self) -> Dict[str, Endpoint]:
+        return self._endpoints
+
+    def process_notereader_document(self, request: CdaRequest) -> CdaResponse:
+        """
+        Process the NoteReader document using the configured service API.
+
+        This method handles the execution of the NoteReader service. It validates the
+        service configuration, checks the input parameters, executes the service
+        function, and ensures the correct response type is returned.
+
+        Args:
+            request (CdaRequest): The request object containing the CDA document to be processed.
+
+        Returns:
+            CdaResponse: The response object containing the processed CDA document.
+
+        Raises:
+            AssertionError: If the service function is not properly configured.
+            TypeError: If the output type does not match the expected CdaResponse type.
+
+        Note:
+            This method performs several checks to ensure the integrity of the service:
+            1. Verifies that the service API is configured.
+            2. Validates the signature of the service function.
+            3. Ensures the service function accepts a CdaRequest as its argument.
+            4. Verifies that the service function returns a CdaResponse.
+        """
+        # Check service_api
+        if self._service_api is None:
+            log.warning("'service_api' not configured, check class init.")
+            return CdaResponse(document="")
+
+        # Check service function signature
+        signature = inspect.signature(self._service_api.func)
+        params = list(signature.parameters.values())
+        if len(params) < 2:  # Only 'self' parameter
+            raise AssertionError(
+                "Service function must have at least one parameter besides 'self'"
+            )
+        first_param = params[1]  # Skip 'self'
+        if first_param.annotation == inspect.Parameter.empty:
+            log.warning(
+                "Service function parameter has no type annotation. Expected CdaRequest."
+            )
+        elif first_param.annotation != CdaRequest:
+            raise TypeError(
+                f"Expected first argument of service function to be CdaRequest, but got {first_param.annotation}"
+            )
+
+        # Call the service function
+        response = self._service_api.func(self, request)
+
+        # Check return type
+        if not isinstance(response, CdaResponse):
+            raise TypeError(
+                f"Expected return type CdaResponse, got {type(response)} instead."
+            )
+
+        return response
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ process_notereader_document(request) + +

+ + +
+ +

Process the NoteReader document using the configured service API.

+

This method handles the execution of the NoteReader service. It validates the +service configuration, checks the input parameters, executes the service +function, and ensures the correct response type is returned.

+ + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ request + +
+

The request object containing the CDA document to be processed.

+
+

+ + TYPE: + CdaRequest + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CdaResponse + +
+

The response object containing the processed CDA document.

+
+

+ + TYPE: + CdaResponse + +

+
+ + + + + + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + AssertionError + + +
+

If the service function is not properly configured.

+
+
+ + TypeError + + +
+

If the output type does not match the expected CdaResponse type.

+
+
+ + +
+ Note +

This method performs several checks to ensure the integrity of the service: +1. Verifies that the service API is configured. +2. Validates the signature of the service function. +3. Ensures the service function accepts a CdaRequest as its argument. +4. Verifies that the service function returns a CdaResponse.

+
+
+ Source code in healthchain/use_cases/clindoc.py +
def process_notereader_document(self, request: CdaRequest) -> CdaResponse:
+    """
+    Process the NoteReader document using the configured service API.
+
+    This method handles the execution of the NoteReader service. It validates the
+    service configuration, checks the input parameters, executes the service
+    function, and ensures the correct response type is returned.
+
+    Args:
+        request (CdaRequest): The request object containing the CDA document to be processed.
+
+    Returns:
+        CdaResponse: The response object containing the processed CDA document.
+
+    Raises:
+        AssertionError: If the service function is not properly configured.
+        TypeError: If the output type does not match the expected CdaResponse type.
+
+    Note:
+        This method performs several checks to ensure the integrity of the service:
+        1. Verifies that the service API is configured.
+        2. Validates the signature of the service function.
+        3. Ensures the service function accepts a CdaRequest as its argument.
+        4. Verifies that the service function returns a CdaResponse.
+    """
+    # Check service_api
+    if self._service_api is None:
+        log.warning("'service_api' not configured, check class init.")
+        return CdaResponse(document="")
+
+    # Check service function signature
+    signature = inspect.signature(self._service_api.func)
+    params = list(signature.parameters.values())
+    if len(params) < 2:  # Only 'self' parameter
+        raise AssertionError(
+            "Service function must have at least one parameter besides 'self'"
+        )
+    first_param = params[1]  # Skip 'self'
+    if first_param.annotation == inspect.Parameter.empty:
+        log.warning(
+            "Service function parameter has no type annotation. Expected CdaRequest."
+        )
+    elif first_param.annotation != CdaRequest:
+        raise TypeError(
+            f"Expected first argument of service function to be CdaRequest, but got {first_param.annotation}"
+        )
+
+    # Call the service function
+    response = self._service_api.func(self, request)
+
+    # Check return type
+    if not isinstance(response, CdaResponse):
+        raise TypeError(
+            f"Expected return type CdaResponse, got {type(response)} instead."
+        )
+
+    return response
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ ClinicalDocumentationStrategy + + +

+ + +
+

+ Bases: BaseStrategy

+ + +

Handles the request construction and validation of a NoteReader CDA file

+ + + + + + +
+ Source code in healthchain/use_cases/clindoc.py +
26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
class ClinicalDocumentationStrategy(BaseStrategy):
+    """
+    Handles the request construction and validation of a NoteReader CDA file
+    """
+
+    def __init__(self) -> None:
+        self.api_protocol: ApiProtocol = ApiProtocol.soap
+        self.soap_envelope: Dict = self._load_soap_envelope()
+
+    def _load_soap_envelope(self):
+        data = pkgutil.get_data("healthchain", "templates/soap_envelope.xml")
+        return xmltodict.parse(data.decode("utf-8"))
+
+    def construct_cda_xml_document(self):
+        """
+        This function should wrap FHIR data from CcdFhirData into a template CDA file (dep. vendor
+        TODO: implement this function
+        """
+        pass
+
+    @validate_workflow(UseCaseMapping.ClinicalDocumentation)
+    def construct_request(self, data: CcdData, workflow: Workflow) -> CdaRequest:
+        """
+        Constructs a CDA request for clinical documentation use cases (NoteReader)
+
+        Parameters:
+            data: CDA data to be injected in the request
+            workflow (Workflow): The NoteReader workflow type, e.g. notereader-sign-inpatient
+
+        Returns:
+            CdaRequest: A Pydantic model that wraps CDA data for SOAP request
+
+        Raises:
+            ValueError: If the workflow is invalid or the data does not validate properly.
+        """
+        # TODO: handle converting fhir data from data generator to cda
+        # TODO: handle different workflows
+        if data.cda_xml is not None:
+            # Encode the cda xml in base64
+            encoded_xml = base64.b64encode(data.cda_xml.encode("utf-8")).decode("utf-8")
+
+            # Make a copy of the SOAP envelope template
+            soap_envelope = self.soap_envelope.copy()
+
+            # Insert encoded cda in the Document section
+            if not insert_at_key(soap_envelope, "urn:Document", encoded_xml):
+                raise ValueError(
+                    "Key 'urn:Document' missing from SOAP envelope template!"
+                )
+            request = CdaRequest.from_dict(soap_envelope)
+
+            return request
+        else:
+            log.warning(
+                "Data generation methods for CDA documents not implemented yet!"
+            )
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ construct_cda_xml_document() + +

+ + +
+ +

This function should wrap FHIR data from CcdFhirData into a template CDA file (dep. vendor +TODO: implement this function

+ +
+ Source code in healthchain/use_cases/clindoc.py +
39
+40
+41
+42
+43
+44
def construct_cda_xml_document(self):
+    """
+    This function should wrap FHIR data from CcdFhirData into a template CDA file (dep. vendor
+    TODO: implement this function
+    """
+    pass
+
+
+
+ +
+ +
+ + +

+ construct_request(data, workflow) + +

+ + +
+ +

Constructs a CDA request for clinical documentation use cases (NoteReader)

+ + + + + + + + + + + + + + + + + + + +
PARAMETERDESCRIPTION
+ data + +
+

CDA data to be injected in the request

+
+

+ + TYPE: + CcdData + +

+
+ workflow + +
+

The NoteReader workflow type, e.g. notereader-sign-inpatient

+
+

+ + TYPE: + Workflow + +

+
+ + + + + + + + + + + + + + + +
RETURNSDESCRIPTION
+ CdaRequest + +
+

A Pydantic model that wraps CDA data for SOAP request

+
+

+ + TYPE: + CdaRequest + +

+
+ + + + + + + + + + + + + + + +
RAISESDESCRIPTION
+ + ValueError + + +
+

If the workflow is invalid or the data does not validate properly.

+
+
+ +
+ Source code in healthchain/use_cases/clindoc.py +
46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
@validate_workflow(UseCaseMapping.ClinicalDocumentation)
+def construct_request(self, data: CcdData, workflow: Workflow) -> CdaRequest:
+    """
+    Constructs a CDA request for clinical documentation use cases (NoteReader)
+
+    Parameters:
+        data: CDA data to be injected in the request
+        workflow (Workflow): The NoteReader workflow type, e.g. notereader-sign-inpatient
+
+    Returns:
+        CdaRequest: A Pydantic model that wraps CDA data for SOAP request
+
+    Raises:
+        ValueError: If the workflow is invalid or the data does not validate properly.
+    """
+    # TODO: handle converting fhir data from data generator to cda
+    # TODO: handle different workflows
+    if data.cda_xml is not None:
+        # Encode the cda xml in base64
+        encoded_xml = base64.b64encode(data.cda_xml.encode("utf-8")).decode("utf-8")
+
+        # Make a copy of the SOAP envelope template
+        soap_envelope = self.soap_envelope.copy()
+
+        # Insert encoded cda in the Document section
+        if not insert_at_key(soap_envelope, "urn:Document", encoded_xml):
+            raise ValueError(
+                "Key 'urn:Document' missing from SOAP envelope template!"
+            )
+        request = CdaRequest.from_dict(soap_envelope)
+
+        return request
+    else:
+        log.warning(
+            "Data generation methods for CDA documents not implemented yet!"
+        )
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdaRequest + + +

+ + +
+

+ Bases: BaseModel

+ + + + + + + +
+ Source code in healthchain/models/requests/cdarequest.py +
13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
class CdaRequest(BaseModel):
+    document: str
+
+    @classmethod
+    def from_dict(cls, data: Dict):
+        """
+        Loads data from dict (xmltodict format)
+        """
+        return cls(document=xmltodict.unparse(data))
+
+    def model_dump(self, *args, **kwargs) -> Dict:
+        """
+        Dumps document as dict with xmltodict
+        """
+        return xmltodict.parse(self.document)
+
+    def model_dump_xml(self, *args, **kwargs) -> str:
+        """
+        Decodes and dumps document as an xml string
+        """
+        xml_dict = xmltodict.parse(self.document)
+        document = search_key(xml_dict, "urn:Document")
+        if document is None:
+            log.warning("Coudln't find document under namespace 'urn:Document")
+            return ""
+        cda = base64.b64decode(document).decode("UTF-8")
+
+        return cda
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ from_dict(data) + + + classmethod + + +

+ + +
+ +

Loads data from dict (xmltodict format)

+ +
+ Source code in healthchain/models/requests/cdarequest.py +
16
+17
+18
+19
+20
+21
@classmethod
+def from_dict(cls, data: Dict):
+    """
+    Loads data from dict (xmltodict format)
+    """
+    return cls(document=xmltodict.unparse(data))
+
+
+
+ +
+ +
+ + +

+ model_dump(*args, **kwargs) + +

+ + +
+ +

Dumps document as dict with xmltodict

+ +
+ Source code in healthchain/models/requests/cdarequest.py +
23
+24
+25
+26
+27
def model_dump(self, *args, **kwargs) -> Dict:
+    """
+    Dumps document as dict with xmltodict
+    """
+    return xmltodict.parse(self.document)
+
+
+
+ +
+ +
+ + +

+ model_dump_xml(*args, **kwargs) + +

+ + +
+ +

Decodes and dumps document as an xml string

+ +
+ Source code in healthchain/models/requests/cdarequest.py +
29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
def model_dump_xml(self, *args, **kwargs) -> str:
+    """
+    Decodes and dumps document as an xml string
+    """
+    xml_dict = xmltodict.parse(self.document)
+    document = search_key(xml_dict, "urn:Document")
+    if document is None:
+        log.warning("Coudln't find document under namespace 'urn:Document")
+        return ""
+    cda = base64.b64decode(document).decode("UTF-8")
+
+    return cda
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + +

+ CdaResponse + + +

+ + +
+

+ Bases: BaseModel

+ + + + + + + +
+ Source code in healthchain/models/responses/cdaresponse.py +
14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
class CdaResponse(BaseModel):
+    document: str
+    error: Optional[str] = None
+
+    @classmethod
+    def from_dict(cls, data: Dict):
+        """
+        Loads data from dict (xmltodict format)
+        """
+        return cls(document=xmltodict.unparse(data))
+
+    def model_dump(self, *args, **kwargs) -> Dict:
+        """
+        Dumps document as dict with xmltodict
+        """
+        return xmltodict.parse(self.document)
+
+    def model_dump_xml(self, *args, **kwargs) -> str:
+        """
+        Decodes and dumps document as an xml string
+        """
+        xml_dict = xmltodict.parse(self.document)
+        document = search_key(xml_dict, "tns:Document")
+        if document is None:
+            log.warning("Couldn't find document under namespace 'tns:Document")
+            return ""
+
+        cda = base64.b64decode(document).decode("UTF-8")
+
+        return cda
+
+
+ + + +
+ + + + + + + + + +
+ + +

+ from_dict(data) + + + classmethod + + +

+ + +
+ +

Loads data from dict (xmltodict format)

+ +
+ Source code in healthchain/models/responses/cdaresponse.py +
18
+19
+20
+21
+22
+23
@classmethod
+def from_dict(cls, data: Dict):
+    """
+    Loads data from dict (xmltodict format)
+    """
+    return cls(document=xmltodict.unparse(data))
+
+
+
+ +
+ +
+ + +

+ model_dump(*args, **kwargs) + +

+ + +
+ +

Dumps document as dict with xmltodict

+ +
+ Source code in healthchain/models/responses/cdaresponse.py +
25
+26
+27
+28
+29
def model_dump(self, *args, **kwargs) -> Dict:
+    """
+    Dumps document as dict with xmltodict
+    """
+    return xmltodict.parse(self.document)
+
+
+
+ +
+ +
+ + +

+ model_dump_xml(*args, **kwargs) + +

+ + +
+ +

Decodes and dumps document as an xml string

+ +
+ Source code in healthchain/models/responses/cdaresponse.py +
31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
def model_dump_xml(self, *args, **kwargs) -> str:
+    """
+    Decodes and dumps document as an xml string
+    """
+    xml_dict = xmltodict.parse(self.document)
+    document = search_key(xml_dict, "tns:Document")
+    if document is None:
+        log.warning("Couldn't find document under namespace 'tns:Document")
+        return ""
+
+    cda = base64.b64decode(document).decode("UTF-8")
+
+    return cda
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/assets/_mkdocstrings.css b/assets/_mkdocstrings.css new file mode 100644 index 0000000..b500381 --- /dev/null +++ b/assets/_mkdocstrings.css @@ -0,0 +1,143 @@ + +/* Avoid breaking parameter names, etc. in table cells. */ +.doc-contents td code { + word-break: normal !important; +} + +/* No line break before first paragraph of descriptions. */ +.doc-md-description, +.doc-md-description>p:first-child { + display: inline; +} + +/* Max width for docstring sections tables. */ +.doc .md-typeset__table, +.doc .md-typeset__table table { + display: table !important; + width: 100%; +} + +.doc .md-typeset__table tr { + display: table-row; +} + +/* Defaults in Spacy table style. */ +.doc-param-default { + float: right; +} + +/* Parameter headings must be inline, not blocks. */ +.doc-heading-parameter { + display: inline; +} + +/* Prefer space on the right, not the left of parameter permalinks. */ +.doc-heading-parameter .headerlink { + margin-left: 0 !important; + margin-right: 0.2rem; +} + +/* Backward-compatibility: docstring section titles in bold. */ +.doc-section-title { + font-weight: bold; +} + +/* Symbols in Navigation and ToC. */ +:root, :host, +[data-md-color-scheme="default"] { + --doc-symbol-parameter-fg-color: #df50af; + --doc-symbol-attribute-fg-color: #953800; + --doc-symbol-function-fg-color: #8250df; + --doc-symbol-method-fg-color: #8250df; + --doc-symbol-class-fg-color: #0550ae; + --doc-symbol-module-fg-color: #5cad0f; + + --doc-symbol-parameter-bg-color: #df50af1a; + --doc-symbol-attribute-bg-color: #9538001a; + --doc-symbol-function-bg-color: #8250df1a; + --doc-symbol-method-bg-color: #8250df1a; + --doc-symbol-class-bg-color: #0550ae1a; + --doc-symbol-module-bg-color: #5cad0f1a; +} + +[data-md-color-scheme="slate"] { + --doc-symbol-parameter-fg-color: #ffa8cc; + --doc-symbol-attribute-fg-color: #ffa657; + --doc-symbol-function-fg-color: #d2a8ff; + --doc-symbol-method-fg-color: #d2a8ff; + --doc-symbol-class-fg-color: #79c0ff; + --doc-symbol-module-fg-color: #baff79; + + --doc-symbol-parameter-bg-color: #ffa8cc1a; + --doc-symbol-attribute-bg-color: #ffa6571a; + --doc-symbol-function-bg-color: #d2a8ff1a; + --doc-symbol-method-bg-color: #d2a8ff1a; + --doc-symbol-class-bg-color: #79c0ff1a; + --doc-symbol-module-bg-color: #baff791a; +} + +code.doc-symbol { + border-radius: .1rem; + font-size: .85em; + padding: 0 .3em; + font-weight: bold; +} + +code.doc-symbol-parameter { + color: var(--doc-symbol-parameter-fg-color); + background-color: var(--doc-symbol-parameter-bg-color); +} + +code.doc-symbol-parameter::after { + content: "param"; +} + +code.doc-symbol-attribute { + color: var(--doc-symbol-attribute-fg-color); + background-color: var(--doc-symbol-attribute-bg-color); +} + +code.doc-symbol-attribute::after { + content: "attr"; +} + +code.doc-symbol-function { + color: var(--doc-symbol-function-fg-color); + background-color: var(--doc-symbol-function-bg-color); +} + +code.doc-symbol-function::after { + content: "func"; +} + +code.doc-symbol-method { + color: var(--doc-symbol-method-fg-color); + background-color: var(--doc-symbol-method-bg-color); +} + +code.doc-symbol-method::after { + content: "meth"; +} + +code.doc-symbol-class { + color: var(--doc-symbol-class-fg-color); + background-color: var(--doc-symbol-class-bg-color); +} + +code.doc-symbol-class::after { + content: "class"; +} + +code.doc-symbol-module { + color: var(--doc-symbol-module-fg-color); + background-color: var(--doc-symbol-module-bg-color); +} + +code.doc-symbol-module::after { + content: "mod"; +} + +.doc-signature .autorefs { + color: inherit; + border-bottom: 1px dotted currentcolor; +} diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/images/healthchain_logo.png b/assets/images/healthchain_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc13a367f88dc982d9340dd22a9e53b55e0cf57 GIT binary patch literal 373000 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

E=$*Vb415sjEH0rD^G0L^et?bM+hp7cwIC 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 literal 0 HcmV?d00001 diff --git a/assets/javascripts/bundle.83f73b43.min.js b/assets/javascripts/bundle.83f73b43.min.js new file mode 100644 index 0000000..43d8b70 --- /dev/null +++ b/assets/javascripts/bundle.83f73b43.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Di=Object.getOwnPropertyDescriptor;var Vi=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,Ni=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var zi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Vi(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Di(t,n))||o.enumerable});return e};var Mt=(e,t,r)=>(r=e!=null?Wi(Ni(e)):{},zi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function te(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,te())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var qr=xr((hy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof It=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof It=="object"?It.ClipboardJS=r():t.ClipboardJS=r()})(It,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(V){try{return document.execCommand(V)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(V){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=V,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var D=f()(F);return u("copy"),F.remove(),D},te=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=te;function k(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(V)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,D=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:D});if(Y)return F==="cut"?y(Y):J(Y,{container:D})},qe=ft;function Fe(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(V)}function ki(V,A){if(!(V instanceof A))throw new TypeError("Cannot call a class as a function")}function no(V,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof D.action=="function"?D.action:this.defaultAction,this.target=typeof D.target=="function"?D.target:this.defaultTarget,this.text=typeof D.text=="function"?D.text:this.defaultText,this.container=Fe(D.container)==="object"?D.container:document.body}},{key:"listenClick",value:function(D){var Y=this;this.listener=c()(D,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(D){var Y=D.delegateTarget||D.currentTarget,$e=this.action(Y)||"copy",Dt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Dt?"success":"error",{action:$e,text:Dt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(D){return vr("action",D)}},{key:"defaultTarget",value:function(D){var Y=vr("target",D);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(D){return vr("text",D)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(D){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(D,Y)}},{key:"cut",value:function(D){return y(D)}},{key:"isSupported",value:function(){var D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof D=="string"?[D]:D,$e=!!document.queryCommandSupported;return Y.forEach(function(Dt){$e=$e&&!!document.queryCommandSupported(Dt)}),$e}}]),M}(s()),Ui=Fi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],N(i)),N(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Ue.EMPTY;function qt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t}(j);var To=function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t}(g);var _r=function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var At={now:function(){return(At.delegate||Date).now()},delegate:void 0};var Ct=function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=At);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(gt);var Lo=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(yt);var kr=new Lo(Oo);var Mo=function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(gt);var _o=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Yt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Yt(Hr(e))?e.pop():void 0}function Bt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Gt(e){return H(e==null?void 0:e.then)}function Jt(e){return H(e[bt])}function Xt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Zt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var er=Zi();function tr(e){return H(e==null?void 0:e[er])}function rr(e){return fo(this,arguments,function(){var r,o,n,i;return Nt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function or(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Jt(e))return ea(e);if(xt(e))return ta(e);if(Gt(e))return ra(e);if(Xt(e))return Ao(e);if(tr(e))return oa(e);if(or(e))return na(e)}throw Zt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?De(t):Qo(function(){return new ir}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},te=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;te(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(te,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(te,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function $t(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?Ht(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function Tt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?Tt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function St(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Ve(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>Ve(e)),Q(Ve(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ne(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return Ne(e).pipe(m(({y:r})=>{let o=ce(e),n=St(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function ze(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function Pt(e){let t=matchMedia(e);return ar(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function Nr(e,t){return e.pipe(v(r=>r?t():S))}function zr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return zr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return z([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(ee("size")),n=z([o,r]).pipe(m(()=>Ve(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),Ot=JSON.parse(Ca.textContent);Ot.base=`${new URL(Ot.base,ye())}`;function xe(){return Ot}function B(e){return Ot.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?Ot.translations[e].replace("#",t.toString()):Ot.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Rt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Mt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=z([et(e),$t(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(Ne),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>z([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(Ht(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());z([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>$t(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return z([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),z([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>z([tn(e),Ne(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Da(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Da(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Mt(Br());var Va=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function Na(e){return ge(e).pipe(m(({width:t})=>({scrollable:St(e).width>t})),ee("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Va++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),Na(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function za(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),za(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?Tt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Dn=x("table");function Vn(e){return e.replaceWith(Dn),Dn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function Nn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));z([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=Ve(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ne(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=St(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function zn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Vn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>Nn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?Ne(o):I({x:0,y:0}),i=O(et(t),$t(t)).pipe(K());return z([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=Ve(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Rt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=ze("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>z([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(ee("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),ee("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ee("bottom"))));return z([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Pt("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Mt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(ee("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),ee("pathname"),v(()=>e),ee("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(ee("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Mt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function jt(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),ze("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),z([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let s=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(s)||(s=[s]);e:for(let p of s)for(let c of n.aliases.concat(n.version))if(new RegExp(p,"i").test(c)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),ze("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(jt)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return z([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));z([t.pipe(Ae(jt)),r],(i,a)=>a).pipe(ee("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ee("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);ze("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(re(r),Wr(t.pipe(Ae(jt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Vr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return z([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=Ve(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),De({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),De({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),De({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ee("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(ee("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(ee("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),ee("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return z([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),ee("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){z([ze("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?Tt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ut=sn(),Lt=ln(Ut),to=an(),Oe=gn(),hr=Pt("(min-width: 960px)"),Mi=Pt("(min-width: 1220px)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ut,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ut,Lt).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),Ft=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Lt})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>zn(e,{viewport$:Oe,target$:Lt,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ut}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:Ft})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Nr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft})):Nr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ut;window.target$=Lt;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.83f73b43.min.js.map + diff --git a/assets/javascripts/bundle.83f73b43.min.js.map b/assets/javascripts/bundle.83f73b43.min.js.map new file mode 100644 index 0000000..fe920b7 --- /dev/null +++ b/assets/javascripts/bundle.83f73b43.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n *\n * @class BehaviorSubject\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:

\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an