From 7196e2a8c54c61169d0b5198436cac29a6818af9 Mon Sep 17 00:00:00 2001 From: <> Date: Fri, 16 Feb 2024 18:29:42 +0000 Subject: [PATCH] Update documentation --- .buildinfo | 4 + .nojekyll | 0 .../grid_loading_example.ipynb | 241 ++ _images/data_model.png | Bin 0 -> 53537 bytes _images/index_api.svg | 97 + _images/index_contribute.svg | 76 + _images/index_getting_started.svg | 66 + _images/index_user_guide.svg | 67 + _images/joined_timeseries.png | Bin 0 -> 111370 bytes _images/metrics.png | Bin 0 -> 160289 bytes _modules/index.html | 530 +++ _modules/teehr.html | 542 +++ _modules/teehr/api/main.html | 847 +++++ _modules/teehr/database/teehr_dataset.html | 1700 +++++++++ _modules/teehr/loading/nwm/grid_utils.html | 666 ++++ _modules/teehr/loading/nwm/nwm_grids.html | 764 ++++ _modules/teehr/loading/nwm/nwm_points.html | 810 +++++ _modules/teehr/loading/nwm/point_utils.html | 747 ++++ .../loading/nwm/retrospective_grids.html | 881 +++++ .../loading/nwm/retrospective_points.html | 877 +++++ _modules/teehr/loading/nwm/utils.html | 1120 ++++++ _modules/teehr/loading/usgs/usgs.html | 867 +++++ _modules/teehr/models/loading/nwm22_grid.html | 827 +++++ .../teehr/models/loading/nwm22_point.html | 944 +++++ _modules/teehr/models/loading/nwm30_grid.html | 653 ++++ .../teehr/models/loading/nwm30_point.html | 659 ++++ _modules/teehr/models/loading/utils.html | 588 +++ _modules/teehr/models/queries.html | 833 +++++ _modules/teehr/models/queries_database.html | 993 +++++ _modules/teehr/queries/duckdb.html | 1148 ++++++ _modules/teehr/queries/duckdb_database.html | 1266 +++++++ _modules/teehr/queries/pandas.html | 880 +++++ _modules/teehr/queries/utils.html | 1248 +++++++ .../teehr/utilities/generate_weights.html | 819 +++++ _sources/autoapi/index.rst.txt | 14 + _sources/autoapi/teehr/api/index.rst.txt | 38 + _sources/autoapi/teehr/api/main/index.rst.txt | 405 +++ _sources/autoapi/teehr/database/index.rst.txt | 38 + .../database/teehr_dataset/index.rst.txt | 1094 ++++++ _sources/autoapi/teehr/index.rst.txt | 83 + _sources/autoapi/teehr/loading/index.rst.txt | 40 + .../teehr/loading/nextgen/index.rst.txt | 38 + .../teehr/loading/nextgen/ngen/index.rst.txt | 19 + .../teehr/loading/nwm/const/index.rst.txt | 62 + .../loading/nwm/grid_utils/index.rst.txt | 99 + .../autoapi/teehr/loading/nwm/index.rst.txt | 45 + .../teehr/loading/nwm/nwm_grids/index.rst.txt | 172 + .../loading/nwm/nwm_points/index.rst.txt | 180 + .../loading/nwm/point_utils/index.rst.txt | 138 + .../nwm/retrospective_grids/index.rst.txt | 224 ++ .../nwm/retrospective_points/index.rst.txt | 249 ++ .../teehr/loading/nwm/utils/index.rst.txt | 498 +++ .../autoapi/teehr/loading/usgs/index.rst.txt | 38 + .../teehr/loading/usgs/usgs/index.rst.txt | 292 ++ _sources/autoapi/teehr/models/index.rst.txt | 48 + .../teehr/models/loading/index.rst.txt | 42 + .../models/loading/nwm22_grid/index.rst.txt | 1280 +++++++ .../models/loading/nwm22_point/index.rst.txt | 1746 +++++++++ .../models/loading/nwm30_grid/index.rst.txt | 492 +++ .../models/loading/nwm30_point/index.rst.txt | 507 +++ .../teehr/models/loading/utils/index.rst.txt | 287 ++ .../teehr/models/queries/index.rst.txt | 1007 +++++ .../models/queries_database/index.rst.txt | 1134 ++++++ .../teehr/queries/duckdb/index.rst.txt | 425 +++ .../queries/duckdb_database/index.rst.txt | 464 +++ _sources/autoapi/teehr/queries/index.rst.txt | 41 + .../teehr/queries/pandas/index.rst.txt | 217 ++ .../autoapi/teehr/queries/utils/index.rst.txt | 1014 ++++++ .../utilities/generate_weights/index.rst.txt | 216 ++ .../autoapi/teehr/utilities/index.rst.txt | 38 + _sources/changelog/index.rst.txt | 288 ++ _sources/development/index.rst.txt | 109 + _sources/getting_started/data_model.rst.txt | 50 + _sources/getting_started/index.rst.txt | 174 + _sources/getting_started/queries.rst.txt | 36 + _sources/index.rst.txt | 102 + _sources/user_guide/index.rst.txt | 64 + .../loading/grid_loading_example.ipynb.txt | 241 ++ .../loading/load_retrospective.ipynb.txt | 122 + .../loading/ngen_to_parquet.ipynb.txt | 386 ++ .../loading/point_loading_example.ipynb.txt | 165 + .../notebooks/loading_examples_index.rst.txt | 12 + .../ngen_simulation_data_queries.ipynb.txt | 359 ++ .../queries/post_event_data_queries.ipynb.txt | 337 ++ .../notebooks/queries/test_queries.ipynb.txt | 160 + .../notebooks/queries_examples_index.rst.txt | 11 + ...e.1e8bd061cd6da7fc9cf755528e8ffc24.min.css | 1 + _sphinx_design_static/design-tabs.js | 27 + _static/basic.css | 925 +++++ _static/css/teehr.css | 70 + ...e.1e8bd061cd6da7fc9cf755528e8ffc24.min.css | 1 + _static/design-tabs.js | 27 + _static/doctools.js | 156 + _static/documentation_options.js | 13 + _static/favicon.png | Bin 0 -> 507 bytes _static/file.png | Bin 0 -> 286 bytes _static/graphviz.css | 19 + _static/index_api.svg | 97 + _static/index_contribute.svg | 76 + _static/index_getting_started.svg | 66 + _static/index_user_guide.svg | 67 + _static/language_data.js | 199 + _static/minus.png | Bin 0 -> 90 bytes ...69c37c29e427902b24a333a5f9fcb2f0b3ac41.css | 2342 ++++++++++++ _static/plus.png | Bin 0 -> 90 bytes _static/pygments.css | 152 + _static/scripts/bootstrap.js | 3 + _static/scripts/bootstrap.js.LICENSE.txt | 5 + _static/scripts/bootstrap.js.map | 1 + _static/scripts/pydata-sphinx-theme.js | 2 + _static/scripts/pydata-sphinx-theme.js.map | 1 + _static/searchtools.js | 574 +++ _static/sphinx_highlight.js | 154 + _static/styles/bootstrap.css | 6 + _static/styles/bootstrap.css.map | 1 + _static/styles/pydata-sphinx-theme.css | 2 + _static/styles/pydata-sphinx-theme.css.map | 1 + _static/styles/theme.css | 2 + _static/teehr.png | Bin 0 -> 10748 bytes _static/vendor/fontawesome/6.5.1/LICENSE.txt | 165 + .../vendor/fontawesome/6.5.1/css/all.min.css | 5 + .../vendor/fontawesome/6.5.1/js/all.min.js | 2 + .../6.5.1/js/all.min.js.LICENSE.txt | 5 + .../6.5.1/webfonts/fa-brands-400.ttf | Bin 0 -> 207972 bytes .../6.5.1/webfonts/fa-brands-400.woff2 | Bin 0 -> 117372 bytes .../6.5.1/webfonts/fa-regular-400.ttf | Bin 0 -> 68004 bytes .../6.5.1/webfonts/fa-regular-400.woff2 | Bin 0 -> 25452 bytes .../6.5.1/webfonts/fa-solid-900.ttf | Bin 0 -> 419720 bytes .../6.5.1/webfonts/fa-solid-900.woff2 | Bin 0 -> 156496 bytes .../6.5.1/webfonts/fa-v4compatibility.ttf | Bin 0 -> 10832 bytes .../6.5.1/webfonts/fa-v4compatibility.woff2 | Bin 0 -> 4792 bytes _static/webpack-macros.html | 31 + autoapi/index.html | 656 ++++ autoapi/teehr/api/index.html | 638 ++++ autoapi/teehr/api/main/index.html | 929 +++++ autoapi/teehr/database/index.html | 638 ++++ .../teehr/database/teehr_dataset/index.html | 1360 +++++++ autoapi/teehr/index.html | 717 ++++ autoapi/teehr/loading/index.html | 656 ++++ autoapi/teehr/loading/nextgen/index.html | 643 ++++ autoapi/teehr/loading/nextgen/ngen/index.html | 627 ++++ autoapi/teehr/loading/nwm/const/index.html | 700 ++++ .../teehr/loading/nwm/grid_utils/index.html | 682 ++++ autoapi/teehr/loading/nwm/index.html | 650 ++++ .../teehr/loading/nwm/nwm_grids/index.html | 771 ++++ .../teehr/loading/nwm/nwm_points/index.html | 777 ++++ .../teehr/loading/nwm/point_utils/index.html | 717 ++++ .../nwm/retrospective_grids/index.html | 766 ++++ .../nwm/retrospective_points/index.html | 824 +++++ autoapi/teehr/loading/nwm/utils/index.html | 972 +++++ autoapi/teehr/loading/usgs/index.html | 643 ++++ autoapi/teehr/loading/usgs/usgs/index.html | 804 ++++ autoapi/teehr/models/index.html | 655 ++++ autoapi/teehr/models/loading/index.html | 647 ++++ .../models/loading/nwm22_grid/index.html | 1847 ++++++++++ .../models/loading/nwm22_point/index.html | 2057 +++++++++++ .../models/loading/nwm30_grid/index.html | 1173 ++++++ .../models/loading/nwm30_point/index.html | 1191 ++++++ autoapi/teehr/models/loading/utils/index.html | 848 +++++ autoapi/teehr/models/queries/index.html | 1382 +++++++ .../teehr/models/queries_database/index.html | 1346 +++++++ autoapi/teehr/queries/duckdb/index.html | 1009 +++++ .../teehr/queries/duckdb_database/index.html | 1053 ++++++ autoapi/teehr/queries/index.html | 641 ++++ autoapi/teehr/queries/pandas/index.html | 827 +++++ autoapi/teehr/queries/utils/index.html | 1121 ++++++ .../utilities/generate_weights/index.html | 757 ++++ autoapi/teehr/utilities/index.html | 638 ++++ changelog/index.html | 1006 +++++ development/index.html | 663 ++++ genindex.html | 3237 +++++++++++++++++ getting_started/data_model.html | 622 ++++ getting_started/index.html | 755 ++++ getting_started/queries.html | 599 +++ index.html | 570 +++ objects.inv | Bin 0 -> 7251 bytes py-modindex.html | 670 ++++ search.html | 509 +++ searchindex.js | 1 + user_guide/index.html | 643 ++++ .../loading/grid_loading_example.html | 739 ++++ .../notebooks/loading/load_retrospective.html | 648 ++++ .../notebooks/loading/ngen_to_parquet.html | 873 +++++ .../loading/point_loading_example.html | 692 ++++ .../notebooks/loading_examples_index.html | 604 +++ .../queries/ngen_simulation_data_queries.html | 851 +++++ .../queries/post_event_data_queries.html | 843 +++++ .../notebooks/queries/test_queries.html | 683 ++++ .../notebooks/queries_examples_index.html | 586 +++ 189 files changed, 90454 insertions(+) create mode 100644 .buildinfo create mode 100644 .nojekyll create mode 100644 _downloads/074212d9cab0d4da5bc9827aca373ee9/grid_loading_example.ipynb create mode 100644 _images/data_model.png create mode 100644 _images/index_api.svg create mode 100644 _images/index_contribute.svg create mode 100644 _images/index_getting_started.svg create mode 100644 _images/index_user_guide.svg create mode 100644 _images/joined_timeseries.png create mode 100644 _images/metrics.png create mode 100644 _modules/index.html create mode 100644 _modules/teehr.html create mode 100644 _modules/teehr/api/main.html create mode 100644 _modules/teehr/database/teehr_dataset.html create mode 100644 _modules/teehr/loading/nwm/grid_utils.html create mode 100644 _modules/teehr/loading/nwm/nwm_grids.html create mode 100644 _modules/teehr/loading/nwm/nwm_points.html create mode 100644 _modules/teehr/loading/nwm/point_utils.html create mode 100644 _modules/teehr/loading/nwm/retrospective_grids.html create mode 100644 _modules/teehr/loading/nwm/retrospective_points.html create mode 100644 _modules/teehr/loading/nwm/utils.html create mode 100644 _modules/teehr/loading/usgs/usgs.html create mode 100644 _modules/teehr/models/loading/nwm22_grid.html create mode 100644 _modules/teehr/models/loading/nwm22_point.html create mode 100644 _modules/teehr/models/loading/nwm30_grid.html create mode 100644 _modules/teehr/models/loading/nwm30_point.html create mode 100644 _modules/teehr/models/loading/utils.html create mode 100644 _modules/teehr/models/queries.html create mode 100644 _modules/teehr/models/queries_database.html create mode 100644 _modules/teehr/queries/duckdb.html create mode 100644 _modules/teehr/queries/duckdb_database.html create mode 100644 _modules/teehr/queries/pandas.html create mode 100644 _modules/teehr/queries/utils.html create mode 100644 _modules/teehr/utilities/generate_weights.html create mode 100644 _sources/autoapi/index.rst.txt create mode 100644 _sources/autoapi/teehr/api/index.rst.txt create mode 100644 _sources/autoapi/teehr/api/main/index.rst.txt create mode 100644 _sources/autoapi/teehr/database/index.rst.txt create mode 100644 _sources/autoapi/teehr/database/teehr_dataset/index.rst.txt create mode 100644 _sources/autoapi/teehr/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nextgen/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nextgen/ngen/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/const/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/grid_utils/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/nwm_grids/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/nwm_points/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/point_utils/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/retrospective_grids/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/retrospective_points/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/nwm/utils/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/usgs/index.rst.txt create mode 100644 _sources/autoapi/teehr/loading/usgs/usgs/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/loading/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/loading/nwm22_grid/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/loading/nwm22_point/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/loading/nwm30_grid/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/loading/nwm30_point/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/loading/utils/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/queries/index.rst.txt create mode 100644 _sources/autoapi/teehr/models/queries_database/index.rst.txt create mode 100644 _sources/autoapi/teehr/queries/duckdb/index.rst.txt create mode 100644 _sources/autoapi/teehr/queries/duckdb_database/index.rst.txt create mode 100644 _sources/autoapi/teehr/queries/index.rst.txt create mode 100644 _sources/autoapi/teehr/queries/pandas/index.rst.txt create mode 100644 _sources/autoapi/teehr/queries/utils/index.rst.txt create mode 100644 _sources/autoapi/teehr/utilities/generate_weights/index.rst.txt create mode 100644 _sources/autoapi/teehr/utilities/index.rst.txt create mode 100644 _sources/changelog/index.rst.txt create mode 100644 _sources/development/index.rst.txt create mode 100644 _sources/getting_started/data_model.rst.txt create mode 100644 _sources/getting_started/index.rst.txt create mode 100644 _sources/getting_started/queries.rst.txt create mode 100644 _sources/index.rst.txt create mode 100644 _sources/user_guide/index.rst.txt create mode 100644 _sources/user_guide/notebooks/loading/grid_loading_example.ipynb.txt create mode 100644 _sources/user_guide/notebooks/loading/load_retrospective.ipynb.txt create mode 100644 _sources/user_guide/notebooks/loading/ngen_to_parquet.ipynb.txt create mode 100644 _sources/user_guide/notebooks/loading/point_loading_example.ipynb.txt create mode 100644 _sources/user_guide/notebooks/loading_examples_index.rst.txt create mode 100644 _sources/user_guide/notebooks/queries/ngen_simulation_data_queries.ipynb.txt create mode 100644 _sources/user_guide/notebooks/queries/post_event_data_queries.ipynb.txt create mode 100644 _sources/user_guide/notebooks/queries/test_queries.ipynb.txt create mode 100644 _sources/user_guide/notebooks/queries_examples_index.rst.txt create mode 100644 _sphinx_design_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css create mode 100644 _sphinx_design_static/design-tabs.js create mode 100644 _static/basic.css create mode 100644 _static/css/teehr.css create mode 100644 _static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css create mode 100644 _static/design-tabs.js create mode 100644 _static/doctools.js create mode 100644 _static/documentation_options.js create mode 100644 _static/favicon.png create mode 100644 _static/file.png create mode 100644 _static/graphviz.css create mode 100644 _static/index_api.svg create mode 100644 _static/index_contribute.svg create mode 100644 _static/index_getting_started.svg create mode 100644 _static/index_user_guide.svg create mode 100644 _static/language_data.js create mode 100644 _static/minus.png create mode 100644 _static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css create mode 100644 _static/plus.png create mode 100644 _static/pygments.css create mode 100644 _static/scripts/bootstrap.js create mode 100644 _static/scripts/bootstrap.js.LICENSE.txt create mode 100644 _static/scripts/bootstrap.js.map create mode 100644 _static/scripts/pydata-sphinx-theme.js create mode 100644 _static/scripts/pydata-sphinx-theme.js.map create mode 100644 _static/searchtools.js create mode 100644 _static/sphinx_highlight.js create mode 100644 _static/styles/bootstrap.css create mode 100644 _static/styles/bootstrap.css.map create mode 100644 _static/styles/pydata-sphinx-theme.css create mode 100644 _static/styles/pydata-sphinx-theme.css.map create mode 100644 _static/styles/theme.css create mode 100644 _static/teehr.png create mode 100644 _static/vendor/fontawesome/6.5.1/LICENSE.txt create mode 100644 _static/vendor/fontawesome/6.5.1/css/all.min.css create mode 100644 _static/vendor/fontawesome/6.5.1/js/all.min.js create mode 100644 _static/vendor/fontawesome/6.5.1/js/all.min.js.LICENSE.txt create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-brands-400.ttf create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-brands-400.woff2 create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-regular-400.ttf create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-regular-400.woff2 create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-solid-900.ttf create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-solid-900.woff2 create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-v4compatibility.ttf create mode 100644 _static/vendor/fontawesome/6.5.1/webfonts/fa-v4compatibility.woff2 create mode 100644 _static/webpack-macros.html create mode 100644 autoapi/index.html create mode 100644 autoapi/teehr/api/index.html create mode 100644 autoapi/teehr/api/main/index.html create mode 100644 autoapi/teehr/database/index.html create mode 100644 autoapi/teehr/database/teehr_dataset/index.html create mode 100644 autoapi/teehr/index.html create mode 100644 autoapi/teehr/loading/index.html create mode 100644 autoapi/teehr/loading/nextgen/index.html create mode 100644 autoapi/teehr/loading/nextgen/ngen/index.html create mode 100644 autoapi/teehr/loading/nwm/const/index.html create mode 100644 autoapi/teehr/loading/nwm/grid_utils/index.html create mode 100644 autoapi/teehr/loading/nwm/index.html create mode 100644 autoapi/teehr/loading/nwm/nwm_grids/index.html create mode 100644 autoapi/teehr/loading/nwm/nwm_points/index.html create mode 100644 autoapi/teehr/loading/nwm/point_utils/index.html create mode 100644 autoapi/teehr/loading/nwm/retrospective_grids/index.html create mode 100644 autoapi/teehr/loading/nwm/retrospective_points/index.html create mode 100644 autoapi/teehr/loading/nwm/utils/index.html create mode 100644 autoapi/teehr/loading/usgs/index.html create mode 100644 autoapi/teehr/loading/usgs/usgs/index.html create mode 100644 autoapi/teehr/models/index.html create mode 100644 autoapi/teehr/models/loading/index.html create mode 100644 autoapi/teehr/models/loading/nwm22_grid/index.html create mode 100644 autoapi/teehr/models/loading/nwm22_point/index.html create mode 100644 autoapi/teehr/models/loading/nwm30_grid/index.html create mode 100644 autoapi/teehr/models/loading/nwm30_point/index.html create mode 100644 autoapi/teehr/models/loading/utils/index.html create mode 100644 autoapi/teehr/models/queries/index.html create mode 100644 autoapi/teehr/models/queries_database/index.html create mode 100644 autoapi/teehr/queries/duckdb/index.html create mode 100644 autoapi/teehr/queries/duckdb_database/index.html create mode 100644 autoapi/teehr/queries/index.html create mode 100644 autoapi/teehr/queries/pandas/index.html create mode 100644 autoapi/teehr/queries/utils/index.html create mode 100644 autoapi/teehr/utilities/generate_weights/index.html create mode 100644 autoapi/teehr/utilities/index.html create mode 100644 changelog/index.html create mode 100644 development/index.html create mode 100644 genindex.html create mode 100644 getting_started/data_model.html create mode 100644 getting_started/index.html create mode 100644 getting_started/queries.html create mode 100644 index.html create mode 100644 objects.inv create mode 100644 py-modindex.html create mode 100644 search.html create mode 100644 searchindex.js create mode 100644 user_guide/index.html create mode 100644 user_guide/notebooks/loading/grid_loading_example.html create mode 100644 user_guide/notebooks/loading/load_retrospective.html create mode 100644 user_guide/notebooks/loading/ngen_to_parquet.html create mode 100644 user_guide/notebooks/loading/point_loading_example.html create mode 100644 user_guide/notebooks/loading_examples_index.html create mode 100644 user_guide/notebooks/queries/ngen_simulation_data_queries.html create mode 100644 user_guide/notebooks/queries/post_event_data_queries.html create mode 100644 user_guide/notebooks/queries/test_queries.html create mode 100644 user_guide/notebooks/queries_examples_index.html diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..8fcda1d2 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 1025286996b58bc9cf006461d359d7b4 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/_downloads/074212d9cab0d4da5bc9827aca373ee9/grid_loading_example.ipynb b/_downloads/074212d9cab0d4da5bc9827aca373ee9/grid_loading_example.ipynb new file mode 100644 index 00000000..8a691f5e --- /dev/null +++ b/_downloads/074212d9cab0d4da5bc9827aca373ee9/grid_loading_example.ipynb @@ -0,0 +1,241 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f87ea1e0", + "metadata": {}, + "source": [ + "# NWM Gridded Data Loading" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "38f2f9f4-5294-4f9b-971a-931b91da8f85", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Import the required packages.\n", + "\n", + "# Run this if TEEHR is not installed\n", + "# import sys\n", + "# sys.path.insert(0, \"../../src\")\n", + "\n", + "import os\n", + "\n", + "from dask.distributed import Client\n", + "from pathlib import Path\n", + "\n", + "import teehr.loading.nwm.nwm_grids as tlg\n", + "\n", + "import teehr.utilities.generate_weights as gw\n", + "from teehr.loading.nwm.const import CONUS_NWM_WKT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Specify input variables" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76e363e7-a3e0-427f-811f-a6298b93536f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Set some notebook variables to point to the relevant study files.\n", + "TEMP_GEO_DIR = Path(Path.home(), \"temp/geo\")\n", + "TEMP_GEO_DIR.mkdir(exist_ok=True, parents=True)\n", + "\n", + "# Generate weights\n", + "# fetch \"https://storage.googleapis.com/national-water-model/nwm.20220101/forcing_short_range/nwm.t00z.short_range.forcing.f001.conus.nc\"\n", + "GRID_TEMPLATE_FILE = Path(TEMP_GEO_DIR, \"nwm.t00z.short_range.forcing.f001.conus.nc\")\n", + "\n", + "# fetch \"https://nextgen-hydrofabric.s3.amazonaws.com/v1.2/nextgen_03S.gpkg\"\n", + "ZONE_GEO_FILE = Path(TEMP_GEO_DIR, \"nextgen_03S.gpkg\")\n", + "ZONAL_WEIGHTS_FILEPATH = Path(TEMP_GEO_DIR, \"nextgen_03S_weights.parquet\")\n", + "UNIQUE_ZONE_ID = \"id\"\n", + "\n", + "# NWM\n", + "CONFIGURATION = \"forcing_short_range\" # forcing_short_range, forcing_analysis_assim, forcing_medium_range\n", + "OUTPUT_TYPE = \"forcing\"\n", + "VARIABLE_NAME = \"RAINRATE\"\n", + "\n", + "START_DATE = \"2020-12-18\"\n", + "INGEST_DAYS = 1\n", + "\n", + "JSON_DIR = Path(Path.home(), \"temp/parquet/jsons/\")\n", + "OUTPUT_DIR = Path(Path.home(), \"temp/parquet\")\n", + "\n", + "NWM_VERSION = \"nwm22\" # Currently accepts \"nwm22\" or \"nwm30\"\n", + " # Use \"nwm22\" for dates prior to 09-19-2023\n", + "\n", + "DATA_SOURCE = \"GCS\" # Specifies the remote location from which to fetch the data\n", + " # (\"GCS\", \"NOMADS\", \"DSTOR\")\n", + "\n", + "KERCHUNK_METHOD = \"auto\" # When data_source = \"GCS\", specifies the preference in creating Kerchunk reference json files.\n", + " # \"local\" - always create new json files from netcdf files in GCS and save locally, if they do not already exist\n", + " # \"remote\" - read the CIROH pre-generated jsons from s3, ignoring any that are unavailable\n", + " # \"auto\" - read the CIROH pre-generated jsons from s3, and create any that are unavailable, storing locally\n", + "\n", + "CONCAT_DIMS = [\"time\"] # \"reference_time\"\n", + "T_MINUS = [0, 1, 2] # Only used if an assimilation run is selected\n", + "IGNORE_MISSING_FILE = True # If True, the missing file(s) will be skipped and the process will resume\n", + " # If False, TEEHR will fail if a missing NWM file is encountered\n", + "OVERWRITE_OUTPUT = True # If True, existing output files will be overwritten\n", + " # If False (default), existing files are retained" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fetch a template forcing netCDF file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53724249-5fbe-4fa4-8fd9-96a3238d16b3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "!wget -O /home/jovyan/temp/geo/nwm.t00z.short_range.forcing.f001.conus.nc \\\n", + "https://storage.googleapis.com/national-water-model/nwm.20220101/forcing_short_range/nwm.t00z.short_range.forcing.f001.conus.nc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fetch some example polygons (nextgen divides)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a717b43", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "!wget -O /home/jovyan/temp/geo/nextgen_03S.gpkg https://lynker-spatial.s3.amazonaws.com/v20/gpkg/nextgen_03S.gpkg" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Start a local dask cluster" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30f8283c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "n_workers = max(os.cpu_count() - 1, 1)\n", + "client = Client(n_workers=n_workers)\n", + "client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate the weights file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ac3c383-9aaf-4fd7-8c33-461ff5e87bb7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%time\n", + "gw.generate_weights_file(\n", + " zone_polygon_filepath=ZONE_GEO_FILE,\n", + " template_dataset=GRID_TEMPLATE_FILE,\n", + " variable_name=VARIABLE_NAME,\n", + " output_weights_filepath=ZONAL_WEIGHTS_FILEPATH,\n", + " crs_wkt=CONUS_NWM_WKT,\n", + " unique_zone_id=UNIQUE_ZONE_ID,\n", + " layer=\"divides\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fetch the gridded data summarized to the polygons" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b77bd47", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%time\n", + "tlg.nwm_grids_to_parquet(\n", + " configuration=CONFIGURATION,\n", + " output_type=OUTPUT_TYPE,\n", + " variable_name=VARIABLE_NAME,\n", + " start_date=START_DATE,\n", + " ingest_days=INGEST_DAYS,\n", + " zonal_weights_filepath=ZONAL_WEIGHTS_FILEPATH,\n", + " json_dir=JSON_DIR,\n", + " output_parquet_dir=OUTPUT_DIR,\n", + " nwm_version=NWM_VERSION,\n", + " data_source=DATA_SOURCE,\n", + " kerchunk_method=KERCHUNK_METHOD,\n", + " t_minus_hours=T_MINUS,\n", + " ignore_missing_file=IGNORE_MISSING_FILE,\n", + " overwrite_output=OVERWRITE_OUTPUT\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/_images/data_model.png b/_images/data_model.png new file mode 100644 index 0000000000000000000000000000000000000000..13628d225da62afb2ec199ab5a12fd5d890d0512 GIT binary patch literal 53537 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 GuKqvsP{C6G literal 0 HcmV?d00001 diff --git a/_images/index_api.svg b/_images/index_api.svg new file mode 100644 index 00000000..69f7ba1d --- /dev/null +++ b/_images/index_api.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/_images/index_contribute.svg b/_images/index_contribute.svg new file mode 100644 index 00000000..de3d9023 --- /dev/null +++ b/_images/index_contribute.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/_images/index_getting_started.svg b/_images/index_getting_started.svg new file mode 100644 index 00000000..2d36622c --- /dev/null +++ b/_images/index_getting_started.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/_images/index_user_guide.svg b/_images/index_user_guide.svg new file mode 100644 index 00000000..bd170535 --- /dev/null +++ b/_images/index_user_guide.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/_images/joined_timeseries.png b/_images/joined_timeseries.png new file mode 100644 index 0000000000000000000000000000000000000000..dbcec546154ae1fc8f726bf3c530777659d4663d GIT binary patch literal 111370 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⪻=R)}qZa$Cf8)xu)|WkTA&LdyD=ieTQ!o~fo_&TX!xT#_^9?ugzKLY%0>NA+ z%37HBl7Q+XMZNwD*Nu=-KhNV*1DX4a)H=NJ7?vvAuuqTQoGiZM^^rBOZoDb56>(S< zb;>GKAL*FKO)zpNUQcgWKs&PMo!UPz$}?OygGC|XD8EH!A&5pFyd1WY4dCkm^R-E_ z5v$K`6}gkSk>V*^Voy^GGOt)|i<^Av<+W+zFnn@pFJTRLR*@F8c&sMw&cPkK^VxDo0SR2cOcR0Y{be*8)}$p zIncZE!CYomoS7%wfAU+BkWNBWJT|ue1<5&;KhaCWsSm}og+aK%+4>XFLzzbBn8npJ zi_H9&#kYPuJ~SdH%N&&JRi>m5@wedbl(Es8S2#w0Pl@Fkv$NBi~r`{409|lst!klDBrQ_5K?4ojH{>I5%nhH}CFGgN~QN@RiGY zBo%+qcv1cmKYzU8R(}Y&``pit0qi45fx>ew9-wxaURHgf>qN^7TD^qu|+-mQL(p2!;owm;Mp( zf%=|x+mj?kR9Y~cp%vNeV#k9HNS$P)W_z|20j`3mBAqGhoky&A6JK=!fNZg zP+n)2!E0YXq=6!B@8;I+wQ)L($@}Xu)NFSjiC$g#2$8M_gw~IAUB$*~*3wwF5J1;% zTOR2V{6+>{#fK`mpd;)~{N5@zb^g-{5TZiFYg=m%UC61nd+~9C6eh<+JvArVCVRu0 zNMiHi2YqAc(!4R!72WR@vCG#mn%4qU#)X=;!51>vV+Q1OTgG((>2YFYx&KA*Y8Oo1 zq-x#oU}N=uc$!)fJZwpIT- zb`5^6-u{*;+2KkD9RWM)UPFir@#b%{Y<8{^dAL^YHa#Oz;%8@7M5=}!?}-WAS+-`- zAsNa=e&*EQH=L*(@ZlCqFqL|%k;`&s-^8JJ<-Eg>1;dYmS27Jgf^U*Qby!r5fanmd zNt@-NIDqd4FyTzjJl70fAO|u?8}y6i5#(Hw%C zC(p!ZqAo={k6|7jD@o{DzNO{A&%X6+J=uECp%1ATFNa$P)eKW0xgXZ)0w*ecN797< zUB*fEZ=PUzEfG5)HPYZEeZW>D(mQC#T)^=*zZ`j^PBO0Amip0V;;XSun%^I63qc;z z=Altx|A&9jBN)&ilkK*-gbbZ)hg~J^ds}bp*6MyCVTr1WGE%HSord(vz2Pe=@u9+1 zbCm{S$bkq+ev{%)yj*vxx$I&&-aOt}5kPeHJ>Q<9o(XW;ZJ`XYpAQbnKe*9P+OSg} z_GC@0sjZ(F;BtY4Sb2XKr5$hXrpzhg(vU(___$5%F}hmY8RiURRv03Ohj-;%FoCL5 zq~XC8aY`;b*~8vJ;79bmSE7cmtvQ5(Ij$5h>3QwUtH7wd!Yea{D-)&WVrw6eZ4~sX z-P}qJQHd;ne)v`tDcSp!@eOI6{`V>P8E5I@iARez*;uKc-QT`YUvJTDl2 z$lWqjMQ5A6h}`PQnlK8wu)|mV8i?XDTa#Ts^^tE9Brpa+*+=pMhqC)?y;$+2($7Lv zfc*i;UcZ+In*pxmPl{NZ*5k54%{*lPqiU3XCclSVE^+3a!<`@e$fUmT>33{k+z8kt z=|7KN>faj$co9)JTMl)-zZKCI5P)@}FCTu${=%%P@180@aONqui+XJlaTl3d^4xv# z%g!CyFS>gw`3_EmQSc4^+9->& z?wv2Y%?G=CBV8T+vFpECv`oi{$YAb_78~?$i@vjKX@i5u#ov zK#BZCB*wl4*fP?U(L{mqhCkLi!OUHlkC_bz7_ma`h0KSF5qh8A8qUJ3LIc`lN6_N0 zE;>P#L@emL7O60zq9Grtw}pWZLV%x(*s}-rKznO5>O2?dx7E>n8Q~8 zJb?Q^*;ztXads36M`lrU9`6tpAE^u`I_6R@xHsUt`mF%ebRCfBtYH);Nh(^27x=5B z%tg3T%z+v4i`3I9cU3l+>Pqe*zCqtEQ)9*cd(*tRB5 z*BgT_PS(DJE{ED*T3=wZs|k{T2{njP*|u-UUmk5}0rveQ2;|8`oSt?=>}yLYP|N%V zqpZ>pdof3!6Pfl1YSKk>pZQZiR1oQ36)Fj&$!>D_%t>l*fG@$nwTaB->CQE>fe1p3 zVL2%MlV%wPQYY9kC%zsZ^sZ4geD+O%;)AqsUOGFnZUTcvJ5`|*&Gpgd`>R1{fFl=KrNE<(qi9g{^~w(w5u^$w!tJ*fLnMc2-$O?Ucp#2u z9tG^YAtK`Ug4T=xmaaV3P!i}cneZ8CL{^H>Uj%ReSO0VqG@%Lsm>-ISImE2r!nT;! zbO;MlcqGyUL+-Q#Vql7(xM?+%7)$wXIwNZQE=cjNP)jXuSvLt|M?Hon5hH{v2?5E6 z{s4Vmj8jD&+aViBgu;go0v&(&5?8@^2)TwD=W?F@g+L-&`h5c2)GWw?E~EEtH+4J3 zDjBVQLBp;wnp+WS=C(;?3^$M?liLRnXG))&gAdgC@&f;4c<>^E#oI*C(0mB1Yq+j! z?8|N-5H3?Qx}qF~KG&DC{FXBmu(ekreDEqCZxu^DHS3UTyf|0L>AN#;>NOI6+O>w} zLs!pi-#xqOzIDA`*OvJ*`u@CMcREX;uYNwyY$xYyilRQP!qnZ4&glnon*1tLTQjdc ztamrrvo2(|fCj4FERaBLyk_csiDflTIAy$sZt$Ll{dzpK?fyK25V~tNGt5pjszX4w z#ls3CAKecALb5i}sebRd6%&e3^(KrRJ%N(}!qAwRjt&{;RPg=sHTm=JkxWUelL8z` zLq2_6N*&_d0>;ry5yhvZkdy5Ch{&^IcjfULfD0F#JeyJ1P({uZ!C4<#60Va!pHa_IbnSDBs-~yhIg|R2Y9WpgS;2^RWsp{D@PmjyR{ViNaAcJG1fpik{Cz zN;HEb3Yn2#{MgUU_C#YN*?gKtqb(q!;ugf=+IaP9S3(OZWMn{QP(G^_<=d=k$fk9R zUr%P6E}GQJ{Ea+lj4DVmR%wfr*NU|^$*?6V2T5&^;884|(M1E^vq2Yj;W36bl{BW1 z3C-!9yFB6TvJ3PvZ%}PC6-MN@YBKMFF;E(C$c_g+0VTVfUbcoO7Y6pW#s+UhsJ|RU z#NKbqG}x)D;aK#J6-Ud18J@+A%x2iVnV~7%ShG#J5o8qGFHr^W9Yo{n5+Oh zFTP2`Gx|$=LBlGV{Nazs;(uZ`2Duk!77DxWgKK}L*K_n6dI=2+cQj1TGHxF5k$v)j zYS4AB({8@6p?DEAltC{|jh@(Nr`Vfva|TYtpV)(_P=IEA#Pm$W!_W5&5~jPj!2|J8 z?umtfjtFPim5(!my$(6SV`H&SCzd?HAFKg~PYGU_-&Y0?S`(lv%$!Z8Sk<(rdD;o8Qf@Z_}a&y#%Q%$E~f& z>`s{Ltj<+j?9#BlI^fgRoGgE1Y~*pMyh6p+gfgp2S<2n{?aPIA=O3!1NM$F$bZgDe z_h#o_Hiw=SP4jO2v|kFn?Zl|yP~vxLLhPgpT5`j#EC&Y4>v5(0v6T3w0{R6_IgY`} zfmUdiw_cm-P=BGI-a7Kg2_f*qsW4Hv+z>TJ%ZK!e)iXjUi9Xa2{S4{U&?L-D;=nv6 z#O|g@63NvF9MyeLGcN;Aq)K1p2!G|VzEs~8><(Z`FsS)ARja0gV7XGD5lZ5u z%h@|h-`V61_$o;8otT3AxQwsEBZ6Y%x=FS!$9O5JJ}KtP2gjulkldHzhH2bxnWL_g z8p0om*D*D$$>*|rD7MwYhuI8_4P_bhKgwMiaXs-!A^wXl^c;>&wroQm5^|nZ4}-Mdqj0`mcii;Y}9&fdQ7`(0c1U3;!>$Vr@Xo46rmtIIq+;CH9b zxfG&z`taozp2Sji$=|5w@!A?E=*(_mFX+&UA20ev3w=>hF1z6N%3627yv6Y|EHmfM z?_-^%9)3R#>e-EtRWlxJR9xzOH>~-7cXxlM&1R_b#oK5iy4&VqsUvIBcc(O?0@i0g zop_qYrTizpMa<`N(eI+m$JqyP0VD<;UwdmXFMFC$1GAZU*%# z`W3-_j;G2WjiTPL$D9vjc7VClt&i{4v)UC`jOLLKV*6lztXUY#4l5!>A;p_k*1#md zP>>$|u5IG%#m8Ylol0E_X>&tmFGc`B1iNl8ct~)JsW)aasXCkKdO)!IBTeRkqP zc4=W*Js<3pNN&T6>d|xR7N3`)0k4QBm4&2syc$2k{TwAkEoNm)NYow|r=lMvN5X%W zHqrOIYHl-!Ky(4&8_lDe)G4q*^m)EWc26EFX#ULqr}LwlGo~xP(O0JLCh2kbJzF>B z*pKg4Am8q2Mb0}-+E2-*68853Fe2PGq-iG$(o_u}P`FmjP5 z+c;mGs(Of&T25bIK0D|32fg`P9vITsVpBT*Cq6U!OZ9s2hTTZ*wQQpthX<;iP_H+& zodx1S3^iAOGeHyszXImtO`pd14erhXa5WRgz8fzl96PxXcb~`rXI_5nbK7#nz3GBr z08+@nya{~XlNVb22XcVc@*`n7V}V5aZsw_q3-&`(k2AsgWFX{`pUzq+IM~l=gq3=u zR6yji=%ew;7fF|6N*C{lmiq+EdvcOGVt1bBUvpBf^ooH2x@kc+4>z0!{gQ0Uv@c)o zu{qeM87rC8Dt>%mbE(39O?bNwv5?a~?h%S1A*6&d9{V)vJ1Dg3k;L((Zf8u%x6{9h zLug@+%ez%q`izDF>UYB`)K^E_15o^Bl5>{=nJUS9dx99VJ*Gpw`*W&@)PJ%EMk*)n z$a=aKI}wrd#&SIHIWt$%Z^U<>ppx7&m}B{%+XJ^rYZ>HjPuG6dU9r!OqyOm0K7*Be zzsL19!Q?13Gt!i2Nw)kOX=3_4NNCB>^N{S(X%u#s=3P)8<6AP+n8cG(Bh>M!y=&%9 z_lBXijbII>K&LRp<7Qt&!)uE>_=JYCyP~|eG^x|NWoJ-WHcqHHei8o)Uv&OOvite-$H-3#E=1(Q zhTGL6+tF7FJ#Kd`>7|oCE~lY2vUR`JR3YNeBRCE)-O2! z3W?z=rqs9v?<8;9Ph~ouW}kTY=2Wd1Q7gDd%Cms;Bo@#q-~G(tyOij)YyJg03p7W9vqHI5YP_&8-LYXk*ly;1Lu*2k)BZbpjQ3c~ zlLhJK&g?YI(oVYCWaAHuiqYzQA-E;+&GD~GoXc`jKVSUpk!DmG_twnAbs+U_I8Ub+ z9wi*YZn#i!a8Z8drTc<@n|Ot9(0)LzF%Nj^}1j*imZ{< zZY=@E)HAfS&3wN_xjWr)BXw4q8jt^D^l_#Y*7Yc{tMwB}2cvmh@+)k}MrZu#jlP|0 zO7eFcYQrX^Zq#=dmfJ@vg1Wk`d8;3()NcPN6~z69j0AXgTA%4 z5wVjhJ8u;hy_MJQfyXq2Qysm5l#6N_o+acX|3qnc)^+9Vp7SF;RpLrQoTjo=84rtK zvUAW77t)Q!NzV5s;t08+?Z|IbBRwH0hH^l$l7$3inSP}7u_PTTP{rJdc7qDT6P5Tz zy0A@{X5nVR$WVR5l3@Ll4HCIJ6tQ!4EyYpQ@Hr{d^XT6~suZ7;dG2?KPK^Cn`+-XJ_aVuEU@p^PR1pt#RM)$q44ZT%WcCi*W<3R4`WgZhrN?z= zeP!Mykt4h-6cKs%Wj9e4>0Ub-FD2^4cC7$y*l0H;4~Zv#|NTY6r^Eb1$8Z`-?;re{ zUbySfKlmkHVAVXedY=3n;TnCB7OV2MNkU^_sjS})m-*0h3icx+#$pE4_$BoBm+w99 z$Lu!=inmaSuN_m>bQTPEE|tTreU;|4`Q>=4B}Uu)a^u^rg;EkJXUkYZ1sJ#@3{Q;C z>u7xN29lGK*=R)I$TRe?9UDbp#$DJw~FA)g-ov_^13 zj-Nz#w@}zZy@0r-x`r&EA3}a{q-)Hb+C7V{$(O+Jy{2_KjxrIp!qv`>ECmOVG2lo7 zA6THe00ivci*xoqP<8`6fuEsC>{f$y=%agqn<`SQcXRbT*RKNGqiy8lf;qI_6p4<6zB-=1M z5=vLaV`jc@#J$ifeWlu2<|W>&BfcK%K3`v2s)E*nmfL_5rU7N2o*1{*lEL3Kyopt{ z;h!2KWm~bc*pat%Yt9YfwG?WvTo=#|s)iiOo)6$RGHzAguUlIK?{T&$<8N=>+luBL>G1|fh9Si8}w7`P52P0GbArUVb&MgsBea0gl zw?}^x4|m*Je_O=mc!SK!=Mqz%U!sy#J#*-X~i?#%N( znWh7MAmzglS)#*BWGx4x)D4Z!_PjOwRrl(J4h>d603PGCvjhyU9#rYsb4eBgdP7*R zn7}4E(*+^y^MM?cE=BDc5qFjb)Np-A7AX@%mJ81lF48~0Eu<$VN%#tiHhH3)Pbopr zOqKCQj+nqE=VCY=L?oA@)puovJp;c1*R?2ZF803KNPP;!`KUqUD7xxQ;0XZ!FpO$* zG9+~mw8IA}O8Ebo_gCyC71=F!w2zf{>6iY#FB&NrPD@Ub ztn3rAdiT=nqEelncXh{peMVD`*8d)&&Ijr7hkz4L@7eehiggR6A*~GhjeD;ji+M{) z!uK!qw)>Ie{i58y5^BKS67~A7I&z3(pVBw9-A&CJS*zLOuO_MqXGh^3e?{GOGkx;d z=F=~U(Y3gHdpX(B_iaDlcylTW5k7N=DUf^?Q(V;xLTVFm1JBer--OW!YQifeS@+B6Z+YS+UZra2XlES z_9bmkVyasj!VR2clmsdY<)!m*?%ir>3I(3Wh zge4IQt7?KHO!neY)BbelL$i$RTuRp0blz}=l%|37JoKN(q}}M}29P^T;Ix<6kLLi0 zXX32N*Vrte33GXug&69O>A%N@F%O#*` z%sA1TW${3UO2AUH$}|NO38I$uLKufSQqZ33p!Id53{>fr##m8y8b3CbeK$z38d1eH zwV%(+1wT>+&8gsS>CcQ?1}9Ewy{1pX_&q|$v}%q8pKvStkz@S-2>Z@JEcf{D+g{m< zWV@{tvR8;3_f3REWTb3KMx@M8cHBr-%1B7cC@WMlvsXm+N@Qm9d@r4I{{QEB^Sn7P z&N*~-UBByheSe=>xR-6y5@8c@ng+X|2X%_KynesDn4_QDP(-lCHY&oDK=J*(f9>!@ zhMD~Dr-QBy*VlrW$^}$z)`dn)LhiC@P=}M&b+mw!6Zj z`wnZTgZ)^r1_5Y;sj@4K2@i;G+*(e*r^1BcGLImcP@<0&>iU2UA`;V+#*;JZ^PX%c zC_-s7T}1ilyGEV7gAxtruj9p?T0-FFuC13#$I^|d@GL=uv;j0nL^obxcX$UAPUA7# z>|dy`&Y2Y)?Fj|08{Klo$%|o zl*5=*rz8Dn1=qp0eCh|P?SsV03H>KQTbG4}hnCv&oMh;uI=jF?=htDJuEH>9TNSz< zMEjs}O|037Q9XQ-a&k4{-n!$|kCgZU;603uI}0x5YiE0}cCSz~?%p%_zP)iEASG7p z>hjXOawn+WM3-pAZ4^oZe;J@heB5IyGYwxyHBR*!yYRTr?3=1(7>%fItiBaB6_A44 z`CN3e%2a8mBWKF8(NDv&=;)huod#x|7ndc2`uv;tH!e}*RnMHM9x@&4|C4XT%lHV* zmwOSrAG=>?ewsC5zigg(6x6V^Go>0Y40Tm~32{?DWlxy*eh$1j{4O?((egIQgNzrY zInQ^tzA+c9a&*ybbuhbGoKE+h?OqYJhGtTB7O{OB@KcSbF14%RA2PCZ-FZU))mbr| z1dr83vudDe-(U!iEIx{rK9o7Hm$m)L>a7hVAR26>mhr#?rzWyiHn=C~QA4MDewsVG z-n&cbA+0a>%xPlZ=^C_C*&n)&O=FAQ?%9+_`?gm`^8Au+luQmL+C0|6Y2i3GUO;eq z+)H*Kf^G{1ggkr1{K=p>rCWDV(MCF52hHq@i4k=Cn1=4+DeuRqt9GHiC1id#jh~*@ z?{3RqXebLAwR~AgqO_Lq(At`77(@t4EJA@$$DY1Y1+~e0B-L!?=zSj=O zO<34J@%JYDFj%?JVyC0bx$jU9=DF|fv)a-E>wyrEM)kHDh{#5q6mQ(Cr36CNW?x?^ z4Q(f%(!J5Jz%2Ql)<2(5j@1TJ5M^0?rk(t8Z--y$E%H#Q#_H-g$+uMpxjDDXXcgQh z8&erBac(lZr7w?JHhP!(NP{iwW1!i}wK>nnHxnJZCmW8ve%-3d$!jjO+Z|j3rCNW2 zRy_rBwG@5teU*2;y1mwK^C|d*&F@R}14| zsa_#JG|(ZxJJ%uz?JWPyw!24OV9<{{vP6zzT_Nq|w-ELEs7h^CnSEWbx6EO32G`Uk z6pyO$vyQf{*QX53-@fkeM}D+3XY$>>+yq8@gjcklsIqNVsJL= zR?ZnyunO#ih}|bu{V79kwO6vjP?4^&A5MXPp>xmVh385#;bg1e=3KElJh5; zkj3QoQ|<7sNE~pDtK|NQKpwuPdQjH2Go7iY+>69@Y^84Uu=*k^TQrzna!+dSbk_bc zDs=19y@a)SuQ$N_F;kt@%}r&Ax@bYZNA2*?XO>E?a6p-eJDZd{yUVh*%2Pl{^OvTR zVt5nAz36Mx$*j0bivm-g_~g%5FIiQ@dB%0Gt-2aWO>F(*OkS2;J4%Iy)ciqlr#^q{ z-0a8VF_Qj%y4gL=fwsP_`$>BdQ0Xe{tjxL9{Y`iQ;@q;cF1QQQ->)W>uxm4GOx^{K z_2vg=6YJPieBnX^SAM~}WJryYXuLD%J@>wz)oC(~IRm1Vj5i)Hnzh>f=zz8t$HGX@ zvp0O>vMcD2{l(M5S^3Jyc!3MH(nx2%WxqdDb8&-d%2Q(1|9j=ekFK2dD1`dy{b}aB zqls0Ryl$!5c135K;8AJ&iHh|0?c_b}cWT*1qK7-c0s`E+4C6;2 zWAA$*ezrUHm;F}8f-w&C1I5VeRbMk1rHnqHWXG|JPoAIa7*lch{wKKZ;aiW&!5b9i z)se4T%{pPXO4LBy1I~a;@d72Glxr0OS6oxk)n9CPAxYdOd~Lqp<@uNHwiCY_R;7UY z1X~ENH^3=2Z@(OigN?Rn(5djWCj0I9^Qp4>9FP>BDnLIly@znxNN)$yiVWoF*kN@0 zR9^n;yzVOY9>i{63qSC)85aRM!H2tFxVwRHbPC2a0R02_mNQcr^&cHP4(!)t<;}zX+%7Bm5UYm5%H%$%D)0vs9=|( z1g^Vk#CEorOsJ1Ip;}-vpY(GFLt!)IYmIhwZ~82aeq3Ay7S9B6tRZalt2du0k;F7I z!WS3PMZkNK2x-Y3O^smXfQ{-;0=Uieqmis^5LlR_9rky(+CKV2cHHncjQX>vt>cV? zH!J4&u zrT864X(yE~KpeS{N)MC|DhNTdGgC;@M};OB0(p2!LG9iG`8)hbLp4|naTa&y_=s>O zr(!*oH|7#AjPMwA4Jt>V2XG!4mz_d-?HJDpyb({UZ4B#VIbC4pu#>dQ*Al-BLE5S- zg>+nVU>SS-G3a|!PP%R@MNFQ)GV&SRpGxs+jNzu)J;E;E6Zi-b2LMeWHgGkF+8SKK z32(1AG((G?$onC;2qZ>(1sYGAAWxbX$kIe8UQnNIOA|)yjbb$V^eR*}P&gY6XHs0B znY#{|5=uBH%`=&Z6P|Y|kvY=T0bStCfS*+&t)qhZuZqG`Z+)Vmd5OX!5qOBLV`!vN z7)5iHn9jr@JAtj^ni7gQ*YRt+gd$kS#BYt(CS$emVipiQ2l&;I<6>6>b}GQLvbe}r zOBS#W?mdO89zZKLXGDnnypsj78s+D!A=FA2>{(&2xWcw3t~c=FfSRlzia?xo6LV0! zGh54rB9{-VLgXaT@0ZBsO{i+K)bU2G4i8KI^yFA7`AWp}ys`SBE_94>B6#yu5vk-R zi$m)j4NKQlfz#>24!%;`FF^2P$+z6T_FkNpo{|7B?`bxm&dxeLK$idCjb%Ba9)kIEZd6n8`2kk-~keNzApte9(<4dY7c2* zb)*NtgJ0cDi@;*t!#0NXawrL?{A`?u1JNZXkPwh5IwIBjw!;x|5$ zah3^E=nDie=yUlYxFkXr4gG!%u2w`{kd35SVZqC0N7G~D)SZLWv+^aB@?&<>)(oSdmM67s*Wvp-KeKcwu(YkzC?{lx=yHB=BTo&s|CU{>a zt6$v%49hX`mzGM~Xo$Ej3&I%2-kt+V#ak@rT3;-xrQ0fIil$w3*cIkDkleQ2j=gy{ z(trJtKliz=+Q0?iU@zd|p}nl9_*(wf$V%QDzKzBHn_sniUVv9jB%`Oy^*Y(rr;w4J zoE^DV1vgd1P0f*m`fBaHH)Sa$UyfZVYbW5w*rFt%>)gmFMiRtbWGWfnp)|0Udefj| zRqM~Pqu7L8FmL+Iy@pw1n8?i6Mxc?w3==s^n|rsMb&m#gjdC8`&;(Cwv?naqO1aQe zu@%_n>zNu+Lmz|!_z!Q3HJ1YcnPeM<08PAq-RRX#*@VVZR!LcP4l;7_301akco%3B z;#ys)Vo_7w)}Q+-=#!2U%{gy2Jp^`O$Fr1H-kY^H`qYAvt4WM`NQQTsU+hUbP8g8s z&p3!Ke&N(oD14XR&i|{W#?0*!`L<7Lu#bak$g{#Bh)3hozTw+HqQ?c0_-l}L2{%Jn zV|7PbM9B_WP5;j;vwk6O{J9rmXnnV)`S2?+0x66J?kP;gUt~j#-=qt8I7`{d%A+_m z!re-x;ts`Ja$FxdB0fjQ=}yib?KOvSkrA9L`)R0tZy$Z;qJ(qvo+AVZy3ioqmvtuFtD0;C~!P9A+sEXrgkiIA9p zgTUIAi%2inVpAnPw4A^ktekeIs|kuSLN8fgD2+ugVm@AnbePkFNa`$vCwg{DEwaUi zYo0_`jakm3Z_{&tc)&@7RlVckB7W1@ zcn|Ajd0yGkT`X(&?LoG0(yuIB5Jbf`G}Z!=b>NZ_Q(i)suxE# z?<@tEV{h{oCA2n#m7FXRjnuZZ58KoV=L)c1?=@zA`jAO=@e1VpyQ7Y&P^uiM){`aB z!)7b0T;d-=8E;Y~#iG0Rc$7)$jz+Z}F!~b3tCD$@VBcm|u=QPlNE9Exemr6^jM8KZek0v`Rrc(s zF@fz>P$gERUa!w|{TTauOFg_DYIoWz0cGO zo~=)^%itXIt)KCDASRuNlgqVX6S-D@4$F@nmqV$dI3tAw1kIF4*u<4CIf>o;N;M2$ zkQ(7QinFP#R;V-aiUI;wEY5Bv1LQALKd&y`i)2i}Soa)3&!zDcxOV?pJ#(KmGaH7? z_J`FS8m1C}C{~pkcW=KS!AaOwo#xdr6O7}JvTRgJ+2srR9^=`li5%z_w+ZY&Gg}?B z^=tW(tD%Kx@ZxjSI2d3({#Xcato?;HigPZR6Yi$CRMR_dUUc#Y_3Z5&5?IQd>5>wk zex%mZx$!wicSuJo3J9VI@=8!{WoUAhv5nNor5(YAsC~1WlWU{_pIqh?pmy5$DWe`n zo;7OebPnTgD@8k$>dq_ik{vgFY4JIi%*s4NLqANn`2pb%!Ar+msak#7_;9*rvT|eA z;cp4-f?qY^rrC*E2GjAMEBAYO4cqFZOK^G*djmqXtx z6Bhw(lGmR-GTN7%NVw+Po%OvnWh2GRcb#9{Yq5~(bmSkolY#!#p1d%hBiKiaw=XW~ z^93GCwdO0;Mvi-}=ij$v$)J57-!!h;Q_FNw+J;zjs|E763^>9@)SbowzPxj7qFXru ziC$*W06ZT`Y+A6C{UkvCqHG>VTv33`6ka# z{J3bCAwhvAc!mmKk(72|_0SWKC#TVuI62$Vc#gjyI$XM3g<zLCt_;HU_ zUw$m%EN1FFMUsT5Q0yeX=5LwNJ-RFw7VLIP-`u6<0sPn0tdHFAny7bm5Tkes=ae5MG?gVM9JW-I zqpIO^lE;h7dc)mQ6+j)sBqf4ja1FEYtDgz}0~%hP=c-3K*#nrU_2~A}i18@8tPyn@ z<(e=pooOQ6Bn4xiMi_w$N<>7YXpUhxOv{5m5bc&8HdGf#J9nFm)8|cu?D7x7l`w~; zfKY=quM!~}4Sbmy&VvetljnxtdyyX-KC4TJ3;uAXNOI+FQlcwC6lR`oUr>2arR(79 zt4GV^4v~lVC&Z72b6a&qeTNO8%zQna-F#sztJt%sp<8MK4!wCcr}>Q-5wYmn`+482 zUN2v@LD%B;^?oI%-6*>GE4^G4bQlV45dlJc*%QrPhM!~Z+<5Ik$@LwX|8fC%&I88$ zMeyPrnC_BqeNtfx*T_5JPC$uL&gyryd#Di{evuqy%I4GgD2`&0F!mkURYH3VpS(NZ z!^dI=HHg?^85-`PeiGuxf7V4Hy_m9IBNV0?9+k4grZIKyQLIv0NZqdr!@-ILA#z$? zx%83Big?1U;I>lfUS#!lC52P!nbz$-5;QgY-RxhLE{x?%pZqO&k_XQ37qukRd!Hme z09b(>I#yr7K64?H6LiNma@&%IS^0E6B2(LrYk-UbtQ$Isi0fAnA3NLkmv2xBhA0k9 zG&!St`vayoa7-M?p56*T|KL-(%g${Vep(NGpNC|qoS;gKR{eV;ZA#4P8oT;MapIf{fhV zQ_CbcM*br)UzpQR?(K}E_1-I5hv0Kv56@dx+p&wc@{|BYHaND`FY;8{j(+L$9=nQ4 z#Ie(6&Blhq(WzG!TViNwzCEW46vbXsTs*UUO+0{(k9W&oh3Yru)th;BbVmsVF?@0o zyg1BL>~}RW!9f*APM7b~!{@88(c#|l9a)1GYkvHB-w3XT{y`~WMbUZcfy9RDG!J>Db z_40BGm|ia_S#{jm>aY{YZPMt~JxO&UcbNd!ol_$+(cl$F!D!LyU3FvI>JYEY3MDeV zM^|Afv|$-l1L##=H2P>ub@BJ4!3YujhfUGOxIC3eqQ@-ZM6<{#@)ho?f^I0OaAm_OANNTQ#NmE3f~2 zF;-Sp(iGY{gkvx5wOCtSYfKE|J1&#zZ}C$r?5jo;D-HsDhRp?|-W&_6M!VO!FNvF2 z><>bgN}{7CZaM4Neg7;zH{%FHrRg!V+Vg zh4I_^i`!0Xz3+@n%e($`H>OY96v6xy1KAU4zlvJg@_f0`zS*8ds^4c~Jh8>q^zbe^ z&0kTBVKUv9^z84G*@O45Rnxz?ASE!1Cm`zv7bf#O_c1x88Y4O6_4a7C09fxYz;THI z_FxsL(T3Fiv2__v?A`4-s;t}d>_~7GHOHCP_3H>~y4f%f_B1RY-^d8H{k0lh7#WyV z!O5RsurGO+xx66gHhgg3~%{$Huw)0m~*H0(JT^i|^F|HUc zbM?Ya)Q{kxQ)8INamwO*A?y0TUr=Y?ZgvZngOkwB;=9^8a$_e2kp3cu(x%AsQzwo` zbp zmf$P(u6pj*gXO{%c!)Bbdtg~PC9&AO?PoAo85ZnuEXsc_QlD@60=y>B`2sM6XqE-* zLD!rOcAqaWYoNFW@3%+u7%UanxK&HxMY@#fhj0|ISPKhWwq^^rjO9{i7`EGy>$XI8 z7B;eydSgJP>FN_T{$A(J;$`qP*(39M`$Mq*H5FZbh$-D-FxbbdblD^poG!NAYxqF= z>6hgCAmd*lo}6( zj@(e>sr&fxx14D-Qy=bSUmQ);MGn=l77}8o$j6?m!1JRaE)VHmX3ZrEeE63-H&=t!rlL4E zC}Liifz`A9;&3ij>Za|cMF2jBN!pm6>OB;QiUptrI>(J;@b8U+;=T>JygzikztRVo z3oMW)zVEo<08`3n`iJv<7W^sp13vNJEb5I?LV6pOTU&;~3kS>=zdygfsW-*&1z!If z(|~)%o=*}tVU@md*(1i#d7HkA#-UG!FbSEea%p`{XCx4M(j3zO1v&^RhtN>d))2y} z(p%#=Gzp6x?m!KlhS+Nqa$h5r+!_81=7${NRQh`6lZt_}H#zk*{UCL&Fc$29(ZTflRfy zsEPX*N`VWq6qtm=tA~73MoYp2QB`4`^Js8?=AOb`z zUYEZZ0uIX!EOF)SVTy`SlCt<-b|JFePWJaMQoevB#PK4KJ%IIQNJbgp?Z!oI_uybw zPTm549ycN*UA;Zq@v&8NEl9ADLSXk|AN+BJUhWs)SVMW?W5BB$10#+aT|Ng!1bzV4 z%b@OPxYZZ3h?)TXn4TVn6uiR_xzavUVf{_e=uw{zNRz+x>^I~%C4gbV zTxoCZV#E7$L8MIp+z9HU!@QGn!22bB zSmti+nb@UG-I2Lkivrht`V|EpoKOAlB(GoJ-hxH-d^y-aYydKKUt5L`$i&bfVh}7K z8U3n)zZ1CTvwe(0-}IpQFV#wlFJf;jZ!gu*fX%!u?p-<%^}H~v@7>N&YYuF(l$R5P z=@sFxS6ZpczTKTKm;$G$XWJPE6WLux0bP&L9Pr@K+(L<comK`S_u+NN|j1aAkv3YD#FEi5{yc{JTL%;f)M? zgUu(aP+feRD@+QaycNKk5Qup6Hm*#fln9IdlH;KcX*B76TaiYBSq=i7o(#?comRgI z?bnAI!YJn0PWaBHUY~#*SiPM>+Y!;=-Ki92GCwdtdeF1+a3d|#NTClfPZO6rVKy4O zNm)yw$*O`;PPNMrlii3oRTO&I2OrRZVh0OmhR5QNiIJu6Z8FMRJp|H;}n)xVVW>qvjZq$ibYK7qwdA;$M2J8vn(y4*X#8(r~w=&rOn}4dk32?GqS=9u7 zp*|=*Y+R;uQ4{|P;Ac%Q^)rXO0>WR8-9p0T7Tc&uNH_g?a9NJA_cd6*Pd3G|V+l`< zRslOS+?Lx8q80+8$&P2d->Wdji2F5JK_MUj7*tCdNUdZJNY&Ei zK2W3`=}bJf*LpUUjw|f#7-!U4CGZ*w9W&7MezskEn@#a7Vrhn@D>9xyfgc?~0`#UM zgV6g25H;zRUc88s-2VY1s`(&CtODx<{;oYF;o$lQrHF7%JLd<)fvt}~+l0R|8t|32 zin{pvFE-_)d%uvrN3oFBH+dQPcuH*JqskGoEz*Briy6kYS(j?>Qm(KPDIRIrpGhRB zP&QZ5P$oH4w@^;%cr7WHkYl3;0!b1FDM-d^ks7LdC8KmO#i5ayHia6JtJW!r9@5m< z`y7jtsjwwWtzjO%bU4T05N7x?=+P317#2N)TF5vg24(`2YM)>3=6P;>t)|WEt?z&De0v`A=K>X8&{1aoLUNwl(LHAuzuZcEFoKuY zI1>atX%(~j{Mc zOInMie0B^SyHc-+nLeyOsa)hvx-1wIZ*me(bvHF5X5$0{I<}=5M|RzAg^wA_qs+pa zTrhRA2{*+SX|n6>BQth`naNjLw192%hTF7twtJDPyuiEaCa34X7pBi?$}62XXa<^R zA*OwMIt3(k*o26@?*w9~6K1q?S1#4+@)T?&wQ$bKu5JiV+!O_<`pkx)1N0G=!8ur-Bn=9ocgk6FD9 zvjNtGy|Nk17&!>@Vya(;q3J1jhNCLAmf;)6u-}RkNSoww-Mj%>ouJC}x-l_#jT*A} zZQ!PUDNOb=+CL!gQ=BHQ%9O^{*f# zl{r&gX--@8!(?6@*BX}(>nCkLQPQ88kCfbOWV}hWOx(I^YXVM*2=Z+9j5f&LYW^HX z)T=EN3U8ml<*`!&gsf*WD z-t+K=N-y*3Q#sl!%1=|`1dp6@|8jzR9THBxSQw1+SaQ=4c)o8 zD1T6O?XV?afD)cCLBwE(gTBe%L4W17^{#e9qQQzNK&qXOVpbpH51Au4;Lf+y0JYG( zb#ll?YvQ-#bkw#OZNO3GC?1Z1U7l4-$3k+&9*{I`<^TLdy^Lje?u?>pgSxxZQ!5t; z1XJ(pbuqE9jvl9qUxsPZ**wTmFzvy*0!vUGpz3drOb>Pr>`(~ zn&6~6%K^T$31&!3ttMm$*Hi{6&BrtjD-#gP_kwWSb`I!KX}h}y*#xE%^JX>`UW-roP^zgj6jz*77>-^J?Zd2D!srn_ouz=I$7-Ft-6Wyv z&xW>nTmjp?n`2l>Y<>#-#UsKbMNTrXL-fb@t&aOKbQALvrv!08rFnFy39jEG!7 zZ8&q|#6hHg^+RFgK)V}~`rdSb>X}trs$7`rh592K1*(fdo!jGaV??%<`32^WeD)f@ zJ^x00TzxZ&$!+x3)BDE_k4>I6*~Bdy3GJV2yj`pSAC&t$lIe8yX|+BY!tlO|^QzM| zs%HnTo!batRU!OBFV39QNMXbCoZ_P`_*bQ4i0Q`hR`G=`cgzgY&e4sARr1=34Ms%x z#<>tbgFSmQI^wEHLfl%*soPEFFTN&LF7GZIB)Z06bVcY-s~8yMYV)|2AYF(^G6NBg zqR3oGLoB@-K&kqd+p=epmwkRk0QKCSx~tmw!ALh>fB#Sva(MJj8y5TTJUZ~lJp%m& zVRFY7noA&jy8Jz6^|6zdDWOkUj=DZkUj8lPFsg?`U&c=N4i%0q>WIW(&|wh?W~`Vn z0^R_$t}aR!WaqW$_rp6oGe5m-hMXxQZ9uT()D)l_fzN2$IbosvQ7^AUZYf)SgW(CwO)e+g%G1 z*^`J=w!;ocWh|?NZKQCI%1KskbH@oHzeZ*f#pQ9yS|XPQQWgEt078<0O!yw1$GS{N z)RL0Wk7x7n>V3RVP!?K6*T+XmLVkVm>(5iwv1rPZF|3X3T$H$5WT73mMPiWG$L@ug zS{2&PHdh;LI$Gw0M{w%sqjV_9$5b(BY?X(TpU$@*?vHa5kZu2|7PN4$8iMo`0iIO{ z%`*%4e`;do1gG}%|EeTSQTUgV{pIdv^+ElwlVAM!mkr_N3j zr|Plr{1CJ`K8%A`f+CcF_=~oi?c)jv7`T(G4W5m-t(V{ILQWi$egm+`PS{;?Ui-gD z>Vf<*LsctA{@sLork`%}0tzs2g`+9$Jf z2)qON;2kg9M_$@j=CBiTd9v&Ys20_;MTBe#F7xcsKC3ea4HR;qJ$>pg&TGGOZ7th< zFcLwST%VTs{6V%}I9zKjSt4`Us)^-sBxW^v|3#eegyj@+eM;LY&QMfpIL#$g$J5ZO?xYAmQ0-Cw z62FPaPytefPYwwC`?B4X>cqE%m3NR?^Ofk)tD4blN$aDXGh*GpEal6^4`1E8iD<)U>;NcYrVFBWMX=RF zRa5%1Xqtr8uCVexB0-)%;bzf2;^Fo`s2DX&MfTH^DRG5TdCzx3^v2x?<`6_rxNSOn z%AKz%J@BjbK;AnLxBmSqChs32cYye;{3GNvD*MR89fo(&7>x9&BPfW={`(h&ow$X> zdXV4D0?Mn;p5N~pXi{B5?ir%Uw>{`;O85^DEyd#14?#;c1hhgaYSdiubUzim(pn2O!RXX z6L=v_tJad>S*6uA661DB>)%@e3U$#ewoL?~t*;sXC6R4ri7$#q$dGZr4>6B$f0_#+O-d-L&URag^0&1t`SUE9QULXAku#Pny1h{G4V5qZO>=_WrFS|ByVa z3Ih;wI{+Eyla0KO;(@_3+TUHhu;F6R9m%}BRamo+2c030aGC66jM^d82|CiI!24#a z+e@@d-Oxk31z1dRTdNOBV!8^{m&bmR4FgL0aOWm~D{G{<&a2;DYCf#}8t7KR3p9QW z(eRzTP+aIps|Fm?_bOx?{(Gf}iR?Ug59_5kWp`lT?HBl8&DpIO`>ilDyxM$KX!`?! z)sPxo1e`4f&8d0TqEDTYI?vs+|q;fF|uD-|*VUcu+}hq|psG@B5!*{wjfJEMW(p1&?9LDj@zNB#OL78T9eN#%YT1 z!!&Fi={=Q5ia;QppY{mgrG66*H2>xTGy%Mwb`(uu`uf8kQ?hH%7NE}KR%|i|t+iPt z?YR^d%QDSiU`P6uTM(5o03A6IX!SYIj;}Ka-bQo=mO!MR0vR^&pynBb_1!Yfwflupt+FhUYx!eARVhO5n%Kc5s-IP(t+`O*XW; z2hmS^fPut3pa#71H;wY{Et%g)@y_Pb7!pDUGEkr0HqeB4lq>zJ*;+e&H@Rd#0U-jy zD$RWL7|1uupCnbK9jxa5@rG?_AXuvXiq^KIMt=NSg2F*$wKcU)Wz!c;sV(hkSNm%F z2I2pfmgw9yR4^6_B~lqGsPvcm<@12-d*nJ|a3}*wm2+!e3|L1PONA%t*_*;y30G%N2oo_*W zYf-)ZQ&>himXAlPbEI=W#amzw(ox(yPeY^6kcX5BmNU%JFa1J}5b2+wDY?TC_(_QUp2s=Y}jSDjm6*oz{*+k~x zV1tjKnk7J-=P(bRdQU^;p>6xqg++eXjR&(`9naO+CBnoA@Ytg#pwfmJ`)Pm-OBa&i zN=xybTCg_Sooo&PJVxlBXUKD)P7+!|9!AX$jS7aIxa&`-CxIj)xqZbY6b_P2p3*1{ zd5&j}Nj8sj8L{EuRJQs&37T>DjifvEg2N&-tN8pY*!hlH zvTn67ITX0(EX;ha0ZcP6>zCNYN)B)%zH-U^rJX(d5a@8Ovxj4o&niRHz>VToB)Lh1 zRc#UiV&1u+6r3;KeAoAkNWc9HW{<}&qBB(uqN*1U}P{KBV9xjSUh>i zLyFY-Xg?2n3^K6~JY7?QskOdbJgQ8g&qfKd%fps#%?MS*c>wBO_BQE}g3+cTY%cFt zy*EF&pede}0r^s{N;SlN?U9{@%7{9%`D7RU^K21lD6zzphvN0AV|EN)MIA0^E(ega zu1>rFtF2glkYb0~>9wjz;H zQ0b@v$$MJ>WX3*qeMUh`1|uQbuOdAR?abUK9fAN6auNE z`!~P8D|l74(kycSag%1`6^~C3mLs&hipuH!%orYF-8@?#y&fK!7nrr=m4B$58{o&c zLH}@NfG5 z#an<$`-jMHg3-F5B7AW68ATNOtRWgG ztTCs%lIl%MSSxP)gpKvIdCnitiR;9ryDf<~$0vr4-mf;03pCw7?%RZF+%#s}D`46} zUW-3)K7aAn|JMTB{T0q?($1qa=UaSzOGV>!cGzUs4O>3DaPQHNkfc9UvrbMx^>e=4 zp4A+@9dYH{8pu$=r|B4jnG9(O33zhJ$mC1lky=vjZ|VN~1Lm?>1Y+b3Zg25|q}k#U z+PZ}J&}r1E&^ta5A|;ZnmdriDSTJtDJ3?>8o%2}na)uJdklb9xd9Wv;BT`ouWrWd^ z26{M^E3A?FS)_*b$&>VyXUQ4d-Nxwe3eHoYcrcvii%@MF+(=R0oh<1vE)uEA{QRDX zP8XJb)W$hp(B>;7ma=3k=rh<)eDMT(t({UyO8&d_N3r0?Lf`-}1xt4&FT+=K^!j{C zv}k?Jufu&}O=hynp)WC%+`)76gzQ15LZsxML(2l~!M6svtQWsz=7NUKHQzv%PmqA& zQFIrn9*;ZuNQ8$nkLjl2<_-N88H?xJTT6vQ{W6lih!UeFj}#|Hr(V}NtxJD35KHm= za)v}tSB`!N=_s)nA(}5Cl^7k%pkYlGqPm6Bocy&S>Rq_J@+6otY)Ef_RP4S!uX$vA z^-BVK)LB8N*H{Mqf|n?feK2T)n)qylVGBJSaR&( zF{vY>Uisr|)*AS5{faodYZeBJ2m#e)I6~!|B{dNu*5#_ny=LpYmQ5sFU$Zii4tDgP zvF+OrQo$_j@GAkah;kxprouXQZP4c})}ZKB*@e>PTpJz-7mP<}q@KxxHAx;#prLO& z*bd2!#}m*o1ZLeSO?p|uo_k~W={?Dmn-#g0#+atBx7X_^(}Q^jMw{y1Q@~n4BF;Bkq_W!k0OHQJ^f#Psq2qpLJ(MHDS@(S7;1J z-1Zk6pmKWIr<=@p(NEV8YxkD5Uj}{o&^NB*-#wT89~jYU>y@vBuSX`Hk+(xtOCXqWRcR7Inf3Duw}#{NX`;Hh_0_>?~;q5jk(G9wUw?@5@=C45zG7 z`YoO#x`7YxM1ajWVG(T}8?J!)jNE?&dJ{sIi3#NI%bOrdMF4UDLABeqie*KlL?I~d z|aM<4Gp|mSL~Th9>wX$Z$?wW|8kU)Bt3U_445{Yr+{1?TEO}(Z-%$ zD>5L2-p?ijZ?#*QmUEP$$BxSWh@h2-nEeNv2#KkN^n(QubD~s1%Y25MXHZCZ4061{%AM1ZYT}@0|7=Uc<*LL%34mX5f+Kt{i z`#%eJLrNx;RsiaT4-N8GgTBg!24B{Wu~A$+Y9pQ3p`Kk+s>)u$DM^pZ5L17v?PZV@ z)Ag%d%j6afEY37Qr?>vIvZw!CLRF zoHBk=MA{eG=u3??gvo&3G%zOz$(#-%9YjDW+w3I#G_8d11TO-g`b@U# z8pQS20q|p-?*fDBtf=n^=^Ia0-@H-kK)_H0jY9%EjD_Y;tjGb{x*G~X63C|mi8Mg# zYk$S&NN|Ac4Pdjs-uuYH5@g!}IFREw5I(n5@?8cc*$|nn-Jf=vuo48h1Ay-1b>C;m z@7erYJB)7Ekkt)=K0}& zwwZUF|7I%y_(HC!0!kdfimM_3&zj^QYcYiieUKAm#)ok5n~CSz(C-Vyw+P_W!*t zmg+(1sE**PPyh_#H2x1tEJ+>P&;JH6B<;U&=a2NSSC)Ntn6$xtIDJ8oN$<+QLrA#l zhf&LJEy-6X-nSEBQ=PBNj!BLZgwzJy*v27aQfCCCy=w(OC4M5 zlOv76o`nA(&fJk#eX{>I$h%7N@gD-Q|ChFWxy*m88bI?o6UpJ* z9DMn9+#qH4R55G{XYxQw`f21J;NL+d|1i!#x!cl`j=?~e>c3mi`l1G~o#KDMOJm-$ zr2Q6v?oMm6xdwwzPC&{xV|lS`f(B9ynpJB=*SPiV55i{#|6ddn|m=u)jSodf46y0w)B$e3y650P@w+!SP@EH(bs*fD<`Gp5N*n2&f11E4 zE6dor29}W>JNx!i*eqtLnwTL%M^*wE+-Z7xaXRB%ZbxC4_?GCix(I-R+gJ#5Ge`yb zsZU79|LgBZ`e7$QHX4J#eMdVkgC}DOG-q*ea?`$9v*(Ej2(4Epo7`#%$QmhyU%@9h zS=Kcp)wTXOViBdv)%wz7QS|mEk4|(ken`xLp=gDk}>E}Ws;P9D^Ri=P_hgH!`tW#yFb4Mis~4&dpab* z!HX0x6JM2pd3|i>f2%YzsotDJtl?8#+M>6;t^fviD2KHv?KR3baoki37No&%Qcpl4 z!ta|4m0rI^B2K@1f@I!Dw<8gmmF`cY9wF!`;BM!U{Cq@^dgIMUQlz;6K_;`I0xH^T zcR&83uIv2KK)@vrPHEdih($L5O7XsDVhlg2_mQg!g>=m3f1q|sQ*f@)GPV4>+X9jg zIQ>5~SZ(-n;qK-kfdyhI*EhJ^M5wW<_s-w@vZG3!`gQv*U#Iql>789n7HQ*KCq}ix z%p_FROw9GgYgzIw0P?yJtY7O^7c6|yK2Ea9&q5R=hx9f3At0L0MoF z?hAZ%@xs|&f>fkx0;Q`+w#FwF@U7R_azW)gL~)!1Z2sM=k}j>AyP;1ZkyW1qo*V03 z*dX|zO7s?rLd*@+uky8@QWfn0mSuM~YxxG^N*mC476I1Z0t`fPRQZaZ`%D^VT#bnKZk&GM695b<|*Ccx8+$z=Yab5Mhc+07Y0D;}G zE*1pR(R$}crL;Z}41)IIG-pm;XI-PB#>YRi)QUM0N~*U~iJx9_k~vJ@VD;{VZ&>@L zF*1{aLHw`bA<@NxM^s^Ok65y^SO@*nbY zjgjUSU&Y`BCzTR(2DY0hz>PjZ+mM#w;h55(?8U2+%x`^>i^ z5>Fpi-xr+ErN?9i4hO-1II;^8zu~GEy1t^M0!VoH{15itN3@N`bYnG%WCM`00}i)FZfN_m>*l2#q_pn`72W6zvVq9iQA-tu<mbC_54q!KQr*^pCCm9wcX$9Wojz@AHv={9?SlJ zAGh}&k&$)DXrOG_!bKF3yMdBK_Q=ZKv$znM;mYbp$qHF1Av?0k%Ff>7_dI#u@B97v z{_}hI<9^)t%{9*R^?IGp<9Hs&@r1tW6{Psz>BzRHJ8@VUEFq~qgXp`Xrl|>DroT0h zdy|Q7$kcD9UtAjRk&Fek)NeX4INC6=AG8U7aELikcxYM@-nlI1%Y=~lW%}__!`Ec&&#IIY0KshT#Czd;ULkQ6|Wjx;BUdaPcePIbrYsf?R z;u{lhwE#bDStST}7_4DFtHZ4Ke(LH_EWr_U7pA};;9wDirRgO^*&9cgoK`7f4pw4D zaGm~Xbr_XyR5uzEuremfW6&*qHGNN!p2%;GS9SDA@-S*#pl=)G_J%|3+&(Nd*(7y{7T)L776u2+;QsAt zdA%(6Oz22A!s!0!VCqyh4gaRALf!t^MXxAxMQYZ0J>448lWeZw&~2p@l#i3S)R4YY zog0FAEfqBC;|D53r^CKxUgCU6tA+oah8KV6hCa1I^HI#5gvb}msVlFeosn^=BP(05 z4;Pl~acgm6mRJ0#8l@i#fN*_zssJ5@;z}yunCDlBsgMWM(VcWR*N_R=%fEW zDH|Evk=Pu>BGkWo%GUF(HGS`u!Y1RLCWQM(oWx>#@rDJk=eQ1-mcns4Z&P!75Ai@@ z@RI`dCBZ09+32Jad1iYY9cLct5mEexzQ^nVEWH?;43K+rBungE-sx`EoIykhin`{< zkzPn)c}Hp^{W|_GQZoW+cFzUFZ9$>P(I(%s%HOB3aYf?|Jua#w+KjjM=$A7*XPa|R z1WvgD;eXQx(S);&el7m041%?D@_dRs27!Me&swAwUVXeu40sE|%KrC@9GiC%J$*B% ztOtti1>9dn3Oeb`{?YAhtnxuEk>lPg|IomW{eCJ;Zba-U40SITRh>>3m3h>SZs*Ck z>draU>sfc3f0s4;ww#l`p5>T7uB2fAiwBGh$q@glSJAi;pu>B6TPq5Kt1# zD}WNj$}@ao$YST-zX}KNI(G7;ZNMF>I(z4cQxudd;08PCjUbAnE<7spW1Fn%fI_lw z`87hZ9poXwQe=BKizANu5+&+Uw0Hh8w0r1dv|pVd3`%dy4c)oAB_r+Ye?SrjVu;_pSx2_O5{BMz_K z)}}AHug@f}TS^!phNup1u~WJ4)my#U$%{Uzh>pLwC3GBZfk5(gkQoA~YTga-`zGgeT2vRH`@He-ZYHkz>MC!bL(P`i^X}hnPy#T#p)zc>&vVJ;b}BZ84it9@{^Q9%-JIaxg9Wb$2=UnC9E12w(=CNnGi% zf$BGiNt|Tf+QhS+;qyAGF#YuaV701olmmv6G4%@CzMuNM66=wiyln zK@f;Vxm&C=3f-(<&@2MHh-6=OQ~%Qw1l?h+6Z7C?b4w`I?D?R+0Lq5I3;UV&sbs$< zgOM8)82c;}eAErTx{J05j!ye`wfzT2hn?VP5>j#{CFwzAQ?6m-Zi4y#SOHj&)OkEo z^mCA+xE2S`LyhF`tGZlITiyG1vo61c3(3LOA<;4$-SAa z0aM`U_AGZWKVGaz{ape04-2IpQkY8txJR1&F!$wg6TnzZ+*i@Hi=egvyw9-Zq0_=v z2#$^d>UK(YJ&LoR1=0>zD{Y1=nh;YP=mKIv6r$hvJ41E40Z_qguM+}P@u{!eKvMO1 zR3mau%g?5lv?sl&wrWp`>_qIG8fZ|#hr33VN*DWLn>X(+&PL#0o$&F+n%>G5>mnLI z&n|qkXwIMhrXa#2k=50Bti2>du|vsx47s&s7AK9wmVc*tHy5>Byh{^BaQ`kOvOPjj zHGtt}i1Fh@a=p&KZ=fq$Z9W$duxDg_8ALwrSxj*~*|~3sIr6&P4mP|12L}-o z93x!z&q0(ipf*E0J))u6}nJN}4zdQ$3Q7pibc3dWI{pCdnnsAK9 z!Zjc9z055S2H(^nfX+)5Y7A)U&Ax~1qp$a{G1?dyPSb3WopM%XP`z^y+WPm3Euchm zjOOv7YEMN8b!Y_rfd2SJu;__I#ePP2%q3l~@z^!vL{F65=0J9pIh!yd^?e-E4LTWg zf1S@tp}6fFxjG9>Sh@$|{Z+`xlp0fv#p84WkJ2|FdH9gTx+!E9Pn!$4qS)SXy?G0% z+)Ja!O}AsF_d zweOdQKY$;!J9OXotS&LGeCMCB9+;5!%cnIf$!6{=jQ4{6F55ng(BlKoqG%_F!d28P z;;;m#LlbD+Cu--AU_pgb?Vq4~t(hclZ3@wqs{pB`6r2I_OQuik+`hbB=MM6d)Fsjy zr9b3%WU1eK|5ahwdGFgh`UmU6_m~6jJs5uXq4%is8>4F7CRkK=kAxp>5+Fxy;0T4X zmBWg@$ZnT!!EUX5E9nP##7+v0R)Kw_lbme_`t*GlNNA(rg55T9n__$=<5J+*7aEH+ zLyOv^=gYs5Jd^8W$04|Y$FZ}7n!-bar`=g6Et*HI8NSD2Egt1Npr>vkLmu~B!15sW zrAl35B2{~Bx^aB5n1;oav+pvhk5mku=J(k+Sdt_1U+s^Mx@-2*1< z5p34j)0fT4i_2kWG^~Vd9DrRF3l`Oab~yZu9Qh!sDTKg2m}KngL&Gr%oKrZ&3LXHE zO~I;CgovBW*^23QD#ydlvAqkdLB4uV(chq_owySBs>YyqlI9c$9@~d=sWmiQWt{fl zgCs{66qkbIwlevFcJU=vk5+@8_g&kxt8Sw11tqM|h1I6;Joighm@5tDaZ`yhBfXHF zS~3Qv`s&Ff2`Y?Tai{~$)r|L}bnNbHtrAm+r*w~AbXBTm;tVYbnq*Kf$dT(Rlc!W}gy= z8>2EgGv-h2Y>9g`ORY{YL))4GXIX1XC%A{3)COegPNB(Wd9#?q>FMf+C5D}1U|Miv zN*0X(w-C5KN1yVYN91%e0x%^u|1tr1n>7~^O;t23kB6~Yvg3n$OfXN)4?Qh=Q@Q?ml!#|g}@ z==b$;M>oNpX_?%s-Ut!%E(2cBok)wZ^`9y@)@iBlY@E9DZHO#VtSriiwL|&t6o)v%)#^{koU1lOj)h zodBf(F$5TWyzCXQx5NI+)7;<7i(}<6Sa6>fA=sVxATH#2l%HkinFZb zjzv3mT#R;3puLqEQ*N0&SM}OSpf>8J9FX9SADiQECGhv|*dO=$rPGjYmgnq9*^hwI zrKT5YgL^sMPIHui~y@s#bcOeS)`1j%WzZ9aB6*44LqPL%WMtqiEY7sV* zi)#3F=8%BOV>$)v0dfOs7n^OS8k{tr&-bXL`Z+#a#i)}u7>uKcceCFw9v*1iHEeWRq z(-+$!xnKP*Ur%tW^}$`rodDz>Mc`W7#Aka-;WL{4esBm~+{5afY1YS9O>RW4 z>D$ca+Bcc0qX_K6l=^WPeNK1sN1P*KQ)UR9jOlB?;)AN+VCI|72&2kA2cD^{<3YN_ zojeq>svMckq%5?;6vCAEzY^i3AlbF}fjArrr-(Qv_JA4%>PY0P$xUAaX6$RA8H|7H4xP8F_CAX_Z#SXz7;81*nl6n$KaCtdCshriW`0^t;ziWnT6baI$2Q=) zq8XJO!69XE>2jQ&bS=%pm7`AI^Y;ZmM88xs)7HaFmKpaEm~QR;e)y&gb|y41EbepW zUb#0)vVNZp9UfjwL=@`qT5^q_KNv#!u(rO#va5Ga-Fu#$6 zn$9$Sl}O#>429tj92B!l{Sh<2wvBLdKZ|Po4Eau_UTFG*ZCq`giZe&tW1`}hwVw)m zsC16k;4|oQ$JvS33L53$ae{NWeo>Tydo1X;S+eUCJI-k*e4-cy-Q>b$0(a664^>D% zKKv1?eY>RxR(Jb=Tg>hk zMcsScd0O)D03ANFjd(qj`W{!t=$%cqCBS@(3m;9um9;k%$S!H$DNr~NdfE$T@bbbKFAv1IEd=h&5n++^cMPtL2(I%Aun+lbV8 zQaGPT_Nibp_^H#@_I)n<++aY5i97zponk?R2dGDeB9PhBr`!IXLQi z>|skHaiKY}7f!?Ag(^{)v9OeMwg%DLc_i~`O@}#OIqsfw&72ce4;w{?XC-@_Aw{;`?>xR6PP5Y70uN=rY@PwO!ok_t&adt#tBu+(uynIla-h z#~0*@kyCNCe^ONYZiv>!YC%=wqKKgBaOWyeTByGpSBCgS&uz77lD&tz6NVbtEQ?0X zZtAFp*+V4uTz5V5)&)+|ny|mwHNw{NqVE-&b*}cS9g1%1fxg06VD>*^mZ`0Sur3}v zT)i809~+w*`)EFeF0}=OpzODT*JZng_)`@gF)a;7?R~F-?;gyj#_}59ZUXOdHBot{ z2E`Zp8R{7>4Hc(|8OF8c)3e?_d=4)&ub5>>?w=7>CZO~w`k||t3xOanL(HNRB_7lh z#Q&t^$P`p!{sTeYeYlr=Zquoc?|f_g+ILkTd?&TPn#n*ZZlks5Bu9!?l&?aR$yO|> z0wfJLZblws_;*4l7~T_^6Jij#%7keLMclC&2ox5%*Qy3dP&zrBKLcxYqgv1mF77y# zRpu9rGnLN73w>nPD3Q^KU0c1i#Ml$B%6Mf->9)SiK^U~oru9pbv8(HMexm_?gKf0D zLg5`ix&Xw#3%rffNMyBBFcYYQkSP)&2S8qq5T7@1|L#=er=7)?Ea;;5EZk^QH8x1K zzjMZr@|7~JN!Gj1@U)!OomY3s6vhkWD&Nyviu^_!w^;7a7^f=alGqXm>25@rVu%bg z4$(r?V%Te~=L7aFz~l(RzbuZ~Wa|mzgDGU!{axsby|g0SLW|N-@uN%9bHX8QW|Z^i8BHdxODAqTr3G8cFkFm+M{M%NthE4|Q{kb3a=FD?o zcrP-6hAYY0u>-L~2F8MWa#!oG4`+Z^-@Zq3th-r9()hurb6Zco5yT-^EEj_yZ_U7K zmsXcb<6||8EVZI>KX%X}t}Xl62_R8%a09)0b*<{#CuBi;1t1IB zVuz{7SgaWgj!V%3`9qc)SN>(@bc@tJj@`7D_LCxQvih+qG&!9&Y^7wyJ%!g5i6ng? z3a|&8?UToTKC`qB9L~r6k$)vY2yreUgizK43L#g?E;Ig^6iNX%FoakgFKjd8uB#D| z-W&0VmI#^2^Y5SPYM<_qwgy9J-z0_UEv9cjfO?hMIH6wU!i(=gr)vIYIJ7WY zxtY6qlPhzscYg|vSKHy0o=Yc84^NiW<#_dGS;0bw2?ZITELy8f3) z)Yv5uW?1~l=~U7M9!Cgad8VML<&dVPu?0}BnK%9(H%P!sVIWRY2})#c3mkmkz=W8< zp9{7yziX*iZiD_I66QX?3jaO?TuU^i#Lx{R*I$8&Dn|41*@!pP zN201NlS0Gp%phP}Ip8;*Y=mIs0mJ-cgz0wvhLHF`>T*rh27{bS?2xfh`{h3$S-T9W zAg91($8cx}xn@k5yj0m|&9MSEvBouCNL?7JF~Y`4drMU_gQmmposZG2QP`(3A;C{*5^8oMt-G zU%oS2wcVMDcd2Hq31^wGzmc*dHTnl4$(GiD;<|pZdfQ}+o?{o2zj`FsQvg~cxLZA{ zaOQffORyg0u|U#^^zKY1`;4OfV41BDqSinJJ*C6WcXxn{aPY%IqoxVi)AP`RP2Au3 zXVQG(TK0m3-V;P_7L9m z>vQ&Vz$(Xr2Fnmoh5QbXA;T`TvWS_*1H4&O$cwl^F? zK#;%l+>fRma~O8skAK|Tp9%gt0^tZ}f&yN*LnkZ>iSKEy^~FCH%pk5aB;pGRnFf)< z0PC!)Q1y03_!&cB)*Lz%iJMeetx*V3h%a3nck50QuCRhG+$B?>Xq`D{0k~t}%8mG@ zzPxa^8IeaQ(ACbg$^3CzuL9H062nxKGF7zaW`ASnLnIfWbhxGQn|Z{`7ty&dw3W_s zXg0~Wx-)xDqiS*CNS(2L^9|{?ewJ^o0@2c^wbLvTHVYXS5vg}@nrJmo3J1pnK*7l9!z;PCniv>OkM0*M2eAGrZBV$hqO{z~V(hT)uAub4nZzn~?Auxry3MU`U+}$dPO`iLW2p<<23Lg#jT(flfzY zCKc3B^WHF)TykI~=GzY`aNfLuPuRv3#;uBAYGg)50d;U=hbu5I;C0#`_t9y5&OZDX z5$05zli+%_J_j-j3f5%O!R4r8929NymmA66hdbJbl7@BsQyOIx>jCNvsxQ>3i70*{ ziD685Xe31qUmOX~V3nc5IFEX|5Wh5MCXW~eQj)8Y%`pQ1$?s&x=j2;JnfF}0&;jD* z>2x{%_g`m8(i`mkbCI+mYES6No&-p?x|<)zWn3zA>>f3JzP<8gQmFDC;_u&0vK;}$ ze*-943Aeq)GImZ$gy4f)ivfJo;G2L96nXS!jH4K0*p?}e)mISGHPw8s&`Az=3oBIx ztm*X><99%Nk`q^LQzAoc@OhDBy5Lfr_uvne>L{7bN~*xWe;iJ?#JLG)p4}Khnx8sd z&6nReSm9eQ&dA<-NqJ!I!%F3YtKAP%O)iHN89VnqAl{Oh9&=1}oBNSVg~i3pxB$am zA@d8k&T`E%8L+Sn?=PEz63c1-R~hm&d9*79fNm~LJf#a+`6^*-LB26mEZLI$=G8K2 zW*luxNkbU6oH_JIy*peD1PjpqsSvg3!k$v+?s?8|g(b*8gwFd99Bvn_U)0l`Ujz5b}rzLDL~l(b_NOqS&$+e~9$*(MraxN!2Zs&f}T z1!bZ-gn~^%0$Ut1V8xK{bqB*Cg#nv-(lbz@4Y6l|6Zs?Ftq5Xu7CDb{nZL7ei69HA z>7*0b<$W~o8ujOSD5P*iJlfZ$;}d7*;MNe0#bi+JDCid3ty_0zMP8=DNxBxb4)$tE zWktAvMZPF)J(Bn|$kU`kJ)Q0Pa+)q2b@16fpDlqth3Xn8vmr|EgAIh)_O?QEH(c_D zHK!z&oy_7$Js+P2_vNx<2n#OTHibM62kxlPSN6x;ilC!(?iVzTSD~a)VhH;lJL7`G zT%?7gfQVh^$rRjxDrTekB|~MIsk%uPeS!NbFn*rqT}RyVR&a=8=(Q#5h9$=3Is!=P zpDfle+z(ko**{JcRq$ZqzN+$0a%d`5qEm=l%?JDDEDG86XXd6w{B~T zrqaKDI+vTxqTbAZ{-wom@unP7JMfo!b2J~`Mp-DLLT|FyH}j}Hcbc`uIa@#cQGxDJ zy+E~HkG{dj4M~_1a!)eRkDU3!SFUX4iAi!yh`RmH&=|%Ds;}?#-`;`~LB0A`zh-U& zOXdwUU=wXC&(naBvHq=EGL4xYH<*=uef$;H%fW zaOPi>#zt;$0k2-6PFGFvg~43qPKW~TS-|iNWp)0m1kTl3}c&Cf!gm`|9q5A z?bRwh|Mx_Oe6dwDs+KMI3zqS4-uTF&QzVtNw(*z9=}SJ?O2g8FpALdR8!?L!b`4HyXQ1{ zk(MZe?Vfh1B;`1RDFhW>f*i7b=#hO=q*E1X7CAeq#hknYS*&Ny4c(8%noF$t*CbYr^qy)&33nbN zj_i;N_pm$_M*R~$+pliBj&R#pa?}cYClB{kw&m4iob#!HZaY^5aodiRe?ma)0uJr0 z^Y%^bX(jyjR>uNJpgbvFyQHn<4pvX+S8!^zKEo(UiF7`TQ;&Z=!0wWgqwuf?Nd;l8 zUGAigOb&9QgYzjkF#V^NOlte?lgX>cIm-rKoG2_CgNdiuo)_2T^~*voRw10Y;_45> z1v~4AyH=KkUPt=tkTK)j$e43x!(b9sUy{bzgzlSjqxCu>^9EZ}WPbVz=grV9)|GKy zy6Voc_2!Zyos-V(_>btc@H?gIbfYn7ryGlUOQrXF;&x%> zlC%qT_&oVgr8Lq4vt7`}45P#D)@P_<%%BG(FDLa}>H^uK*T{qKV7bXTLN=#agD|E|22^t;tTJ%R{1gQP3S%R&N~P?$*&oK-xl?<`X>1`g`F+yECJOxep4wTj8X34d`y9=)3G$y^`@feNUtS zivLs7_m?~-z^&F6aEr=gJ6j5U?Dteg@ZNFP|4R%=piGSnl02zk1FuVrR#oW+ggI47 zdcpWahg>I>rx>bxEnlit1YGwkAs_whF6&A?C7O=;MAU&Tw_+5-h^G1NzR%Lvww4dd za(eVtln|fEUO1}VyoV4q<=Bkm(5;DdO`{TG{+Zu+GHH=x_nQ1QdBnYfL~$S|?domg z)~X?~@pm!GH@JndFYRslKE(O&!&u%FtEJm94qHcyDTE1ZUwVVCpxAuq7#LEB{^WKDDk}ebeOYio=ra_j%`ly`-g& zM|&2JXVMx+O@@pG(Rxa{o^LKohVq$zvBN0&D?g^b^nPQF;6kCH+n~L-j1gw;{kbLE z(!C(Bz>Vg%bJbGDVtitjQNvS1>2Kh;6ej@0hcD+eZD(~rLh-BD3L<8Dul*YVr? zqBsTk$0*N?IEzF?p*D`{TY|&cNA_qd4W+hz(icDJuSu}`b#P{CM(wCe)+hL{D-El= zNdsRZl&7}gqPW-CN*(Z2(->DdPgxsi?yYU#jW`q;em?7*8=Z+Ff*9qwC;Mv?wb+g4rnLmZMDmM>#T9&Q>@vXAK%Dy z*jFB`y*1e0GMm6e>xG)K z%DN>g-^E01?bb@e?PF#+Xp%XI&OPux-AmQS#0bgI;|ywDgbK>YX_-A$ogx8_l!%GJ`C z)r}0}d<^sYXYWskrz#D3_a)5O@>GsYcBKwlURq@u|L$p@_${&|6s3Y8M8!#^_$ywN z&0QzRZ;LXu_a3CC92&Jk6YoCyDP+bL7HvA^#j|9)xu@xIR<7j*^3L9{E=b`Hbg31* zm*2R>az!y5<%?1zb6w>8%BhAK_>0xCAws#&xYRY!YsNskYU??C(0FL2HyDEJY)@47@F6>nChfzQ;kMUljqMK`M0^ zi@)(kCi@02szwowC$uH~dEICHYqC4$O1g3BB>O>!kWOu-nbfAT0{jv4KUV~K!yHQ2 zn2f$f6AA|fvxkaVd~T})0-N!aL><%z(w@Ab@lA;5}d5Pc>)cU)XzB;-E{e7r{);(y04O z0PISEY$$zyhm!lImRQ^{glQv5P9-}B0NbqYTD=ea1l$a`c*}~?NT|U}0vYkvgTUDU zBBCNs+d4}x67jC=1o1vQ>i*o{lKCO{fhSFWE{>M32K@+`k_{z>rg? zeUxzq4w;h;;qK<5B@j9SJG@2H-q*_Jak>}HlI>NWO}~0<1^O+mPrnk^`hWZ|4L;+Z zBMTur`2)~#mlrqtp0F(H2^mM$OsLraq^YhYH5KdJK&uu@i7rWunbRtoF_!+(&?_`8 z8t(v+7D&|;4Oj|7fD+%Rd+NJ@6Rm++pz@@} z0e6pfX{Rb{Kfp*U`Tb@83&0&YHw$F<0e50ce*T@0C-6SbP>BzK{^kji_hV>RM~DPF z^??-Y0f*;$^Z~q=Ga%N%^B=yOytuL&vIsF#=`Y}+swvqHtPNf=F4{YAuPasK-hi;K z7GK+^>aB_q=P82IoC31xoM=w;^6uRQ=c#R>73!lehee89yFF=Ml$2<{#H*)nH$Z~X zfN!Om|9uv;jVoZYhgZ%r*AX6)lj8WYjo=$j3 zbr(jN*2hYfz>@l6RW|HQ{(5aZTxbADm_?W}EW#V}u`QMy?Uw+oTToaVtQAuveV6jg z6-l3V5I>v}L|62JNX`fSpX%lm7_~@&=&nZ+N4X)Z9q$8)%I~gc_Ix#!CE|sY6-LU|t$~ zxdA)1g?EEQ1qo4tXz+F=ULY`a(($m$uP;MEI2f&V<}{}TP;ugrMG`C(eW&Fzq?mPR z-?$97WB~4l6)0xX5UWuy__vm2e0xDgW(KsO5D6MD9%e9z>bbrA%OrI9Hg9g?^LPae zjycSsE&>)xY+sH8v+kx}>y+cp+tGc8$F~QTCA-%ls8?R}%tb@Hx{fX9xa0gUmVDn_$;r!s>!>c21Znf! zdRT{ZXwvU)T<9b@b7UKPGG1JPJwYst#jMMJ`Q)#=;JhLF4Hu2_-Bv5bQo@;?ypogp z^+j8v3O?7y!5MMRTpU#HK<2CkV*a!64+aWHNY6)npR*J%w4qQ{B!L~9L7tF$#EQ%vJh&WpSkV!fCG#*y8tX3gp1Pxxh%lBj$Q%!CO zRmXa<>CpAtF+Xl`*(F-w)#ES#JVy!(t-DC&|poNkd2VaOCP_f@*P z*PZUodxOX^|J}t|AcPUuU%{l4j3UOl?B-RC9+rIeE*rN2b*|A_2ML=FBI?B6Y%%5C z?=VWEXZ7@=RW|#dVmIZFQP^3Rfdr+4M3XQ9s~57HBuA(0HkO8yB3TbjCW4gGJ6H*F zQ99_=vuPvFs9fYHG6k!foBKyYORc7+>RPY#*A~+>m?G@=2i?4T>F)-TlGA}0`uz8F zKDimZ%t@vg^qnzyJiR=+G*ZmPY(Tq}kMO#QMMa)_>+nBsW9n=KHKc$lrl2?trlJGm#FSD7eTrOhAL07=VNm6m&tC!(ju zp{cTaNC&qU^MM5)ltlsvPQ!6aDOlfXLDqf2-j^;786;yd|;!ViNSP?F=Kt#FNs z`^NG`anK4F3Afc!o+j?5xdrqon)MwDmJC-b=HQsltZ%s&&OUWdqSR}>a4}_N zgnG%gLf=3Ms(!b^dF#f5{E>sm(?82=`j=RD)1egE!QJtdIMZPBlVrly&;WDgdZ}Ai zUCi^gLoCl0RqrbLb>rxhe)(AjOP0{BXEey;B0i&?qrYDI>X~uP(sEy~nRx!Fr)PqMqoP%w#XN+S0#7qcZSz^*D*-$5^Zg$l>qLKA5py~dx50hGj zI&G7^{<8@F`Gf1udj=-uP%#L2w3_SF#nTUh%x@!>Bx0V-9Osj~*3 zzMFOSi-SKa)~_#gs8BG2xhChnF@03M&tmD1Zk6Ls%m=k*HOm3Hu=K(me0E2UrM7LBTu#UxA|ydI&Cqv}$a$g4}>WDv?BK zad>J5HAl*o!POrGy{QRw4Lx%(9`Ku+07d$^g4s-1ZOR`=HbPNtsmA_PF9+|9A|_9F zZVNh9X9oU&%>B3)hHCQkbZX&;HtAA$N(Nb?qMTslO0YOyCIFo_I4anyw_tqIs32YL zz*EpK2h3}O2G$u`fdg08zCU~nAf!Xh^A0#p{Kcg)e|Eqb67a_nYPvyqnm&?0+|Jax z)P(xw*yRaSxrHwldWH2!-3VUI`2`@LOCimu4NMu?uRr8H+WmM)g!ww0c!x-lGZgji zINCT_*FT1$N;ZFs8g+d2~P=Yt#!pp>dU^oYa_J>wQxOs3Y6-oYd7bA7N%K) z?h=>T2x2l>Aq5^%VVyRimqt932&qe-pY|B_#6XOFKfB8iZPxSLI~lnTcY%Jp$m&%C z9WH_>8jsu*s=C&Dj;)Nfxi@$%V4YI^PPNE-Wd(|MiUS%H>1I}%m-{ogMfg}!%#S7L zs+J;*`PpQ<`VNV@yI#DrHAe@97pj))e?230CD57Mf)dsiE|87SIo!y$B(}Lx*inz6 zukP-X2~iQ3J&beUAn5B)qKwxz7Y8M-okn!i%5T&YYnJQD6=h1;L0F)C1VA}d zBCG7oHRga1lm&1mgyQKv$@$?rp}62Ig7M&7I#Qy=_USBCi;1EvJX>HXT0s+Ck!A&f zFFowMb9hkzjs_9X@;d)Dnvsil$}boW1gYBRRK5`kZy9W81wumL*4i zjesYR6D^z`)`mO=VJs7F9)Un&b#~rQpi@l~ZCt<)(FeDN14pBWC#8@Eg~s2wX_ukQ z$f@t7$EZ7{CEsq$fn76%&gLfILjqX@LEPx?!okkPRj0ky_u*IHg*w;&5b%Xlc@QUh zYp#x%gW|{=+FT2@_|z2E2oa}AV@lH`9s=dc6d&gZ*yV&-BcPB#;}+I@fhXYR&yFZ; z?o$Ah?~zN4E2$?&CrP4#l$|#yX(MJjc3JfOM8#~owIoUqQ}`DdLasce-0_Tdizg{# zYNXXTGk{^qt@ZP}7({$Syd(h9mL@BAe~6<%*6{z`JuGjAQGTy0S=cB&?GJAN2NghnU2Rh zgHBfQ$(bj49z@My12D<+?F{d4hHJ>YsC2(90mLnnxd>Pg*JnM2z#}hl)@vV*o^-*I z&r~wS3Ye^A-k(3jL>HIgY$3on#1=fFF|9D%^=XZnDXnl_ET$!Tz)Am&;L;HpU4y$k z{R%JZm5foz0~QvH=o`)24AXlw8BZ_eJt4sqpG(f_v8XV(7+{|FO7Og2A{d8)MlBAa z1aIVJxpJa?(#2#GGhT8qIAjl!VhHH-Hc?S1A6?eE=e>V&c5hN(2pr)Hq7!cA%v$D2 z&&rTRh(-c*y+wXh4Z;h&WYW|&m|KB~!@_wetLNxMB1(wPDBIVizm~j#_k&kXBRo){i5x$Q7`&k!I^(oqg?3MW!z$h@ObuB$BYL zbin8VQ?)t}S7s=+sfGzWa9eC1q}oIHJy;>XvuDHh|7080te1vA@P?yFqgJ1gj^Bc1 z5cag~9_)2Il)!0uuuLiUC^Me2u6>QV(Y;J?9gi|!N_XlJAV|_Q9sSSv=sO(6VA2bk zC!_m8t#z|o{3n2uR+L^(mtGN|$1Ey8BK>j#o;!|u6qt1x-%!D;i3Q>?e<^rF1@DTX zf&6vnvhJE5A${NC2Y$W6xk7WwOX-x1Sl=-%OH8qpe_MXZwY*|Re^h?H?w4!#u^Lgm z+@6rsp=ddqP+6HGcx&Ljwl{&z0Kby^{NCGo?9-cx6-dc?t;|w(UC)sQ{fwDzKGZHk z@%f=nZqxPe%P*{&WRTKO;Zu98xw(3mmfV;RJ|2VlZeo@PuT1BxOP*EEr3@d=kP09; zl>AZ`Y!C}$P_D&5`F&`4QYLZ0U7xqd3@;XMs?uaVd6_4BNm4&c^wag5KUcZX2X_D> zYyIq9|2WBCd=_29bi<$KbxEPXo`^ehQ&+yB; zop>oHenU{Pr&vxuS3?og>uI5p*{i=X*K^K1yX5Yjc5DisBNv**{8Ri=cma zVQ}6pQpx|2tyqQgl02j+Qma^0R4~aEl`$@*U$dz&XS5$Fu3H(4qw65Y_p?lx-WQ9) zPFG?BTbT`>qerm-dLnnx_->S$u`;mN%5@D$E)OLVLBJu zKUnI2iZwqFl?o`G)|0wc)lXdjtPEEqW!-*iL z7xjk&b6vN`HLo4zFg5@-Py0D)Qua1`h4RSy`%3;VE>ga>%}D|vbKO=6P-4@Zy6w~t ze6YRZT68HFMqnSs-Oc^QrDo0HfP)q*2oNT@O!8Bch5&fwlXAb_QFNJlAOC3P9?&4T z=-gBDl+${cBEkdrE@~jHs_Z`x7NqX`&q{9x|6KaCxJ>M+sZ;rXA`!e2PJ85oy@XN7(u^W-Pw5`K7)0+9%h#zucT&>2uTp=d z-v4a5HR8NYOhZ9naLNzXSf~bF97+)Vka77e9}oy4$WNS>iFC6vHzza6=5z89=Ln7x z4*9v=M@2|=33>R8FFMS=vNjEB=sl;0i`4|hr< zq)txMlx&Pv1fkkv*pr$03N~U!+)4cqG^&+awHVUg@Oa#D<#*M_B`?=iG25Z#QlxRD z@g=?@)#kUobn~nJ6x93@*R}YZ7sDxk#WXn4+d7O}t3gX5Uc-c0e(vG^@}%WR`7iiS zT-C8*LJD-27~M|+oG}+%MKchXGx>kvJPBax9U~==Gub33Sbpe+lU#6_E*O^3BXvE4 z&rxacQ)BLGMn2Er1}%bdB7p4q=f5Qp=u({}T|Y(+8hE~_lk2!K$q~WvpPT%`O4tY5 z9&=n2$n`|J7&>T{w>}bZ%O290(OeWGxjY;?t9{L7_ni8Txv9L7^7)f564w3?nxl)- z`$1HNR$Dl}4{tEc$4@ig?@IW8^}aIs_qPa8Q~YCrvYb>Q_~T(gRK3^Ud{g#lIwkQx zw8IJd>rT2{1582t8H*a<)4A~$ON*0PmJ!9pHhS`voSWnCWuu_$CUPjk< zzeH344VH`P{w|J z)$$0Hw%6kH#pB%HSTXiN(ntMW~bRMWov%W5z8!e375VQq}awOX`2L0A$osuHQQQ&)NUq z4+&^mFt8u<5ZGK5+$_tolE5dskS6sn%!glxkD#F&1`VUtzQvq|NB&dHu*&}L%N+mP zM~S=;T&|A}E-K#^x`u{^<{lNr&6pk?9$158{wnbUD4+Q|J~GY>mK9Q-zJ$$dD!nKU z`D*jQgwBuuuBj zANhcJ)8%tCUNf?C1=udxyJu*XPiU|wS02AqOi+~E9GjChj!+~Z!Bz5!s_`RuKOz)x zWTyEXZl;MW7CDIFiK6i$=b<-Q1FroDmIq$u;xTE~==470bCO84NHrsC+3~L-9pm?8 zQIn4~1>w(|(apZ)R-4d*Gk9J@o7Pgs1g~`5F+Aqq-V+=A*-^)9;rOv8Ul-$$8vE?v zg0l>Cofa2p{+Bb;eU68e;!&t~ANYJfx^$czs)wEZ2l)TI+VP8qMiT2*`Dn1kNeVH1 zwIMd`J#)$J)!(mHF11@J(!ld)b8JjDEw=ISo&0}VT-QyOjrS!=josUW;R3x8G=xXQ zt!WK9e>VSQmm6Wj&lR657X08en4MK=kRLZ*D4&+)fP1R8e@>c|dres{YfxnP;$@Tw zJJ($d97n&k!c58@N~>+6$bmO_xc6qu1Ed0XE!Fe3wokr~B_>QREO(3ZuUQTJU4+yc z>04CKK*LYC0Sj_IkHqK=sky%T3sQCJ65-|`Kax7NtP*g1Lmy_G9H`)~4>`216BY~3 zku+%t8GXFTG`jvGWL7ESoq~HmrJ%)wx~-RY)b75CYO;O7KE}~MZb5PI&mDrz%ohjD z<^!RxoI|OTp@)w&Q-LF+BTTUQ*?VgFg?%DC{t!jPf40d=DwaWrGxTierjqi4^Ob-m zqBRjPvHtsV$2S%GS^1d)f*E=1~obZ`j61UuenGFjT#5Mfps zaA1Y^HHB_@L`F5}Rp|P|T{rR$AEr9WmnATGRFQ(=;|!%>C7ip9`y$Q2gyzA6r==+1 zB!!SA!Z-C^excn`=>6aW?|v@q$N`gj-k+*C+h9EXn>gwk@f9P)(HiniYVw$wzNYtE z9;i-Kr*7@Z@0XOxbLKt~Krt!#?%u+IMXOF%L^+4tD%2FJvwwjQ=fJXNET9jn(3J9H;F+x4PdP%kNlh~4?t^0WoXM7AMut_39YU;5=}>jy=Oe%cPGDPT zB<-!%RUG`7>CDWB#}ND69xj@F&Y59>0v3TAu{SWU0Rw!b3T%INn#z>+C6bIYjDL4h ztIx6RJf1d^11M4oF_B3=51}Dk3KT%&t-l@wE`!wl5Bs4U2yPc4s^x>!bi(JuwX~x? zAOg;eRDGIGv>X6ww$06JB7p}0?17!C40HyCbN0sK;7g|2wTMVQW#K(bfyo?%Bn9^B%!0RaZ9rV5 zUk5SOoo(-Q4NTj|Zf%)ZYvB(;37o4*56relE4DA@qlJQB>vDrz1DaUu_JP34aE9T9d5Z~Z(lIiG~HlgD_0CSJd$h{5N1#Dgn;>|(WB9=%;ycLje zFNkXF3`@HIz;r2Aq*trU1#j+Q(A@yhU#uyGwkI=yek z$zj}ORgs`U4Umk=SJmveNp=Cp9@D(Q+{x)V5}*z4*#|;Wrt>Cb5(H{sIH&g4+Nlqa zQTK~re|Qhp`3tpCoa)|0RBYx*Dr6)O4V&=o3Dgro@{6yM591Wl6S2zr{Q7Z7_~CKC z8}KOge7I9SbYDBxi>lL=g9jjUJYgeVHm{?>}oPwpUQ|z7iaqDbH3&B%B zv>X0^b-j5!l->V7j>Zi%|Df8t~uwr&Uw9_+hOwqaI*tGh3nG@{!$=wDMUT< zVnM8v+6mb8S3P)8_9ZIqiBw_ZL*O_gXfdg$;qCeBJmH+BFmFyS3nd`%cSuhlaRb1$ z?J)F@?X|?t6<)CgXf#bBYgEQ;ban|o^UD@x!znZJ2v`jG?LFY5M$ky9`a$O7LdOS> zmYR8<3*naGGGif%+yj!?MRtefn$=t+O!4zIs77xthIG+?dMdpOMm&e@Dn6lS7}yz) z<#i9OUhpy1DxunP4#rptWKccW?!onbfB-^qv0bNeISM@h{VE372>9_76W*EVGz)=<}{#(%$PpH*;d)Y0Q z=je2^>vNFPCFO^}f`0-QYXDKc4Sl!tqX%c+O7cE3+=G~C%15W3+igqVKLD96Na;a9 zVxA;^-5B7sNMqv#R#DccQN*awXG|F(nq{rJ{ z9weU(Fy7UpM8!&L>#}6@j3cObhpQ1e5ifK1Sq&U#R(S7zuwK^XJiOZcr%O-$u47EB z^)mU^A*%JG5qXRFqRnWG$L8_i?AOikWVxZ>qH2gXI+=aIRHXAIP=<(jpsIwV3CrABd&IGy3;bpHJL zxm}$E%rImmht2o6r?RYWDPRK*=(dE z7dCGg4a);(sq<5%%hkfMOP4OS#wI{N60RsJ7cwIQAx2H@=b(cYz88272VA372gIS{8y^9Lf{A5XC7}Ck;5v(`7 zoqUb4lXQ*GcrOSZUx^lM)L!AeGiP!8e`fc}T0wDf11*>?VcH3(URJme#Ln&fQ0BdQ zO=IngO?`h$JE-M-K+>m9@=rD=ur624NPW@Re{)Z({It(kW2tA$H9gJ&eg?_%INx<3 zgoT1SKR`P&byyP!mNx%XeKoiNx7AAVkJ^My?6mz{tNs?6Q;I)8zIf6$^#0kKhj;}y z46KKjKTLuqKst65;hjm5!-uZ1{Y4)H>^7>`OzyJUojbpm!*b)7kIFXt%TCOqmNXP! z{mCw%(t6v2BU)dsuXA=Ng~m_f=eq9vs-dThN4mZiOYEfk+LLIXg)_GLlt4sAGt!qI zz#?x{HDm%Y*BORSQoea*?GmH=Jh;P{J2T`HKSUAHu(Bql_myiL@d1BV2dpRUO-r9- zuON`R4D#CCdPgd{&pp10a$}fP2rMUJf$=Ft()2zcU9C0m%}yW|46>ki&HavRIxH); z!~Wn$e~_CLLJ_w$O1#C4iGM{)kcA6I8nl890RDP)XBLPEqTe5TL((b3CJ_Ccs%0$Z z%Cn}NNpwobzP$F>d%UX^Dxj+LAp@H-Z%_z!LR=dN!@R7_ap-uZaWMH5!`1Hf?*mg$ zClC=fUQ5$jF%sxZ!cunzSQ@K{X2rsiu0@SUSRF6&)9q5Xx+|{T7R%5AdEs^mav(fY zg*#8Z=s?<{#ek}MZ>0N^rzAY#;M=(N`5F(BuT{eE-QJPbPQc`5bBRK>%0D9lhqqk& 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 W8q}4KO+U{9fAEKm4(03Ehx|XJSBymf literal 0 HcmV?d00001 diff --git a/_images/metrics.png b/_images/metrics.png new file mode 100644 index 0000000000000000000000000000000000000000..23c28f51785b114ee5e1a4e21f9f90e32d729b1d GIT binary patch literal 160289 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

&txwZ_9bTKcC-% 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 z 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 + + + + + + + + Overview: module code — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + + +
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr.html b/_modules/teehr.html new file mode 100644 index 00000000..e4c1514b --- /dev/null +++ b/_modules/teehr.html @@ -0,0 +1,542 @@ + + + + + + + + + + teehr — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr

+"""TEEHR logger implementation."""
+# Set default logging handler to avoid "No handler found" warnings.
+import logging
+from logging import NullHandler
+
+__all__ = ["add_stderr_logger"]
+
+# https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
+# We don't want the TEEHR logger to interfere with any application using TEEHR.
+logging.getLogger(__name__).addHandler(NullHandler())
+
+
+
+[docs] +def add_stderr_logger(level: int = logging.DEBUG) -> logging.StreamHandler: + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + + Taken from the urllib3 package. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if TEEHR is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) # noqa + logger.addHandler(handler) + logger.setLevel(level) + logger.debug("Added a stderr logging handler to logger: %s", __name__) + return handler
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/api/main.html b/_modules/teehr/api/main.html new file mode 100644 index 00000000..957ef4a6 --- /dev/null +++ b/_modules/teehr/api/main.html @@ -0,0 +1,847 @@ + + + + + + + + + + teehr.api.main — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.api.main

+"""Module for database queries via FastAPI."""
+import json
+
+from yaml import load  # , dump
+try:
+    from yaml import CLoader as Loader  # , CDumper as Dumper
+except ImportError:
+    from yaml import Loader  # , Dumper
+
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+
+from typing import Union, Dict, List
+# from pydantic import validator
+from pathlib import Path
+
+# from teehr.queries.duckdb import get_metrics
+from teehr.database.teehr_dataset import TEEHRDatasetAPI
+from teehr.models.queries import (
+    # JoinedFilterFieldEnum,
+    # JoinedFilter,
+    # BaseModel,
+    FilterOperatorEnum,
+    MetricEnum
+)
+from teehr.models.queries_database import (
+    MetricQuery,
+    TimeseriesQuery,
+    TimeseriesCharQuery,
+    JoinedTimeseriesFieldName
+)
+import pandas as pd
+import geopandas as gpd
+# import duckdb
+
+app = FastAPI()
+
+origins = [
+    "http://localhost",
+    "http://localhost:5173",
+    "http://localhost:8000",
+]
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=origins,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+
+with open(Path(Path(__file__).resolve().parent, "data.yaml")) as f:
+    datasets = load(f.read(), Loader)
+
+
+
+[docs] +def format_response(df: Union[gpd.GeoDataFrame, pd.DataFrame]) -> List[Dict]: + """Convert the query response format from the dataframe to a dict. + + Parameters + ---------- + df : Union[gpd.GeoDataFrame, pd.DataFrame] + Dataframe or Geodataframe. + + Returns + ------- + List[Dict] + A list of dictionaries. + """ + # print(df.info()) + if isinstance(df, gpd.GeoDataFrame): + # convert datetime/duration to string + for col in df.columns: + if df[col].dtype in ["datetime64[ns]", "timedelta64[ns]"]: + df[col] = df[col].astype(str) + return json.loads(df.to_json()) + elif isinstance(df, pd.DataFrame): + return df.to_dict(orient="records", index=True) + else: + return df
+ + + +
+[docs] +@app.get("/") +def read_root(): + """Root message.""" + return {"msg": "Welcome to TEEHR"}
+ + + +
+[docs] +@app.get("/datasets/") +async def get_datasets(): + """Get datasets.""" + return datasets["datasets"]
+ + + +
+[docs] +@app.get("/datasets/{dataset_id}") +async def read_dataset_by_id(dataset_id: str) -> Dict: + """Get a dataset by its ID. + + Parameters + ---------- + dataset_id : str + Dataset ID. + + Returns + ------- + Dict + Dataset filepaths. + """ + return datasets["datasets"][dataset_id]
+ + + +
+[docs] +@app.get("/datasets/{dataset_id}/get_metric_fields") +async def get_metric_fields( + dataset_id: str, +) -> List[str]: + """Get metric fields. + + Parameters + ---------- + dataset_id : str + Dataset ID. + + Returns + ------- + List + List of available metrics. + """ + return list(MetricEnum)
+ + + +
+[docs] +@app.get("/datasets/{dataset_id}/get_data_fields") +async def get_data_fields( + dataset_id: str, +) -> List[Dict]: + """Get data fields and datatypes. + + Parameters + ---------- + dataset_id : str + Dataset ID. + + Returns + ------- + List + A list of dictionaries of database fields and their types. + """ + config = datasets["datasets"][dataset_id] + tds = TEEHRDatasetAPI(config["database_filepath"]) + fields = tds.get_joined_timeseries_schema() + fields.rename( + columns={ + "column_name": "name", + "column_type": "type" + }, + inplace=True + ) + return fields[["name", "type"]].to_dict(orient="records")
+ + + +
+[docs] +@app.get("/datasets/{dataset_id}/get_filter_operators") +async def get_filter_operators(dataset_id: str) -> Dict: + """Get filter operators. + + Parameters + ---------- + dataset_id : str + Dataset ID. + + Returns + ------- + Dict + A dictionary of filter operator keywords and their symbols. + """ + return {item.name: item.value for item in FilterOperatorEnum}
+ + + +# @app.get("/datasets/{dataset_id}/get_group_by_fields") +# async def get_metric_fields( +# dataset_id: str, +# ): + +# db = TEEHRDataset(filepath="/some/file/path/to/database.db") +# # df = db.get_database_fields() +# df = db.get_metrics(APIMetricQuery) + +# return {"fields": df} + + +# @app.post("/datasets/{dataset_id}/get_field_values") +# async def get_metric_fields( +# dataset_id: str, +# field_name: str +# ): + +# db = TEEHRDataset(filepath="/some/file/path/to/database.db") +# list = db.get_distinct_values("field_name") + +# return {"fields": df} + + +
+[docs] +@app.post("/datasets/{dataset_id}/get_metrics") +async def get_metrics_by_query( + dataset_id: str, + api_metrics_query: MetricQuery +) -> List[Dict]: + """Get metrics by query. + + Parameters + ---------- + dataset_id : str + Dataset ID. + api_metrics_query : MetricQuery + Pydantic model of query variables. + + Returns + ------- + List[Dict] + A list of dictionaries of query results. + """ + config = datasets["datasets"][dataset_id] + tds = TEEHRDatasetAPI(config["database_filepath"]) + df = tds.get_metrics(api_metrics_query) + + return format_response(df)
+ + + +
+[docs] +@app.post("/datasets/{dataset_id}/get_timeseries") +async def get_timeseries_by_query( + dataset_id: str, + api_timeseries_query: TimeseriesQuery +) -> List[Dict]: + """Get timeseries by query. + + Parameters + ---------- + dataset_id : str + Dataset ID. + api_timeseries_query : TimeseriesQuery + Pydantic model of query variables. + + Returns + ------- + List[Dict] + A list of dictionaries of query results. + """ + config = datasets["datasets"][dataset_id] + tds = TEEHRDatasetAPI(config["database_filepath"]) + df = tds.get_timeseries(api_timeseries_query) + + return format_response(df)
+ + + +
+[docs] +@app.post("/datasets/{dataset_id}/get_timeseries_chars") +async def get_timeseries_chars_by_query( + dataset_id: str, + api_timeseries_char_query: TimeseriesCharQuery +) -> List[Dict]: + """Get time series characteristics by query. + + Parameters + ---------- + dataset_id : str + Dataset ID. + api_timeseries_char_query : TimeseriesCharQuery + Pydantic model of query variables. + + Returns + ------- + List[Dict] + A list of dictionaries of query results. + """ + config = datasets["datasets"][dataset_id] + tds = TEEHRDatasetAPI(config["database_filepath"]) + df = tds.get_timeseries_chars(api_timeseries_char_query) + + return format_response(df)
+ + + +
+[docs] +@app.post("/datasets/{dataset_id}/get_unique_field_values") +async def get_unique_field_vals( + dataset_id: str, + api_field_name: JoinedTimeseriesFieldName +) -> List[Dict]: + """Get unique field names in the joined_timeseries table. + + Parameters + ---------- + dataset_id : str + Dataset ID. + api_field_name : JoinedTimeseriesFieldName + Pydantic model of query variables. + + Returns + ------- + List[Dict] + Pydantic model of query variables. + """ + config = datasets["datasets"][dataset_id] + tds = TEEHRDatasetAPI(config["database_filepath"]) + df = tds.get_unique_field_values(api_field_name) + + return format_response(df)
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/database/teehr_dataset.html b/_modules/teehr/database/teehr_dataset.html new file mode 100644 index 00000000..85b71f5f --- /dev/null +++ b/_modules/teehr/database/teehr_dataset.html @@ -0,0 +1,1700 @@ + + + + + + + + + + teehr.database.teehr_dataset — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.database.teehr_dataset

+"""Defines the TEEHR dataset class and pre-processing methods."""
+from typing import Union, List, Callable, Dict, Any
+from pathlib import Path
+import logging
+
+import re
+import duckdb
+import pandas as pd
+import geopandas as gpd
+
+import teehr.queries.duckdb_database as tqu_db
+
+import teehr.queries.utils as tqu
+from teehr.models.queries_database import (
+    JoinedFieldNameEnum,
+    InsertJoinedTimeseriesQuery,
+    JoinedTimeseriesQuery,
+    CalculateField,
+    MetricQuery,
+    TimeseriesQuery,
+    TimeseriesCharQuery,
+    JoinedTimeseriesFieldName,
+)
+from teehr.models.queries import MetricEnum
+
+logger = logging.getLogger(__name__)
+
+
+
+[docs] +class TEEHRDatasetAPI: + """Create an instance of a TEEHRDataset class for API-based queries and \ + initialize a study area database. + + Methods + ------- + __init__(database_filepath: Union[str, Path]) + Establish a connection to an existing study area database. + + profile_query(query: str) + A helper function to profile query performance (runs EXPLAIN ANALYZE). + + query(query: str, format: str = None, create_function_args: Dict = None) + Submit an SQL query string against the database. + + get_joined_timeseries_schema() + Get field names and field data types from the joined_timeseries, + table as a pandas dataframe. + + describe_inputs(primary_filepath: Union[str, Path], \ + secondary_filepath: Union[str, Path]) + Get descriptive statistics on the primary and secondary + timeseries by reading the parquet files as a pandas dataframe. + + get_metrics(mq: MetricQuery) + Calculate performance metrics using database queries. + + get_timeseries(tq: TimeseriesQuery) + Retrieve timeseries using a database query. + + get_timeseries_chars(tcq: TimeseriesCharQuery) + Retrieve timeseries characteristics using database query. + + get_unique_field_values(fn: JoinedTimeseriesFieldName) + Get unique values for a given field as a pandas dataframe. + """ + + def __init__( + self, + database_filepath: Union[str, Path], + ): + """Set the path to the pre-existing study area database, + and establishe a read-only database connection. + + Parameters + ---------- + database_filepath : Union[str, Path] + Filepath to the database. + """ + self.database_filepath = str(database_filepath) + self.con = duckdb.connect(self.database_filepath, read_only=True) + +
+[docs] + def profile_query(self, query: str): + """A helper function to profile query performance, + which runs EXPLAIN ANALYZE and prints the output to screen. + """ + query = "EXPLAIN ANALYZE " + query + print(self.query(query, format="df").explain_value.values[0])
+ + +
+[docs] + def query( + self, + query: str, + format: str = None, + ): + """Run an SQL query against the class's database. + + Return formats include: + + * A pandas dataframe (format='df') + * Results bprinted to the screen (format='raw') + * A DuckDBPyRelation, a symbolic representation of the SQL query + (format='relation'). + """ + if format == "df": + return self.con.sql(query).df() + elif format == "raw": + return self.con.sql(query).show() + elif format == "relation": + return self.con.sql(query) + self.con.sql(query) + return None
+ + +
+[docs] + def get_joined_timeseries_schema(self) -> pd.DataFrame: + """Get field names and field data types from the joined_timeseries \ + table. + + Returns + ------- + pd.DataFrame + Includes column_name, column_type, null, key, default, + and extra columns. + """ + desc = """DESCRIBE SELECT * FROM joined_timeseries;""" + joined_df = self.query(desc, format="df") + + return joined_df
+ + +
+[docs] + @staticmethod + def _sanitize_field_name(field_name: str) -> str: + """Replace unallowed characters from user-defined field names with an + underscore. + """ + # I think we will need this + allowed_chars = r"[^a-zA-Z0-9_]" + search = re.compile(allowed_chars).search + + if bool(search(field_name)): + sub = re.compile(allowed_chars).sub + return str(sub("_", field_name)) + + return field_name
+ + +
+[docs] + @staticmethod + def describe_inputs( + primary_filepath: Union[str, Path], + secondary_filepath: Union[str, Path], + ) -> pd.DataFrame: + """Get descriptive statistics on the primary and secondary + timeseries by reading the parquet files. + + Parameters + ---------- + primary_filepath : Union[str, Path] + Path to the primary time series parquet file. + secondary_filepath : Union[str, Path] + Path to the primary time series parquet file. + + Returns + ------- + pd.DataFrame + A dataframe of descriptive statistics for each time series. + + Output includes: + + * Number of unique location IDs + * Total number of rows + * Start date + * End date + * Number of duplicate rows + * Number of location IDs with duplicate value times + * Number of location IDs with missing time steps + """ + primary_dict = tqu_db.describe_timeseries( + timeseries_filepath=primary_filepath + ) + + secondary_dict = tqu_db.describe_timeseries( + timeseries_filepath=secondary_filepath + ) + + df = pd.DataFrame( + { + "primary": [primary_dict[key] for key in primary_dict.keys()], + "secondary": [ + secondary_dict[key] for key in secondary_dict.keys() + ], + }, + index=primary_dict.keys(), + ) + + return df
+ + +
+[docs] + def _check_if_geometry_is_inserted(self): + """Make sure the geometry table is not empty.""" + df = self.query("SELECT COUNT(geometry) FROM geometry;", format="df") + if df["count(geometry)"].values == 0: + raise ValueError( + "The geometry table is empty! Please insert geometry first" + )
+ + +
+[docs] + def _validate_query_model(self, query_model: Any) -> Any: + """Validate pydantic query models based on the existing fields + in the joined_timeseries table. + """ + schema_df = self.get_joined_timeseries_schema() + validated_model = query_model.model_validate( + query_model.model_dump(), + context={"existing_fields": schema_df.column_name.tolist()}, + ) + return validated_model
+ + +
+[docs] + def get_metrics( + self, + mq: MetricQuery, + ) -> Union[pd.DataFrame, gpd.GeoDataFrame, str]: + """Calculate performance metrics using database queries. + + Parameters + ---------- + mq : MetricQuery + Pydantic model containing query parameters. + + Returns + ------- + Union[pd.DataFrame, gpd.GeoDataFrame, str] + A DataFrame or optionally a GeoDataFrame containing query results, + or the query itself as a string. + + See Also + -------- + teehr.queries.duckdb_database.create_get_metrics_query : \ + Create the get metrics query. + """ + + mq = self._validate_query_model(mq) + + query = tqu_db.create_get_metrics_query(mq) + + if mq.return_query: + return tqu.remove_empty_lines(query) + elif mq.include_geometry: + self._check_if_geometry_is_inserted() + df = self.query(query, format="df") + return tqu.df_to_gdf(df) + else: + df = self.query(query, format="df") + return df
+ + +
+[docs] + def get_joined_timeseries( + self, + jtq: JoinedTimeseriesQuery + ) -> Union[pd.DataFrame, gpd.GeoDataFrame, str]: + """Retrieve joined timeseries using database query. + + Parameters + ---------- + jtq : JoinedTimeseriesQuery + Pydantic model containing query parameters. + + Returns + ------- + Union[pd.DataFrame, gpd.GeoDataFrame, str] + A DataFrame or GeoDataFrame of query results + or the query itself as a string. + + See Also + -------- + teehr.queries.duckdb_database.create_get_joined_timeseries_query : \ + Create the get joined timeseries query. + """ + + jtq = self._validate_query_model(jtq) + + query = tqu_db.create_get_joined_timeseries_query(jtq) + + if jtq.return_query: + return tqu.remove_empty_lines(query) + elif jtq.include_geometry: + self._check_if_geometry_is_inserted() + df = self.query(query, format="df") + return tqu.df_to_gdf(df) + else: + df = self.query(query, format="df") + return df
+ + +
+[docs] + def get_timeseries( + self, + tq: TimeseriesQuery + ) -> Union[pd.DataFrame, str]: + """Retrieve timeseries using database query. + + Parameters + ---------- + tq : TimeseriesQuery + Pydantic model containing query parameters. + + Returns + ------- + Union[pd.DataFrame, str] + A DataFrame of query results or the query itself as a string. + + See Also + -------- + teehr.queries.duckdb_database.create_get_timeseries_query : \ + Create the get timeseries query. + """ + + tq = self._validate_query_model(tq) + + query = tqu_db.create_get_timeseries_query(tq) + + if tq.return_query: + return tqu.remove_empty_lines(query) + else: + df = self.query(query, format="df") + return df
+ + +
+[docs] + def get_timeseries_chars( + self, tcq: TimeseriesCharQuery + ) -> Union[str, pd.DataFrame]: + """Retrieve timeseries characteristics using database query. + + Parameters + ---------- + tcq : TimeseriesCharQuery + Pydantic model containing query parameters. + + Returns + ------- + Union[str, pd.DataFrame] + A DataFrame of time series characteristics including: + + - location_id + - count + - min + - max + - average + - sum + - variance + + or, the query itself as a string. + + See Also + -------- + teehr.queries.duckdb_database.create_get_timeseries_char_query : \ + Create the get timeseries characteristics query. + """ + + tcq = self._validate_query_model(tcq) + + query = tqu_db.create_get_timeseries_char_query(tcq) + + if tcq.return_query: + return tqu.remove_empty_lines(query) + else: + df = self.query(query, format="df") + return df + + pass
+ + +
+[docs] + def get_unique_field_values( + self, + fn: JoinedTimeseriesFieldName + ) -> pd.DataFrame: + """Get unique values for a given field. + + Parameters + ---------- + fn : JoinedTimeseriesFieldName + Pydantic model containing the joined_timeseries table field name. + + Returns + ------- + pd.DataFrame + A dataframe containing unique values for the given field. + + See Also + -------- + teehr.queries.duckdb_database.create_unique_field_values_query : \ + Create the get unique field values query. + """ + fn = self._validate_query_model(fn) + query = tqu_db.create_unique_field_values_query(fn) + df = self.query(query, format="df") + return df
+
+ + + +
+[docs] +class TEEHRDatasetDB(TEEHRDatasetAPI): + """Extends TEEHRDatasetAPI class with additional functionality \ + for local database-based queries. + + Methods + ------- + insert_geometry(geometry_filepath: Union[str, Path]) + Inserts geometry from a parquet file into a separate + database table named 'geometry'. + + insert_joined_timeseries(primary_filepath: Union[str, Path], \ + secondary_filepath: Union[str, Path], \ + crosswalk_filepath: Union[str, Path], \ + order_by: List[str] = [ \ + "reference_time", \ + "primary_location_id", \ + ], \ + drop_added_fields=False, \ + ) + Joins the primary and secondary timeseries from parquet files + and inserts into the database as the joined_timeseries table. + + join_attributes(attributes_filepath: Union[str, Path]) + Joins attributes from the provided attribute table(s) to new + fields in the joined_timeseries table + + calculate_field(new_field_name: str, new_field_type: str, \ + parameter_names: List[str], \ + user_defined_function: Callable, \ + replace: bool = True, \ + ) + Calculate a new field in joined_timeseries based on existing + fields and a user-defined function. + + profile_query(query: str) + A helper function to profile query performance (runs EXPLAIN ANALYZE). + Inherited from TEEHRDatasetAPI. + + query(query: str, format: str = None, create_function_args: Dict = None) + Submit an SQL query string against the database. + Inherited from TEEHRDatasetAPI. + + get_joined_timeseries_schema() -> pd.DataFrame + Get field names and field data types from the joined_timeseries, + table as a pandas dataframe. Inherited from TEEHRDatasetAPI. + + describe_inputs( \ + primary_filepath: Union[str, Path], \ + secondary_filepath: Union[str, Path] \ + ) -> pd.DataFrame + Get descriptive statistics on the primary and secondary + timeseries by reading the parquet files as a pandas dataframe. + Inherited from TEEHRDatasetAPI. + + get_metrics( \ + group_by: List[str], \ + order_by: List[str], \ + include_metrics: Union[List[MetricEnum], "all"], \ + filters: Union[List[dict], None] = None, \ + include_geometry: bool = False, \ + return_query: bool = False, \ + ) -> Union[str, pd.DataFrame, gpd.GeoDataFrame]: + Calculate performance metrics using database queries + Overrides TEEHRDatasetAPI. + + get_timeseries( \ + order_by: List[str], \ + timeseries_name: str, \ + filters: Union[List[dict], None] = None, \ + return_query: bool = False, \ + ) -> Union[pd.DataFrame, str] \ + Retrieve timeseries using a database query. + Overrides TEEHRDatasetAPI. + + get_timeseries_chars( \ + group_by: List[str], \ + order_by: List[str], \ + timeseries_name: str, \ + filters: Union[List[dict], None] = None, \ + return_query: bool = False, \ + ) -> Union[str, pd.DataFrame] + Retrieve timeseries characteristics using database query + Overrides TEEHRDatasetAPI. + + get_unique_field_values(fn: JoinedTimeseriesFieldName) -> pd.DataFrame + Get unique values for a given field as a pandas dataframe. + Overrides TEEHRDatasetAPI. + Use get_joined_timeseries_schema() to see existing table fields. + """ + + def __init__( + self, + database_filepath: Union[str, Path], + ): + """Initialize a study area database. Create the joined_timeseries + and geometry tables with a fixed schemas if they do not already + exist. + + Parameters + ---------- + database_filepath : Union[str, Path] + Filepath to the database. + """ + self.database_filepath = str(database_filepath) + self._initialize_database_tables() + +
+[docs] + def query( + self, + query: str, + read_only: bool = False, + format: str = None, + create_function_args: Dict = None + ): + """Run query against the class's database.""" + if not create_function_args: + with duckdb.connect( + self.database_filepath, read_only=read_only + ) as con: + if format == "df": + return con.sql(query).df() + elif format == "raw": + return con.sql(query).show() + elif format == "relation": + return con.sql(query) + con.sql(query) + con.close() + return None + else: + user_defined_function = create_function_args["function"] + function_name = create_function_args["function_name"] + parameter_types = create_function_args["parameter_types"] + new_field_type = create_function_args["new_field_type"] + with duckdb.connect(self.database_filepath) as con: + # Register the function + con.create_function( + function_name, + user_defined_function, + parameter_types, + new_field_type, + ) + # Call the function and add the results to joined_timeseries + con.sql(query) + con.close()
+ + +
+[docs] + def _initialize_database_tables(self): + """Create the persistent study database and empty table(s).""" + create_timeseries_table = """ + CREATE TABLE IF NOT EXISTS joined_timeseries( + reference_time DATETIME, + value_time DATETIME, + secondary_location_id VARCHAR, + secondary_value FLOAT, + configuration VARCHAR, + measurement_unit VARCHAR, + variable_name VARCHAR, + primary_value FLOAT, + primary_location_id VARCHAR, + lead_time INTERVAL, + absolute_difference FLOAT + );""" + + self.query(create_timeseries_table) + + # Also initialize the geometry table (what if multiple geometry types?) + create_geometry_table = """ + CREATE TABLE IF NOT EXISTS geometry( + id VARCHAR, + name VARCHAR, + geometry BLOB + );""" + self.query(create_geometry_table)
+ + +
+[docs] + def _drop_joined_timeseries_field(self, field_name: str): + """Drop the specified field by name from joined_timeseries table.""" + query = f""" + ALTER TABLE joined_timeseries + DROP COLUMN {field_name} + ;""" + self.query(query)
+ + +
+[docs] + def _validate_joined_timeseries_base_fields(self, drop_added_fields: bool): + """Ensure that no user-defined fields have been added or base fields + have been dropped. This is necessary in order to add multiple + configurations into the joined_timeseries table. + """ + schema_df = self.get_joined_timeseries_schema() + if schema_df.index.size < len(JoinedFieldNameEnum) - 1: + raise ValueError( + "There are missing fields in the joined_timeseries schema" + ) + for field_name in schema_df.column_name.tolist(): + if field_name not in JoinedFieldNameEnum.__members__: + if drop_added_fields: + logger.info(f"Dropping added field {field_name}") + self._drop_joined_timeseries_field(field_name) + else: + raise ValueError( + f"An added field '{field_name}' exists," + "please drop it before joining timeseries" + )
+ + +
+[docs] + def insert_geometry(self, geometry_filepath: Union[str, Path]): + """Insert geometry from a parquet file into a separate + database table named 'geometry'. + + Parameters + ---------- + geometry_filepath : Union[str, Path] + Path to the geometry file. + """ + # Load the geometry data into a separate table + if geometry_filepath: + query = f""" + INSERT INTO + geometry + SELECT + pq.id, pq.name, pq.geometry + FROM + read_parquet('{str(geometry_filepath)}') pq + WHERE NOT EXISTS ( + SELECT + id, name, geometry + FROM + geometry + WHERE + pq.id = id AND pq.name = name + AND pq.geometry = geometry + ) + ;""" + self.query(query)
+ + +
+[docs] + def insert_joined_timeseries( + self, + primary_filepath: Union[str, Path], + secondary_filepath: Union[str, Path], + crosswalk_filepath: Union[str, Path], + order_by: List[str] = [ + "primary_location_id", + "configuration", + "variable_name", + "measurement_unit", + "value_time" + ], + drop_added_fields=False, + ): + """Join the primary and secondary timeseries read from parquet files + and inserts into the database as the joined_timeseries table. + + Parameters + ---------- + primary_filepath : Union[str, Path] + File path to the "observed" data. String must include path to + file(s) and can include wildcards. For example, + "/path/to/parquet/\\*.parquet". + secondary_filepath : Union[str, Path] + File path to the "forecast" data. String must include path to + file(s) and can include wildcards. For example, + "/path/to/parquet/\\*.parquet". + crosswalk_filepath : Union[str, Path] + File path to single crosswalk file. + order_by : List[str], optional + List of column/field names to order results by, + by default ["reference_time", "primary_location_id"]. + drop_added_fields : bool, optional + A flag to determine whether to drop any user-defined fields that + have been added to the table (True), or raise an error if added + fields exist (False). By default False. + + See Also + -------- + teehr.queries.duckdb_database.create_join_and_save_timeseries_query : \ + Create the join and save timeseries query. + """ + self._validate_joined_timeseries_base_fields(drop_added_fields) + + jtq = InsertJoinedTimeseriesQuery.model_validate( + { + "primary_filepath": primary_filepath, + "secondary_filepath": secondary_filepath, + "crosswalk_filepath": crosswalk_filepath, + "order_by": order_by, + } + ) + + query = tqu_db.create_join_and_save_timeseries_query(jtq) + self.query(query)
+ + +
+[docs] + def _get_unique_attributes(self, attributes_filepath: str) -> List: + """Get a list of unique attributes and attribute units from the + provided attribute table(s). + """ + query = f""" + SELECT + DISTINCT attribute_name, attribute_unit + FROM + read_parquet('{attributes_filepath}') + ;""" + attr_list = self.query(query, format="df").to_dict(orient="records") + return attr_list
+ + +
+[docs] + def _add_field_name_to_joined_timeseries( + self, field_name: str, field_dtype="VARCHAR" + ): + """Add a field name to joined_timeseries + if it does not already exist. + """ + query = f""" + ALTER TABLE + joined_timeseries + ADD IF NOT EXISTS + {field_name} {field_dtype} + ;""" + self.query(query)
+ + +
+[docs] + def join_attributes(self, attributes_filepath: Union[str, Path]): + """Join attributes from the provided attribute table(s) to new + fields in the joined_timeseries table. + + Parameters + ---------- + attributes_filepath : Union[str, Path] + File path to the "attributes" data. String must include path to + file(s) and can include wildcards. For example, + "/path/to/parquet/\\*.parquet". + """ + attr_list = self._get_unique_attributes(str(attributes_filepath)) + + for attr in attr_list: + + if attr["attribute_unit"]: + field_name = ( + f"{attr['attribute_name']}_{attr['attribute_unit']}" + ) + unit_clause = ( + f"AND attribute_unit = '{attr['attribute_unit']}'" + ) + else: + field_name = attr["attribute_name"] + unit_clause = "" + + field_name = self._sanitize_field_name(field_name) + + self._add_field_name_to_joined_timeseries(field_name) + + query = f""" + WITH selected_attribute AS ( + SELECT + * + FROM + read_parquet('{attributes_filepath}') + WHERE + attribute_name = '{attr['attribute_name']}' + {unit_clause} + ) + UPDATE + joined_timeseries + SET + {field_name} = ( + SELECT + CAST(attribute_value AS VARCHAR) + FROM + selected_attribute + WHERE + joined_timeseries.primary_location_id = + selected_attribute.location_id + ) + ;""" + + self.query(query)
+ + +
+[docs] + def calculate_field( + self, + new_field_name: str, + new_field_type: str, + parameter_names: List[str], + user_defined_function: Callable, + replace: bool = True, + ): + """Calculate a new field in joined_timeseries based on existing + fields and a user-defined function. + + Parameters + ---------- + new_field_name : str + Name of new field to be added to joined_timeseries. + new_field_type : str + Data type of the new field. + parameter_names : List[str] + Arguments to your user function, + must be exisiting joined_timeseries fields. + user_defined_function : Callable + Function to apply. + replace : bool + If replace is True and new_field_name already exists, it is + dropped before being recalculated and re-added. + """ + schema_df = self.get_joined_timeseries_schema() + + sanitized_field_name = self._sanitize_field_name(new_field_name) + + cf = CalculateField.model_validate( + { + "new_field_name": sanitized_field_name, + "new_field_type": new_field_type, + "parameter_names": parameter_names, + }, + context={"existing_fields": schema_df.column_name.tolist()}, + ) + + # Get data types of function parameters + schema_dict = dict(zip(schema_df.column_name, schema_df.column_type)) + parameter_types = [schema_dict[param] for param in parameter_names] + + if replace and ( + sanitized_field_name in schema_df.column_name.tolist() + ): + self._drop_joined_timeseries_field(new_field_name) + + self._add_field_name_to_joined_timeseries( + field_name=sanitized_field_name, field_dtype=new_field_type + ) + + # Build the query using the parameter names + args = "" + for name in cf.parameter_names: + args += f"{name}, " + args = args[:-2] + + query = f""" + UPDATE + joined_timeseries + SET + {cf.new_field_name} = ( + SELECT + user_defined_function({args}) + ); + """ + + create_function_args = { + "function": user_defined_function, + "function_name": "user_defined_function", + "parameter_types": parameter_types, + "new_field_type": cf.new_field_type, + } + + self.query(query=query, create_function_args=create_function_args)
+ + +
+[docs] + def _validate_query_model(self, query_model: Any, data: Dict) -> Any: + """Validate the query based on existing table fields.""" + schema_df = self.get_joined_timeseries_schema() + validated_model = query_model.model_validate( + data, + context={"existing_fields": schema_df.column_name.tolist()}, + ) + return validated_model
+ + +
+[docs] + def get_metrics( + self, + group_by: List[str], + order_by: List[str], + include_metrics: Union[List[MetricEnum], str] = "all", + filters: Union[List[dict], None] = None, + include_geometry: bool = False, + return_query: bool = False, + ) -> Union[str, pd.DataFrame, gpd.GeoDataFrame]: + """Calculate performance metrics using database queries. + + Parameters + ---------- + group_by : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + include_metrics : Union[List[MetricEnum], str] + List of metrics (see below) for allowable list, or "all" to return + all. Placeholder, currently ignored -> returns "all". + filters : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data + that is included in metrics. + include_geometry : bool, optional + True joins the geometry to the query results. + Only works if `primary_location_id` + is included as a group_by field, by default False. + return_query : bool, optional + True returns the query string instead of the data, + by default False. + + Returns + ------- + Union[pd.DataFrame, gpd.GeoDataFrame, str] + A DataFrame or optionally a GeoDataFrame containing query results, + or the query itself as a string. + + See Also + -------- + teehr.queries.duckdb_database.create_get_metrics_query : \ + Create the get metrics query. + """ + + data = { + "group_by": group_by, + "order_by": order_by, + "include_metrics": include_metrics, + "filters": filters, + "include_geometry": include_geometry, + "return_query": return_query, + } + mq = self._validate_query_model(MetricQuery, data) + + query = tqu_db.create_get_metrics_query(mq) + + if mq.return_query: + return tqu.remove_empty_lines(query) + elif mq.include_geometry: + self._check_if_geometry_is_inserted() + df = self.query(query, read_only=True, format="df") + return tqu.df_to_gdf(df) + else: + df = self.query(query, read_only=True, format="df") + return df
+ + +
+[docs] + def get_joined_timeseries( + self, + order_by: List[str], + filters: Union[List[dict], None] = None, + include_geometry: bool = False, + return_query: bool = False, + ) -> Union[pd.DataFrame, gpd.GeoDataFrame, str]: + """Retrieve joined timeseries using database query. + + Parameters + ---------- + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + filters : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data + that is included in metrics. + include_geometry : bool + True joins the geometry to the query results. + Only works if `primary_location_id` + is included as a group_by field. + return_query : bool = False + True returns the query string instead of the data. + + Returns + ------- + Union[pd.DataFrame, gpd.GeoDataFrame str] + A DataFrame or GeoDataFrame of query results + or the query itself as a string. + + See Also + -------- + teehr.queries.duckdb_database.create_get_joined_timeseries_query : \ + Create the get joined timeseries query. + """ + + data = { + "order_by": order_by, + "filters": filters, + "return_query": return_query, + "include_geometry": include_geometry, + } + jtq = self._validate_query_model(JoinedTimeseriesQuery, data) + + query = tqu_db.create_get_joined_timeseries_query(jtq) + + if jtq.return_query: + return tqu.remove_empty_lines(query) + elif jtq.include_geometry: + self._check_if_geometry_is_inserted() + df = self.query(query, format="df") + return tqu.df_to_gdf(df) + else: + df = self.query(query, format="df") + return df
+ + +
+[docs] + def get_timeseries( + self, + order_by: List[str], + timeseries_name: str, + filters: Union[List[dict], None] = None, + return_query: bool = False, + ) -> Union[pd.DataFrame, str]: + """Retrieve timeseries using database query. + + Parameters + ---------- + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + timeseries_name : str + Name of the time series to query ('primary' or 'secondary'). + filters : Union[List[dict], None], optional + List of dictionaries describing the "where" clause to limit data + that is included in metrics, by default None. + return_query : bool, optional + True returns the query string instead of the data, + by default False. + + Returns + ------- + Union[pd.DataFrame, str] + A DataFrame of query results or the query itself as a string. + + See Also + -------- + teehr.queries.duckdb_database.create_get_timeseries_query : \ + Create the get timeseries query. + """ + data = { + "order_by": order_by, + "filters": filters, + "return_query": return_query, + "timeseries_name": timeseries_name, + } + tq = self._validate_query_model(TimeseriesQuery, data) + + query = tqu_db.create_get_timeseries_query(tq) + + if tq.return_query: + return tqu.remove_empty_lines(query) + else: + df = self.query(query, read_only=True, format="df") + return df
+ + +
+[docs] + def get_timeseries_chars( + self, + group_by: List[str], + order_by: List[str], + timeseries_name: str, + filters: Union[List[dict], None] = None, + return_query: bool = False, + ) -> Union[str, pd.DataFrame]: + """Retrieve timeseries characteristics using database query. + + Parameters + ---------- + group_by : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + timeseries_name : str + Name of the time series to query (primary or secondary). + filters : Union[List[dict], None], optional + List of dictionaries describing the "where" clause to limit data + that is included in metrics., by default None. + return_query : bool, optional + True returns the query string instead of the data, + by default False. + + Returns + ------- + Union[str, pd.DataFrame] + A DataFrame of time series characteristics including: + - count + - min + - max + - average + - sum + - variance + + or, the query itself as a string. + + See Also + -------- + teehr.queries.duckdb_database.create_get_timeseries_char_query : \ + Create the get timeseries characteristics query. + """ + data = { + "order_by": order_by, + "group_by": group_by, + "timeseries_name": timeseries_name, + "filters": filters, + "return_query": return_query, + } + tcq = self._validate_query_model(TimeseriesCharQuery, data) + + query = tqu_db.create_get_timeseries_char_query(tcq) + + if tcq.return_query: + return tqu.remove_empty_lines(query) + else: + df = self.query(query, read_only=True, format="df") + return df
+ + +
+[docs] + def get_unique_field_values(self, field_name: str) -> pd.DataFrame: + """Get unique values for a given field. + + Parameters + ---------- + field_name : str + Name of the joined_timeseries field. + + Returns + ------- + pd.DataFrame + A dataframe containing unique values for the given field. + + See Also + -------- + teehr.queries.duckdb_database.create_unique_field_values_query : \ + Create the get unique field values query. + """ + data = {"field_name": field_name} + fn = self._validate_query_model(JoinedTimeseriesFieldName, data) + query = tqu_db.create_unique_field_values_query(fn) + df = self.query(query, read_only=True, format="df") + return df
+
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/loading/nwm/grid_utils.html b/_modules/teehr/loading/nwm/grid_utils.html new file mode 100644 index 00000000..5847040b --- /dev/null +++ b/_modules/teehr/loading/nwm/grid_utils.html @@ -0,0 +1,666 @@ + + + + + + + + + + teehr.loading.nwm.grid_utils — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.loading.nwm.grid_utils

+"""Module defining shared functions for processing NWM grid data."""
+from pathlib import Path
+from typing import Dict, List, Tuple
+import re
+
+import dask
+import numpy as np
+import pandas as pd
+import xarray as xr
+
+from teehr.loading.nwm.utils import get_dataset, write_parquet_file
+
+
+
+[docs] +def compute_zonal_mean( + da: xr.DataArray, weights_filepath: str +) -> pd.DataFrame: + """Compute zonal mean of area-weighted pixels for given + zones and weights.""" + # Read weights file + weights_df = pd.read_parquet( + weights_filepath, columns=["row", "col", "weight", "location_id"] + ) + # Get variable data + arr_2d = da.values[0] + arr_2d[arr_2d == da.rio.nodata] = np.nan + # Get row/col indices + rows = weights_df.row.values + cols = weights_df.col.values + # Get the values and apply weights + var_values = arr_2d[rows, cols] + weights_df["value"] = var_values * weights_df.weight.values + # Compute mean + df = weights_df.groupby(by="location_id")["value"].mean().to_frame() + df.reset_index(inplace=True) + + return df
+ + + +@dask.delayed +def process_single_file( + row: Tuple, + configuration: str, + variable_name: str, + weights_filepath: str, + ignore_missing_file: bool, + units_format_dict: Dict +) -> pd.DataFrame: + """Fetch a single json reference file and format \ + to a dataframe using the TEEHR data model.""" + ds = get_dataset( + row.filepath, + ignore_missing_file, + target_options={'anon': True} + ) + if not ds: + return None + yrmoday = row.day + z_hour = row.z_hour[1:3] + ref_time = pd.to_datetime(yrmoday) \ + + pd.to_timedelta(int(z_hour), unit="h") + + nwm_units = ds[variable_name].attrs["units"] + teehr_units = units_format_dict.get(nwm_units, nwm_units) + value_time = ds.time.values[0] + da = ds[variable_name] + + # Calculate mean areal value of selected variable + df = compute_zonal_mean(da, weights_filepath) + + df["value_time"] = value_time + df["reference_time"] = ref_time + df["measurement_unit"] = teehr_units + df["configuration"] = configuration + df["variable_name"] = variable_name + + return df + + +
+[docs] +def fetch_and_format_nwm_grids( + json_paths: List[str], + configuration: str, + variable_name: str, + output_parquet_dir: str, + zonal_weights_filepath: str, + ignore_missing_file: bool, + units_format_dict: Dict, + overwrite_output: bool, +): + """ + Read in the single reference jsons, subset the NWM data based on + provided IDs and format and save the data as a parquet files. + """ + output_parquet_dir = Path(output_parquet_dir) + if not output_parquet_dir.exists(): + output_parquet_dir.mkdir(parents=True) + + # Format file list into a dataframe and group by reference time + pattern = re.compile(r'[0-9]+') + days = [] + z_hours = [] + for path in json_paths: + filename = Path(path).name + if path.split(":")[0] == "s3": + # If it's a remote json day and z-hour are in the path + res = re.findall(pattern, path) + days.append(res[1]) + z_hours.append(f"t{res[2]}z") + else: + days.append(filename.split(".")[1]) + z_hours.append(filename.split(".")[3]) + df_refs = pd.DataFrame( + {"day": days, "z_hour": z_hours, "filepath": json_paths} + ) + gps = df_refs.groupby(["day", "z_hour"]) + + for gp in gps: + _, df = gp + + results = [] + for row in df.itertuples(): + results.append( + process_single_file( + row, + configuration, + variable_name, + zonal_weights_filepath, + ignore_missing_file, + units_format_dict, + ) + ) + + output = dask.compute(*results) + + output = [df for df in output if df is not None] + if len(output) == 0: + raise FileNotFoundError("No NWM files for specified input" + "configuration were found in GCS!") + z_hour_df = pd.concat(output) + + # Save to parquet + yrmoday = df.day.iloc[0] + z_hour = df.z_hour.iloc[0][1:3] + ref_time_str = f"{yrmoday}T{z_hour}Z" + parquet_filepath = Path( + Path(output_parquet_dir), f"{ref_time_str}.parquet" + ) + z_hour_df.sort_values(["location_id", "value_time"], inplace=True) + write_parquet_file(parquet_filepath, overwrite_output, z_hour_df)
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/loading/nwm/nwm_grids.html b/_modules/teehr/loading/nwm/nwm_grids.html new file mode 100644 index 00000000..6319a2ed --- /dev/null +++ b/_modules/teehr/loading/nwm/nwm_grids.html @@ -0,0 +1,764 @@ + + + + + + + + + + teehr.loading.nwm.nwm_grids — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.loading.nwm.nwm_grids

+"""Module for loading and processing NWM gridded data."""
+from typing import Union, Iterable, Optional
+from datetime import datetime
+from pathlib import Path
+
+from teehr.loading.nwm.grid_utils import fetch_and_format_nwm_grids
+from teehr.loading.nwm.utils import (
+    build_remote_nwm_filelist,
+    generate_json_paths,
+    check_dates_against_nwm_version
+)
+from teehr.models.loading.utils import (
+    SupportedNWMOperationalVersionsEnum,
+    SupportedNWMDataSourcesEnum,
+    SupportedKerchunkMethod
+)
+from teehr.loading.nwm.const import (
+    NWM22_UNIT_LOOKUP,
+    NWM22_ANALYSIS_CONFIG,
+    NWM30_ANALYSIS_CONFIG,
+)
+
+
+
+[docs] +def nwm_grids_to_parquet( + configuration: str, + output_type: str, + variable_name: str, + start_date: Union[str, datetime], + ingest_days: int, + zonal_weights_filepath: str, + json_dir: Union[str, Path], + output_parquet_dir: Union[str, Path], + nwm_version: SupportedNWMOperationalVersionsEnum, + data_source: Optional[SupportedNWMDataSourcesEnum] = "GCS", + kerchunk_method: Optional[SupportedKerchunkMethod] = "local", + t_minus_hours: Optional[Iterable[int]] = None, + ignore_missing_file: Optional[bool] = True, + overwrite_output: Optional[bool] = False, +): + """ + Fetch NWM gridded data, calculate zonal statistics (currently only + mean is available) of selected variable for given zones, convert + and save to TEEHR tabular format. + + Parameters + ---------- + configuration : str + NWM forecast category. + (e.g., "analysis_assim", "short_range", ...). + output_type : str + Output component of the configuration. + (e.g., "channel_rt", "reservoir", ...). + variable_name : str + Name of the NWM data variable to download. + (e.g., "streamflow", "velocity", ...). + start_date : str or datetime + Date to begin data ingest. + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + ingest_days : int + Number of days to ingest data after start date. + zonal_weights_filepath : str + Path to the array containing fraction of pixel overlap + for each zone. + json_dir : str + Directory path for saving json reference files. + output_parquet_dir : str + Path to the directory for the final parquet files. + nwm_version : SupportedNWMOperationalVersionsEnum + The NWM operational version. + "nwm22", or "nwm30". + data_source : Optional[SupportedNWMDataSourcesEnum] + Specifies the remote location from which to fetch the data + "GCS" (default), "NOMADS", or "DSTOR". + Currently only "GCS" is implemented. + kerchunk_method : Optional[SupportedKerchunkMethod] + When data_source = "GCS", specifies the preference in creating Kerchunk + reference json files. "local" (default) will create new json files from + netcdf files in GCS and save to a local directory if they do not already + exist locally, in which case the creation is skipped. "remote" - read the + CIROH pre-generated jsons from s3, ignoring any that are unavailable. + "auto" - read the CIROH pre-generated jsons from s3, and create any that + are unavailable, storing locally. + t_minus_hours : Optional[Iterable[int]] + Specifies the look-back hours to include if an assimilation + configuration is specified. + ignore_missing_file : bool + Flag specifying whether or not to fail if a missing NWM file is encountered + True = skip and continue; False = fail. + overwrite_output : bool + Flag specifying whether or not to overwrite output files if they already + exist. True = overwrite; False = fail. + + See Also + -------- + teehr.utilities.generate_weights.generate_weights_file : Weighted average. + + Notes + ----- + The NWM configuration variables, including configuration, output_type, and + variable_name are stored as a pydantic model in grid_config_models.py + + Forecast and assimilation data is grouped and saved one file per reference + time, using the file name convention "YYYYMMDDTHHZ". The tabular output + parquet files follow the timeseries data model described in the + :ref:`data model <data_model>`. + + Examples + -------- + Here we will calculate mean areal precipitation using NWM forcing data for + some watersheds (polygons) a using pre-calculated weights file + (see: :func:`generate_weights_file() + <teehr.utilities.generate_weights.generate_weights_file>` for weights calculation). + + Import the necessary module. + + >>> import teehr.loading.nwm.nwm_grids as tlg + + Specify the input variables. + + >>> CONFIGURATION = "forcing_short_range" + >>> OUTPUT_TYPE = "forcing" + >>> VARIABLE_NAME = "RAINRATE" + >>> START_DATE = "2020-12-18" + >>> INGEST_DAYS = 1 + >>> ZONAL_WEIGHTS_FILEPATH = Path(Path.home(), "nextgen_03S_weights.parquet") + >>> JSON_DIR = Path(Path.home(), "temp/parquet/jsons/") + >>> OUTPUT_DIR = Path(Path.home(), "temp/parquet") + >>> NWM_VERSION = "nwm22" + >>> DATA_SOURCE = "GCS" + >>> KERCHUNK_METHOD = "auto" + >>> T_MINUS = [0, 1, 2] + >>> IGNORE_MISSING_FILE = True + >>> OVERWRITE_OUTPUT = True + + Perform the calculations, writing to the specified directory. + + >>> tlg.nwm_grids_to_parquet( + >>> configuration=CONFIGURATION, + >>> output_type=OUTPUT_TYPE, + >>> variable_name=VARIABLE_NAME, + >>> start_date=START_DATE, + >>> ingest_days=INGEST_DAYS, + >>> zonal_weights_filepath=ZONAL_WEIGHTS_FILEPATH, + >>> json_dir=JSON_DIR, + >>> output_parquet_dir=OUTPUT_DIR, + >>> nwm_version=NWM_VERSION, + >>> data_source=DATA_SOURCE, + >>> kerchunk_method=KERCHUNK_METHOD, + >>> t_minus_hours=T_MINUS, + >>> ignore_missing_file=IGNORE_MISSING_FILE, + >>> overwrite_output=OVERWRITE_OUTPUT + >>> ) + """ # noqa + # Import appropriate config model and dicts based on NWM version + if nwm_version == SupportedNWMOperationalVersionsEnum.nwm22: + from teehr.models.loading.nwm22_grid import GridConfigurationModel + analysis_config_dict = NWM22_ANALYSIS_CONFIG + unit_lookup_dict = NWM22_UNIT_LOOKUP + elif nwm_version == SupportedNWMOperationalVersionsEnum.nwm30: + from teehr.models.loading.nwm30_grid import GridConfigurationModel + analysis_config_dict = NWM30_ANALYSIS_CONFIG + unit_lookup_dict = NWM22_UNIT_LOOKUP + else: + raise ValueError("nwm_version must equal 'nwm22' or 'nwm30'") + + # Parse input parameters to validate configuration + vars = { + "configuration": configuration, + configuration: { + "output_type": output_type, + output_type: variable_name, + }, + } + + cm = GridConfigurationModel.model_validate(vars) + configuration = cm.configuration.name + forecast_obj = getattr(cm, configuration) + output_type = forecast_obj.output_type.name + variable_name = getattr(forecast_obj, output_type).name + + # Check data_source + if data_source == SupportedNWMDataSourcesEnum.NOMADS: + # TODO + raise ValueError("Loading from NOMADS is not yet implemented") + elif data_source == SupportedNWMDataSourcesEnum.DSTOR: + # TODO + raise ValueError("Loading from DSTOR is not yet implemented") + else: + + # Make sure start/end dates work with specified NWM version + check_dates_against_nwm_version(nwm_version, start_date, ingest_days) + + # Build paths to netcdf files on GCS + gcs_component_paths = build_remote_nwm_filelist( + configuration, + output_type, + start_date, + ingest_days, + analysis_config_dict, + t_minus_hours, + ignore_missing_file, + ) + + # Create paths to local and/or remote kerchunk jsons + json_paths = generate_json_paths( + kerchunk_method, + gcs_component_paths, + json_dir, + ignore_missing_file + ) + + # Fetch the data, saving to parquet files based on TEEHR data model + fetch_and_format_nwm_grids( + json_paths, + configuration, + variable_name, + output_parquet_dir, + zonal_weights_filepath, + ignore_missing_file, + unit_lookup_dict, + overwrite_output, + )
+ + + +# if __name__ == "__main__": +# # Local testing +# weights_parquet = "/mnt/data/ciroh/wbdhuc10_weights.parquet" + +# import time +# t1 = time.time() + +# nwm_grids_to_parquet( +# configuration="forcing_analysis_assim", +# output_type="forcing", +# variable_name="RAINRATE", +# start_date="2023-11-28", +# ingest_days=1, +# zonal_weights_filepath=weights_parquet, +# json_dir="/mnt/data/ciroh/jsons", +# output_parquet_dir="/mnt/data/ciroh/parquet", +# nwm_version="nwm30", +# data_source="GCS", +# kerchunk_method="use_available", +# t_minus_hours=[0], +# ignore_missing_file=False, +# overwrite_output=True +# ) + +# print(f"elapsed: {time.time() - t1:.2f} s") +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/loading/nwm/nwm_points.html b/_modules/teehr/loading/nwm/nwm_points.html new file mode 100644 index 00000000..f01cd951 --- /dev/null +++ b/_modules/teehr/loading/nwm/nwm_points.html @@ -0,0 +1,810 @@ + + + + + + + + + + teehr.loading.nwm.nwm_points — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.loading.nwm.nwm_points

+"""Module for fetchning and processing NWM point data."""
+from typing import Union, Optional, List
+from datetime import datetime
+from pathlib import Path
+
+from pydantic import validate_call
+
+from teehr.loading.nwm.point_utils import (
+    fetch_and_format_nwm_points,
+)
+from teehr.loading.nwm.utils import (
+    generate_json_paths,
+    build_remote_nwm_filelist,
+    check_dates_against_nwm_version
+)
+from teehr.models.loading.utils import (
+    SupportedNWMOperationalVersionsEnum,
+    SupportedNWMDataSourcesEnum,
+    SupportedKerchunkMethod
+)
+from teehr.loading.nwm.const import (
+    NWM22_UNIT_LOOKUP,
+    NWM22_ANALYSIS_CONFIG,
+    NWM30_ANALYSIS_CONFIG,
+)
+
+
+
+[docs] +@validate_call() +def nwm_to_parquet( + configuration: str, + output_type: str, + variable_name: str, + start_date: Union[str, datetime], + ingest_days: int, + location_ids: List[int], + json_dir: Union[str, Path], + output_parquet_dir: Union[str, Path], + nwm_version: SupportedNWMOperationalVersionsEnum, + data_source: Optional[SupportedNWMDataSourcesEnum] = "GCS", + kerchunk_method: Optional[SupportedKerchunkMethod] = "local", + t_minus_hours: Optional[List[int]] = None, + process_by_z_hour: Optional[bool] = True, + stepsize: Optional[int] = 100, + ignore_missing_file: Optional[bool] = True, + overwrite_output: Optional[bool] = False, +): + """Fetch NWM point data, format to tabular TEEHR data model, + and save to parquet. + + Parameters + ---------- + configuration : str + NWM forecast category. + (e.g., "analysis_assim", "short_range", ...). + output_type : str + Output component of the configuration. + (e.g., "channel_rt", "reservoir", ...). + variable_name : str + Name of the NWM data variable to download. + (e.g., "streamflow", "velocity", ...). + start_date : str or datetime + Date to begin data ingest. + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + ingest_days : int + Number of days to ingest data after start date. + location_ids : List[int] + Array specifying NWM IDs of interest. + json_dir : str + Directory path for saving json reference files. + output_parquet_dir : str + Path to the directory for the final parquet files. + nwm_version : SupportedNWMOperationalVersionsEnum + The NWM operational version + "nwm22", or "nwm30". + data_source : Optional[SupportedNWMDataSourcesEnum] + Specifies the remote location from which to fetch the data + "GCS" (default), "NOMADS", or "DSTOR" + Currently only "GCS" is implemented. + kerchunk_method : Optional[SupportedKerchunkMethod] + When data_source = "GCS", specifies the preference in creating Kerchunk + reference json files. "local" (default) will create new json files from + netcdf files in GCS and save to a local directory if they do not already + exist locally, in which case the creation is skipped. "remote" - read the + CIROH pre-generated jsons from s3, ignoring any that are unavailable. + "auto" - read the CIROH pre-generated jsons from s3, and create any that + are unavailable, storing locally. + t_minus_hours : Optional[List[int]] + Specifies the look-back hours to include if an assimilation + configuration is specified. + process_by_z_hour : Optional[bool] + A boolean flag that determines the method of grouping files + for processing. The default is True, which groups by day and z_hour. + False groups files sequentially into chunks, whose size is determined + by stepsize. This allows users to process more data potentially more + efficiently, but runs to risk of splitting up forecasts into separate + output files. + stepsize : Optional[int] + The number of json files to process at one time. Used if + process_by_z_hour is set to False. Default value is 100. Larger values + can result in greater efficiency but require more memory. + ignore_missing_file : Optional[bool] + Flag specifying whether or not to fail if a missing NWM file is encountered + True = skip and continue. + False = fail. + overwrite_output : Optional[bool] + Flag specifying whether or not to overwrite output files if they already + exist. True = overwrite; False = fail. + + Notes + ----- + The NWM configuration variables, including configuration, output_type, and + variable_name are stored as pydantic models in point_config_models.py + + Forecast and assimilation data is grouped and saved one file per reference + time, using the file name convention "YYYYMMDDTHHZ". The tabular output + parquet files follow the timeseries data model described in the + :ref:`data model <data_model>`. + + Examples + -------- + Here we fetch operational streamflow forecasts for NWM v2.2 from GCS, and + save the output in the TEEHR :ref:`data model <data_model>` format. + + Import the necessary module. + + >>> import teehr.loading.nwm.nwm_points as tlp + + Specify the input variables. + + >>> CONFIGURATION = "short_range" + >>> OUTPUT_TYPE = "channel_rt" + >>> VARIABLE_NAME = "streamflow" + >>> START_DATE = "2023-03-18" + >>> INGEST_DAYS = 1 + >>> JSON_DIR = Path(Path.home(), "temp/parquet/jsons/") + >>> OUTPUT_DIR = Path(Path.home(), "temp/parquet") + >>> NWM_VERSION = "nwm22" + >>> DATA_SOURCE = "GCS" + >>> KERCHUNK_METHOD = "auto" + >>> T_MINUS = [0, 1, 2] + >>> IGNORE_MISSING_FILE = True + >>> OVERWRITE_OUTPUT = True + >>> PROCESS_BY_Z_HOUR = True + >>> STEPSIZE = 100 + >>> LOCATION_IDS = [7086109, 7040481, 7053819] + + Fetch and format the data, writing to the specified directory. + + >>> tlp.nwm_to_parquet( + >>> configuration=CONFIGURATION, + >>> output_type=OUTPUT_TYPE, + >>> variable_name=VARIABLE_NAME, + >>> start_date=START_DATE, + >>> ingest_days=INGEST_DAYS, + >>> location_ids=LOCATION_IDS, + >>> json_dir=JSON_DIR, + >>> output_parquet_dir=OUTPUT_DIR, + >>> nwm_version=NWM_VERSION, + >>> data_source=DATA_SOURCE, + >>> kerchunk_method=KERCHUNK_METHOD, + >>> t_minus_hours=T_MINUS, + >>> process_by_z_hour=PROCESS_BY_Z_HOUR, + >>> stepsize=STEPSIZE, + >>> ignore_missing_file=IGNORE_MISSING_FILE, + >>> overwrite_output=OVERWRITE_OUTPUT, + >>> ) + """ + + # Import appropriate config model and dicts based on NWM version + if nwm_version == SupportedNWMOperationalVersionsEnum.nwm22: + from teehr.models.loading.nwm22_point import PointConfigurationModel + analysis_config_dict = NWM22_ANALYSIS_CONFIG + unit_lookup_dict = NWM22_UNIT_LOOKUP + elif nwm_version == SupportedNWMOperationalVersionsEnum.nwm30: + from teehr.models.loading.nwm30_point import PointConfigurationModel + analysis_config_dict = NWM30_ANALYSIS_CONFIG + unit_lookup_dict = NWM22_UNIT_LOOKUP + else: + raise ValueError("nwm_version must equal 'nwm22' or 'nwm30'") + + # Parse input parameters to validate configuration + vars = { + "configuration": configuration, + configuration: { + "output_type": output_type, + output_type: variable_name, + }, + } + cm = PointConfigurationModel.model_validate(vars) + configuration = cm.configuration.name + forecast_obj = getattr(cm, configuration) + output_type = forecast_obj.output_type.name + variable_name = getattr(forecast_obj, output_type).name + + # Check data_source + if data_source == SupportedNWMDataSourcesEnum.NOMADS: + # TODO + raise ValueError("Loading from NOMADS is not yet implemented") + elif data_source == SupportedNWMDataSourcesEnum.DSTOR: + # TODO + raise ValueError("Loading from DSTOR is not yet implemented") + else: + + # Make sure start/end dates work with specified NWM version + check_dates_against_nwm_version(nwm_version, start_date, ingest_days) + + # Build paths to netcdf files on GCS + gcs_component_paths = build_remote_nwm_filelist( + configuration, + output_type, + start_date, + ingest_days, + analysis_config_dict, + t_minus_hours, + ignore_missing_file, + ) + + # Create paths to local and/or remote kerchunk jsons + json_paths = generate_json_paths( + kerchunk_method, + gcs_component_paths, + json_dir, + ignore_missing_file + ) + + # Fetch the data, saving to parquet files based on TEEHR data model + fetch_and_format_nwm_points( + json_paths, + location_ids, + configuration, + variable_name, + output_parquet_dir, + process_by_z_hour, + stepsize, + ignore_missing_file, + unit_lookup_dict, + overwrite_output, + nwm_version, + )
+ + + +# if __name__ == "__main__": +# # analysis_assim_extend, short_range, analysis_assim_alaska +# configuration = ( +# "analysis_assim_extend" +# ) +# output_type = "channel_rt" +# variable_name = "streamflow" +# start_date = "2023-11-28" +# ingest_days = 1 +# location_ids = [ +# 7086109, +# 7040481, +# 7053819, +# 7111205, +# 7110249, +# 14299781, +# 14251875, +# 14267476, +# 7152082, +# 14828145, +# ] +# # location_ids = np.load( +# # "/mnt/sf_shared/data/ciroh/temp_location_ids.npy" +# # ) # all 2.7 million +# json_dir = "/mnt/data/ciroh/jsons" +# output_parquet_dir = "/mnt/data/ciroh/parquet" + +# process_by_z_hour = True +# stepsize = 100 +# ignore_missing_file = False + +# import time +# t1 = time.time() + +# nwm_to_parquet( +# configuration, +# output_type, +# variable_name, +# start_date, +# ingest_days, +# location_ids, +# json_dir, +# output_parquet_dir, +# nwm_version="nwm30", +# data_source="GCS", +# kerchunk_method="use_available", +# t_minus_hours=[0], +# process_by_z_hour=process_by_z_hour, +# stepsize=stepsize, +# ignore_missing_file=ignore_missing_file, +# overwrite_output=False, +# ) + +# print(f"elapsed: {time.time() - t1:.2f} s") +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/loading/nwm/point_utils.html b/_modules/teehr/loading/nwm/point_utils.html new file mode 100644 index 00000000..6471d209 --- /dev/null +++ b/_modules/teehr/loading/nwm/point_utils.html @@ -0,0 +1,747 @@ + + + + + + + + + + teehr.loading.nwm.point_utils — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.loading.nwm.point_utils

+"""Module defining shared functions for processing NWM point data."""
+from pathlib import Path
+from typing import Dict, Iterable, List, Tuple
+import re
+
+import dask
+import numpy as np
+import pandas as pd
+import pyarrow as pa
+
+from teehr.loading.nwm.utils import (
+    get_dataset,
+    write_parquet_file,
+)
+
+
+@dask.delayed
+def file_chunk_loop(
+    row: Tuple,
+    location_ids: np.array,
+    variable_name: str,
+    configuration: str,
+    schema: pa.Schema,
+    ignore_missing_file: bool,
+    units_format_dict: Dict,
+    nwm_version: str
+):
+    """Fetch NWM values and convert to tabular format for a single json."""
+    ds = get_dataset(
+        row.filepath,
+        ignore_missing_file,
+        target_options={'anon': True}
+    )
+    if not ds:
+        return None
+    ds = ds.sel(feature_id=location_ids)
+    vals = ds[variable_name].astype("float32").values
+    nwm22_units = ds[variable_name].units
+    teehr_units = units_format_dict.get(nwm22_units, nwm22_units)
+    ref_time = pd.to_datetime(row.day) \
+        + pd.to_timedelta(int(row.z_hour[1:3]), unit="h")
+
+    valid_time = ds.time.values
+    feature_ids = ds.feature_id.astype("int32").values
+    teehr_location_ids = \
+        [f"{nwm_version}-{feat_id}" for feat_id in feature_ids]
+    num_vals = vals.size
+
+    output_table = pa.table(
+        {
+            "value": vals,
+            "reference_time": np.full(vals.shape, ref_time),
+            "location_id": teehr_location_ids,
+            "value_time": np.full(vals.shape, valid_time),
+            "configuration": num_vals * [configuration],
+            "variable_name": num_vals * [variable_name],
+            "measurement_unit": num_vals * [teehr_units],
+        },
+        schema=schema,
+    )
+
+    return output_table
+
+
+
+[docs] +def process_chunk_of_files( + df: pd.DataFrame, + location_ids: Iterable[int], + configuration: str, + variable_name: str, + output_parquet_dir: str, + process_by_z_hour: bool, + ignore_missing_file: bool, + units_format_dict: Dict, + overwrite_output: bool, + nwm_version: str +): + """Assemble a table for a chunk of NWM files.""" + + location_ids = np.array(location_ids).astype(int) + + schema = pa.schema( + [ + ("value", pa.float32()), + ("reference_time", pa.timestamp("ms")), + ("location_id", pa.string()), + ("value_time", pa.timestamp("ms")), + ("configuration", pa.string()), + ("variable_name", pa.string()), + ("measurement_unit", pa.string()), + ] + ) + + results = [] + for row in df.itertuples(): + results.append( + file_chunk_loop( + row, + location_ids, + variable_name, + configuration, + schema, + ignore_missing_file, + units_format_dict, + nwm_version + ) + ) + output = dask.compute(*results) + + if not any(output): + raise FileNotFoundError("No NWM files for specified input" + "configuration were found in GCS!") + + output = [tbl for tbl in output if tbl is not None] + output_table = pa.concat_tables(output) + + if process_by_z_hour: + row = df.iloc[0] + filename = f"{row.day}T{row.z_hour[1:3]}Z.parquet" + else: + # Use start and end dates including forecast hour + # for the output file name + filepath_list = df.filepath.sort_values().tolist() + start_json = filepath_list[0].split("/")[-1].split(".") + start = f"{start_json[1]}T{start_json[3][1:3]}Z{start_json[6][1:]}F" + end_json = filepath_list[-1].split("/")[-1].split(".") + end = f"{end_json[1]}T{end_json[3][1:3]}Z{end_json[6][1:]}F" + filename = f"{start}_{end}.parquet" + + write_parquet_file( + Path(output_parquet_dir, filename), + overwrite_output, + output_table + )
+ + + +
+[docs] +def fetch_and_format_nwm_points( + json_paths: List[str], + location_ids: Iterable[int], + configuration: str, + variable_name: str, + output_parquet_dir: str, + process_by_z_hour: bool, + stepsize: int, + ignore_missing_file: bool, + units_format_dict: Dict, + overwrite_output: bool, + nwm_version: str +): + """Read in the single reference jsons, subset the + NWM data based on provided IDs and formats and save + the data as parquet files using Dask. + + Parameters + ---------- + json_paths : list + List of the single json reference filepaths. + location_ids : Iterable[int] + Array specifying NWM IDs of interest. + configuration : str + NWM forecast category. + variable_name : str + Name of the NWM data variable to download. + output_parquet_dir : str + Path to the directory for the final parquet files. + process_by_z_hour : bool + A boolean flag that determines the method of grouping files + for processing. + stepsize : int + The number of json files to process at one time. + ignore_missing_file : bool + Flag specifying whether or not to fail if a missing NWM + file is encountered + True = skip and continue + False = fail. + units_format_dict : Dict, + Dictionary of unit formats. + overwrite_output : bool + Flag specifying whether or not to overwrite output files if + they already exist. True = overwrite; False = fail. + nwm_version : str + Specified NWM version. + """ + + output_parquet_dir = Path(output_parquet_dir) + if not output_parquet_dir.exists(): + output_parquet_dir.mkdir(parents=True) + + # Format file list into a dataframe and group by specified method + pattern = re.compile(r'[0-9]+') + days = [] + z_hours = [] + for path in json_paths: + filename = Path(path).name + if path.split(":")[0] == "s3": + # If it's a remote json day and z-hour are in the path + res = re.findall(pattern, path) + days.append(res[1]) + z_hours.append(f"t{res[2]}z") + else: + days.append(filename.split(".")[1]) + z_hours.append(filename.split(".")[3]) + df_refs = pd.DataFrame( + {"day": days, "z_hour": z_hours, "filepath": json_paths} + ) + if process_by_z_hour: + # Option #1. Groupby day and z_hour + gps = df_refs.groupby(["day", "z_hour"]) + dfs = [df for _, df in gps] + else: + # Option #2. Chunk by some number of files + if stepsize > df_refs.index.size: + num_partitions = 1 + else: + num_partitions = int(df_refs.index.size / stepsize) + dfs = np.array_split(df_refs, num_partitions) + + for df in dfs: + process_chunk_of_files( + df, + location_ids, + configuration, + variable_name, + output_parquet_dir, + process_by_z_hour, + ignore_missing_file, + units_format_dict, + overwrite_output, + nwm_version + )
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/loading/nwm/retrospective_grids.html b/_modules/teehr/loading/nwm/retrospective_grids.html new file mode 100644 index 00000000..64f7e96d --- /dev/null +++ b/_modules/teehr/loading/nwm/retrospective_grids.html @@ -0,0 +1,881 @@ + + + + + + + + + + teehr.loading.nwm.retrospective_grids — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.loading.nwm.retrospective_grids

+"""A module for loading retrospective NWM gridded data."""
+import time
+from datetime import datetime
+from pathlib import Path
+from typing import Union, Optional, Tuple, Dict
+
+import pandas as pd
+import xarray as xr
+import fsspec
+from pydantic import validate_call
+import numpy as np
+import dask
+
+from teehr.loading.nwm.const import NWM22_UNIT_LOOKUP
+from teehr.models.loading.utils import (
+    ChunkByEnum,
+    SupportedNWMRetroVersionsEnum,
+    SupportedNWMRetroDomainsEnum
+)
+from teehr.models.loading.nwm22_grid import ForcingVariablesEnum
+from teehr.loading.nwm.utils import write_parquet_file, get_dataset
+from teehr.loading.nwm.retrospective_points import (
+    format_grouped_filename,
+    validate_start_end_date,
+)
+
+
+
+[docs] +def get_data_array(var_da: xr.DataArray, rows: np.array, cols: np.array): + """Read the forcing variable into memory for the grouped + timesteps and zone rows and cols.""" + var_arr = var_da.values[:, rows, cols] + return var_arr
+ + + +
+[docs] +def process_group( + da_i: xr.DataArray, + rows: np.array, + cols: np.array, + weights_df: pd.DataFrame, + weight_vals: np.array, + variable_name: str, + units_format_dict: Dict, + nwm_version: str +): + """Fetch a chunk of NWM v3.0 gridded data, compute weighted + values for each zone, and format to dataframe.""" + var_arr = get_data_array(da_i, rows, cols) + + # Get the subset data array of start and end times just for the time values + time_subset_vals = da_i.time.values + + hourly_dfs = [] + for i, dt in enumerate(time_subset_vals): + # Calculate weighted pixel values + weights_df["value"] = var_arr[i, :] * weight_vals + # Compute mean per group/location + df = weights_df.groupby( + by="location_id", observed=True + )["value"].mean().to_frame() + df["value_time"] = pd.to_datetime(dt) + df.reset_index(inplace=True) + hourly_dfs.append(df) + + chunk_df = pd.concat(hourly_dfs) + chunk_df["reference_time"] = chunk_df.value_time + nwm_units = da_i.attrs["units"] + teehr_units = units_format_dict.get(nwm_units, nwm_units) + chunk_df["measurement_unit"] = teehr_units + chunk_df["configuration"] = f"{nwm_version}_retrospective" + chunk_df["variable_name"] = variable_name + chunk_df["location_id"] = ( + f"{nwm_version}-" + chunk_df["location_id"].astype(str) + ) + return chunk_df
+ + + +
+[docs] +def construct_nwm21_json_paths( + start_date: Union[str, datetime], + end_date: Union[str, datetime] +): + """Construct the remote paths for the NWM v2.1 zarr json files + within the specified start and end dates.""" + base_path = ( + "s3://ciroh-nwm-zarr-retrospective-data-copy/" + "noaa-nwm-retrospective-2-1-zarr-pds/forcing" + ) + date_rng = pd.date_range(start_date, end_date, freq="H") + dates = [] + paths = [] + for dt in date_rng: + dates.append(dt) + paths.append( + f"{base_path}/{dt.year}/{dt.year}{dt.month:02d}" + f"{dt.day:02d}{dt.hour:02d}.LDASIN_DOMAIN1.json" + ) + paths_df = pd.DataFrame({"datetime": dates, "filepath": paths}) + return paths_df
+ + + +
+[docs] +def compute_zonal_mean( + var_da: xr.DataArray, weights_filepath: str, time_dt: pd.Timestamp +) -> pd.DataFrame: + """Compute the zonal mean for given zones and weights.""" + weights_df = pd.read_parquet( + weights_filepath, columns=["row", "col", "weight", "location_id"] + ) + # Get row/col indices + rows = weights_df.row.values + cols = weights_df.col.values + arr_2d = var_da.compute().values + var_values = arr_2d[rows, cols] + # Get the values and apply weights + weights_df["value"] = var_values * weights_df.weight.values + # Compute mean + df = weights_df.groupby(by="location_id")["value"].mean().to_frame() + df["value_time"] = time_dt + df.reset_index(inplace=True) + return df
+ + + +@dask.delayed +def process_single_file( + row: Tuple, + variable_name: str, + weights_filepath: str, + ignore_missing_file: bool, + units_format_dict: Dict, + nwm_version: str +): + """Compute the zonal mean for a single json reference file and format + to a dataframe using the TEEHR data model.""" + ds = get_dataset( + row.filepath, + ignore_missing_file, + target_options={'anon': True} + ) + if not ds: + return None + + nwm_units = ds[variable_name].attrs["units"] + teehr_units = units_format_dict.get(nwm_units, nwm_units) + value_time = row.datetime + da = ds[variable_name].isel(Time=0) + + # Calculate mean areal of selected variable + df = compute_zonal_mean(da, weights_filepath, value_time) + + df["value_time"] = value_time + df["reference_time"] = value_time + df["measurement_unit"] = teehr_units + df["configuration"] = f"{nwm_version}_retrospective" + df["variable_name"] = variable_name + df["location_id"] = f"{nwm_version}-" + df["location_id"].astype(str) + + return df + + +
+[docs] +@validate_call(config=dict(arbitrary_types_allowed=True)) +def nwm_retro_grids_to_parquet( + nwm_version: SupportedNWMRetroVersionsEnum, + variable_name: ForcingVariablesEnum, + zonal_weights_filepath: Union[str, Path], + start_date: Union[str, datetime, pd.Timestamp], + end_date: Union[str, datetime, pd.Timestamp], + output_parquet_dir: Union[str, Path], + chunk_by: Union[ChunkByEnum, None] = None, + overwrite_output: Optional[bool] = False, + domain: Optional[SupportedNWMRetroDomainsEnum] = "CONUS" +): + """Fetch NWM v2.1 or v3.0 gridded data, summarize to zones using + a pre-computed weight file, and save as a Parquet file. + + Parameters + ---------- + nwm_version : SupportedNWMRetroVersionsEnum + NWM retrospective version to fetch. + Currently `nwm21` and `nwm30` supported. + variable_name : str + Name of the NWM forcing data variable to download. + (e.g., "PRECIP", "PSFC", "Q2D", ...). + zonal_weights_filepath : str, + Path to the array containing fraction of pixel overlap + for each zone. + start_date : Union[str, datetime, pd.Timestamp] + Date to begin data ingest. + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + Rounds down to beginning of day. + end_date : Union[str, datetime, pd.Timestamp], + Last date to fetch. Rounds up to end of day. + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + output_parquet_dir : Union[str, Path], + Directory where output will be saved. + chunk_by : Union[ChunkByEnum, None] = None, + If None (default) saves all timeseries to a single file, otherwise + the data is processed using the specified parameter. + Can be: 'location_id', 'day', 'week', 'month', or 'year'. + overwrite_output : bool = False, + Whether output should overwrite files if they exist. Default is False. + domain : str = "CONUS" + Geographical domain when NWM version is v3.0. + Acceptable values are "Alaska", "CONUS" (default), "Hawaii", and "PR". + Only used when NWM version equals v3.0. + """ + + start_date = pd.Timestamp(start_date) + end_date = pd.Timestamp(end_date) + + validate_start_end_date(nwm_version, start_date, end_date) + + output_dir = Path(output_parquet_dir) + if not output_dir.exists(): + output_dir.mkdir(parents=True) + + if nwm_version == SupportedNWMRetroVersionsEnum.nwm21: + + # Construct Kerchunk-json paths within the selected time + nwm21_paths = construct_nwm21_json_paths(start_date, end_date) + + if chunk_by is None: + gps = [(None, nwm21_paths)] + + if chunk_by == "week": + gps = nwm21_paths.groupby( + pd.Grouper(key='datetime', axis=0, freq='W', sort=True) + ) + + if chunk_by == "month": + gps = nwm21_paths.groupby( + pd.Grouper(key='datetime', axis=0, freq='M', sort=True) + ) + + if chunk_by == "year": + raise ValueError( + "Chunkby 'year' is not yet implemented for gridded data" + ) + + for _, df in gps: + # Process this chunk using dask delayed + results = [] + for row in df.itertuples(): + results.append( + process_single_file( + row=row, + variable_name=variable_name, + weights_filepath=zonal_weights_filepath, + ignore_missing_file=False, + units_format_dict=NWM22_UNIT_LOOKUP, + nwm_version=nwm_version + ) + ) + output = dask.compute(*results) + + output = [df for df in output if df is not None] + if len(output) == 0: + raise FileNotFoundError("No NWM files for specified input" + "configuration were found in GCS!") + chunk_df = pd.concat(output) + + start = df.datetime.min().strftime("%Y%m%dZ") + end = df.datetime.max().strftime("%Y%m%dZ") + if start == end: + output_filename = Path(output_parquet_dir, f"{start}.parquet") + else: + output_filename = Path( + output_parquet_dir, + f"{start}_{end}.parquet" + ) + write_parquet_file( + filepath=output_filename, + overwrite_output=overwrite_output, + data=chunk_df) + + if nwm_version == SupportedNWMRetroVersionsEnum.nwm30: + + # Construct the path to the zarr store + if variable_name == "RAINRATE": + zarr_name = "precip" + else: + zarr_name = variable_name.lower() + s3_zarr_url = ( + f"s3://noaa-nwm-retrospective-3-0-pds/{domain}/" + f"zarr/forcing/{zarr_name}.zarr" + ) + + var_da = xr.open_zarr( + fsspec.get_mapper(s3_zarr_url, anon=True), + chunks={}, consolidated=True + )[variable_name].sel(time=slice(start_date, end_date)) + + # Get weights and their row/col indices + weights_df = pd.read_parquet( + zonal_weights_filepath, + columns=["row", "col", "weight", "location_id"] + ) + weights_df["location_id"] = weights_df.location_id.astype("category") + + rows = weights_df.row.values + cols = weights_df.col.values + weight_vals = weights_df.weight.values + + if chunk_by is None: + gps = [(None, var_da)] + + if chunk_by == "week": + gps = var_da.groupby(var_da.time.dt.isocalendar().week) + + if chunk_by == "month": + gps = var_da.groupby("time.month") + + if chunk_by == "year": + raise ValueError( + "Chunkby 'year' is not yet implemented for gridded data" + ) + + for _, da_i in gps: + + chunk_df = process_group( + da_i=da_i, + rows=rows, + cols=cols, + weights_df=weights_df, + weight_vals=weight_vals, + variable_name=variable_name, + units_format_dict=NWM22_UNIT_LOOKUP, + nwm_version=nwm_version + ) + + fname = format_grouped_filename(da_i) + output_filename = Path( + output_parquet_dir, + fname + ) + write_parquet_file( + filepath=output_filename, + overwrite_output=overwrite_output, + data=chunk_df)
+ + + +if __name__ == "__main__": + + t0 = time.time() + + nwm_retro_grids_to_parquet( + nwm_version="nwm30", + variable_name="RAINRATE", + zonal_weights_filepath="/mnt/data/ciroh/wbdhuc10_weights.parquet", + start_date="2008-05-22 00:00", + end_date="2008-05-22 23:00", + output_parquet_dir="/mnt/data/ciroh/retro", + overwrite_output=True, + chunk_by=None + ) + + print(f"Total elapsed: {(time.time() - t0):.2f} secs") +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/loading/nwm/retrospective_points.html b/_modules/teehr/loading/nwm/retrospective_points.html new file mode 100644 index 00000000..7072173e --- /dev/null +++ b/_modules/teehr/loading/nwm/retrospective_points.html @@ -0,0 +1,877 @@ + + + + + + + + + + teehr.loading.nwm.retrospective_points — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.loading.nwm.retrospective_points

+"""A module for loading retrospective NWM point data."""
+import pandas as pd
+import xarray as xr
+import fsspec
+from pydantic import validate_call
+
+from datetime import datetime, timedelta
+
+from pathlib import Path
+from typing import Union, List, Optional
+
+from teehr.loading.nwm.const import NWM22_UNIT_LOOKUP
+from teehr.models.loading.utils import (
+    ChunkByEnum,
+    SupportedNWMRetroVersionsEnum,
+    SupportedNWMRetroDomainsEnum
+)
+from teehr.loading.nwm.utils import write_parquet_file
+
+NWM20_MIN_DATE = datetime(1993, 1, 1)
+NWM20_MAX_DATE = datetime(2018, 12, 31, 23)
+NWM21_MIN_DATE = pd.Timestamp(1979, 1, 1)
+NWM21_MAX_DATE = pd.Timestamp(2020, 12, 31, 23)
+NWM30_MIN_DATE = pd.Timestamp(1979, 2, 1, 1)
+NWM30_MAX_DATE = pd.Timestamp(2023, 1, 31, 23)
+
+
+
+[docs] +def validate_start_end_date( + nwm_version: SupportedNWMRetroVersionsEnum, + start_date: Union[str, datetime], + end_date: Union[str, datetime] +): + """Validate the start and end dates by NWM version.""" + if nwm_version == SupportedNWMRetroVersionsEnum.nwm20: + if end_date <= start_date: + raise ValueError("start_date must be before end_date") + + if start_date < NWM21_MIN_DATE: + raise ValueError( + f"start_date must be on or after {NWM20_MIN_DATE}" + ) + + if end_date > NWM21_MAX_DATE: + raise ValueError(f"end_date must be on or before {NWM20_MAX_DATE}") + + elif nwm_version == SupportedNWMRetroVersionsEnum.nwm21: + if end_date <= start_date: + raise ValueError("start_date must be before end_date") + + if start_date < NWM21_MIN_DATE: + raise ValueError( + f"start_date must be on or after {NWM21_MIN_DATE}" + ) + + if end_date > NWM21_MAX_DATE: + raise ValueError(f"end_date must be on or before {NWM21_MAX_DATE}") + elif nwm_version == SupportedNWMRetroVersionsEnum.nwm30: + if end_date <= start_date: + raise ValueError("start_date must be before end_date") + + if start_date < NWM30_MIN_DATE: + raise ValueError( + f"start_date must be on or after {NWM30_MIN_DATE}" + ) + + if end_date > NWM30_MAX_DATE: + raise ValueError(f"end_date must be on or before {NWM30_MAX_DATE}") + else: + raise ValueError(f"unsupported NWM version {nwm_version}")
+ + + +
+[docs] +def da_to_df( + nwm_version: SupportedNWMRetroVersionsEnum, + da: xr.DataArray +) -> pd.DataFrame: + """Format NWM retrospective data to TEEHR format.""" + df = da.to_dataframe() + df.reset_index(inplace=True) + df["measurement_unit"] = NWM22_UNIT_LOOKUP.get(da.units, da.units) + df["variable_name"] = da.name + df["configuration"] = f"{nwm_version}_retrospective" + df["reference_time"] = df["time"] + df.rename( + columns={ + "time": "value_time", + "feature_id": "location_id", + da.name: "value", + }, + inplace=True, + ) + df.drop(columns=["latitude", "longitude"], inplace=True) + + df["location_id"] = f"{nwm_version}-" + df["location_id"].astype(str) + df["location_id"] = df["location_id"].astype(str).astype("category") + df["measurement_unit"] = df["measurement_unit"].astype("category") + df["variable_name"] = df["variable_name"].astype("category") + df["configuration"] = df["configuration"].astype("category") + + if nwm_version == "nwm21": + df.drop(columns=["elevation", "gage_id", "order"], inplace=True) + + return df
+ + + +
+[docs] +def datetime_to_date(dt: datetime) -> datetime: + """Convert datetime to date only.""" + dt.replace(hour=0, minute=0, second=0, microsecond=0) + return dt
+ + + +
+[docs] +def format_grouped_filename(ds_i: xr.Dataset) -> str: + """Format the output filename based on min and max + datetime in the dataset.""" + min_year = ds_i.time.min().dt.year + min_month = ds_i.time.min().dt.month + min_day = ds_i.time.min().dt.day + + max_year = ds_i.time.max().dt.year + max_month = ds_i.time.max().dt.month + max_day = ds_i.time.max().dt.day + + min_time = f"{min_year.values}{min_month.values:02d}{min_day.values:02d}Z" + max_time = f"{max_year.values}{max_month.values:02d}{max_day.values:02d}Z" + + if min_time == max_time: + return f"{min_time}.parquet" + else: + return f"{min_time}_{max_time}.parquet"
+ + + +
+[docs] +@validate_call(config=dict(arbitrary_types_allowed=True)) +def nwm_retro_to_parquet( + nwm_version: SupportedNWMRetroVersionsEnum, + variable_name: str, + location_ids: List[int], + start_date: Union[str, datetime, pd.Timestamp], + end_date: Union[str, datetime, pd.Timestamp], + output_parquet_dir: Union[str, Path], + chunk_by: Union[ChunkByEnum, None] = None, + overwrite_output: Optional[bool] = False, + domain: Optional[SupportedNWMRetroDomainsEnum] = "CONUS" +): + """Fetch NWM retrospective at NWM COMIDs and store as Parquet file. + + Parameters + ---------- + nwm_version : SupportedNWMRetroVersionsEnum + NWM retrospective version to fetch. + Currently `nwm20`, `nwm21`, and `nwm30` supported. + variable_name : str + Name of the NWM data variable to download. + (e.g., "streamflow", "velocity", ...). + location_ids : Iterable[int], + NWM feature_ids to fetch. + start_date : Union[str, datetime, pd.Timestamp] + Date to begin data ingest. + Str formats can include YYYY-MM-DD or MM/DD/YYYY + Rounds down to beginning of day. + end_date : Union[str, datetime, pd.Timestamp], + Last date to fetch. Rounds up to end of day + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + output_parquet_dir : Union[str, Path], + Directory where output will be saved. + chunk_by : Union[ChunkByEnum, None] = None, + If None (default) saves all timeseries to a single file, otherwise + the data is processed using the specified parameter. + Can be: 'location_id', 'day', 'week', 'month', or 'year'. + overwrite_output : bool = False, + Whether output should overwrite files if they exist. Default is False. + domain : str = "CONUS" + Geographical domain when NWM version is v3.0. + Acceptable values are "Alaska", "CONUS" (default), "Hawaii", and "PR". + Only used when NWM version equals `nwm30`. + + Examples + -------- + Here we fetch and format retrospective NWM v2.0 streamflow data + for two locations. + + Import the module. + + >>> import teehr.loading.nwm.retrospective_points as nwm_retro + + Specify the input variables. + + >>> NWM_VERSION = "nwm20" + >>> VARIABLE_NAME = "streamflow" + >>> START_DATE = datetime(2000, 1, 1) + >>> END_DATE = datetime(2000, 1, 2, 23) + >>> LOCATION_IDS = [7086109, 7040481] + >>> OUTPUT_ROOT = Path(Path().home(), "temp") + >>> OUTPUT_DIR = Path(OUTPUT_ROOT, "nwm20_retrospective") + + Fetch and format the data, writing to the specified directory. + + >>> nwm_retro.nwm_retro_to_parquet( + >>> nwm_version=NWM_VERSION, + >>> variable_name=VARIABLE_NAME, + >>> start_date=START_DATE, + >>> end_date=END_DATE, + >>> location_ids=LOCATION_IDS, + >>> output_parquet_dir=OUTPUT_DIR + >>> ) + """ + + if nwm_version == SupportedNWMRetroVersionsEnum.nwm20: + s3_zarr_url = "s3://noaa-nwm-retro-v2-zarr-pds" + elif nwm_version == SupportedNWMRetroVersionsEnum.nwm21: + s3_zarr_url = "s3://noaa-nwm-retrospective-2-1-zarr-pds/chrtout.zarr/" + elif nwm_version == SupportedNWMRetroVersionsEnum.nwm30: + s3_zarr_url = ( + f"s3://noaa-nwm-retrospective-3-0-pds/{domain}/zarr/chrtout.zarr" + ) + else: + raise ValueError(f"unsupported NWM version {nwm_version}") + + start_date = datetime_to_date(pd.Timestamp(start_date)) + end_date = ( + datetime_to_date(pd.Timestamp(end_date)) + + timedelta(days=1) - + timedelta(minutes=1) + ) + + validate_start_end_date(nwm_version, start_date, end_date) + + output_dir = Path(output_parquet_dir) + if not output_dir.exists(): + output_dir.mkdir(parents=True) + + ds = xr.open_zarr( + fsspec.get_mapper(s3_zarr_url, anon=True), consolidated=True + ).sel(feature_id=location_ids, time=slice(start_date, end_date)) + + # Fetch all at once + if chunk_by is None: + + da = ds[variable_name] + df = da_to_df(nwm_version, da) + min_time = df.value_time.min().strftime("%Y%m%d%HZ") + max_time = df.value_time.max().strftime("%Y%m%d%HZ") + output_filepath = Path( + output_parquet_dir, f"{min_time}_{max_time}.parquet" + ) + write_parquet_file(output_filepath, overwrite_output, df) + return + + # Fetch data by site + if chunk_by == "location_id": + for location_id in location_ids: + + da = ds[variable_name].sel(feature_id=location_id) + df = da_to_df(nwm_version, da) + min_time = df.value_time.min().strftime("%Y%m%d%HZ") + max_time = df.value_time.max().strftime("%Y%m%d%HZ") + output_filepath = Path( + output_parquet_dir, + f"{location_id}_{min_time}_{max_time}.parquet" + ) + write_parquet_file(output_filepath, overwrite_output, df) + return + + # Group dataset by day, week, month, or year + if chunk_by == "day": + gps = ds.groupby("time.day") + + if chunk_by == "week": + # Calendar week: Monday to Sunday + gps = ds.groupby(ds.time.dt.isocalendar().week) + + if chunk_by == "month": + # Calendar month + gps = ds.groupby("time.month") + + if chunk_by == "year": + # Calendar year + gps = ds.groupby("time.year") + + # Process the data by selected chunk + for _, ds_i in gps: + df = da_to_df(nwm_version, ds_i[variable_name]) + output_filename = format_grouped_filename(ds_i) + output_filepath = Path( + output_parquet_dir, output_filename + ) + write_parquet_file(output_filepath, overwrite_output, df)
+ + + +# if __name__ == "__main__": +# # Examples + +# LOCATION_IDS = [7086109, 7040481] + +# nwm_retro_to_parquet( +# nwm_version="nwm20", +# variable_name="streamflow", +# start_date="2000-01-01", +# end_date="2000-01-02", +# location_ids=LOCATION_IDS, +# output_parquet_dir=Path(Path().home(), "temp", "nwm20_retrospective"), +# ) + +# nwm_retro_to_parquet( +# nwm_version="nwm20", +# variable_name="streamflow", +# start_date=datetime(2000, 1, 1), +# end_date=datetime(2000, 1, 2), +# location_ids=LOCATION_IDS, +# output_parquet_dir=Path(Path().home(), "temp", "nwm20_retrospective"), +# chunk_by="day", +# ) + +# nwm_retro_to_parquet( +# nwm_version="nwm20", +# variable_name="streamflow", +# start_date=datetime(2000, 1, 1), +# end_date=datetime(2000, 1, 2), +# location_ids=LOCATION_IDS, +# output_parquet_dir=Path(Path().home(), "temp", "nwm20_retrospective"), +# chunk_by="location_id", +# ) + +# nwm_retro_to_parquet( +# nwm_version="nwm21", +# variable_name="streamflow", +# start_date="2000-01-01", +# end_date="2000-01-02", +# location_ids=LOCATION_IDS, +# output_parquet_dir=Path(Path().home(), "temp", "nwm21_retrospective"), +# ) + +# nwm_retro_to_parquet( +# nwm_version="nwm21", +# variable_name="streamflow", +# start_date=datetime(2000, 1, 1), +# end_date=datetime(2000, 1, 2), +# location_ids=LOCATION_IDS, +# output_parquet_dir=Path(Path().home(), "temp", "nwm21_retrospective"), +# chunk_by="day", +# ) + +# nwm_retro_to_parquet( +# nwm_version="nwm21", +# variable_name="streamflow", +# start_date=datetime(2000, 1, 1), +# end_date=datetime(2000, 1, 2), +# location_ids=LOCATION_IDS, +# output_parquet_dir=Path(Path().home(), "temp", "nwm21_retrospective"), +# chunk_by="location_id", +# ) +# pass +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/loading/nwm/utils.html b/_modules/teehr/loading/nwm/utils.html new file mode 100644 index 00000000..b8221fb3 --- /dev/null +++ b/_modules/teehr/loading/nwm/utils.html @@ -0,0 +1,1120 @@ + + + + + + + + + + teehr.loading.nwm.utils — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.loading.nwm.utils

+"""Module defining common utilities for fetching and processing NWM data."""
+from pathlib import Path
+from typing import Union, Optional, Iterable, List, Dict
+from datetime import datetime
+from datetime import timedelta
+from dateutil.parser import parse
+import logging
+
+import dask
+import fsspec
+import ujson  # fast json
+from kerchunk.hdf import SingleHdf5ToZarr
+import pandas as pd
+import numpy as np
+import xarray as xr
+import geopandas as gpd
+import pyarrow as pa
+import pyarrow.parquet as pq
+
+from teehr.models.loading.utils import (
+    SupportedKerchunkMethod
+)
+from teehr.models.loading.utils import (
+    SupportedNWMOperationalVersionsEnum
+)
+from teehr.loading.nwm.const import (
+    NWM_BUCKET,
+    NWM_S3_JSON_PATH,
+    NWM30_START_DATE
+)
+
+logger = logging.getLogger(__name__)
+
+
+
+[docs] +def check_dates_against_nwm_version( + nwm_version: str, + start_date: Union[str, datetime], + ingest_days: int +): + """Make sure start/end dates work with specified NWM version.""" + if isinstance(start_date, str): + start_date = parse(start_date) + + if ( + (nwm_version == SupportedNWMOperationalVersionsEnum.nwm30) & + (start_date < NWM30_START_DATE) + ): + raise ValueError( + f"The specified start date ({start_date}) is before the NWM " + f"v3.0 release date ({NWM30_START_DATE})" + ) + + end_date = start_date + timedelta(days=ingest_days) + if ( + (nwm_version == SupportedNWMOperationalVersionsEnum.nwm22) & + (end_date > NWM30_START_DATE) + ): + raise ValueError( + f"The specified end date ({end_date}) is after the NWM " + f"v2.2 to v3.0 transition date ({NWM30_START_DATE})" + )
+ + + +
+[docs] +def generate_json_paths( + kerchunk_method: str, + gcs_component_paths: List[str], + json_dir: str, + ignore_missing_file: bool +) -> List[str]: + """Generate remote and/or local paths to Kerchunk reference json files + depending on the specified method. + + Parameters + ---------- + kerchunk_method : str + Specifies the preference in creating Kerchunk reference json files. + gcs_component_paths : List[str] + Paths to NWM netcdf files in GCS. + json_dir : str + Local directory for caching created json files. + ignore_missing_file : bool + Flag specifying whether or not to fail if a missing + NWM file is encountered. + + Returns + ------- + List[str] + List of filepaths to json files locally and/or in s3. + """ + + if kerchunk_method == SupportedKerchunkMethod.local: + # Create them manually first + json_paths = build_zarr_references(gcs_component_paths, + json_dir, + ignore_missing_file) + + elif kerchunk_method == SupportedKerchunkMethod.remote: + # Use whatever pre-builts exist, skipping the rest + fs = fsspec.filesystem("s3", anon=True) + results = [] + for gcs_path in gcs_component_paths: + results.append(check_for_prebuilt_json_paths(fs, gcs_path)) + json_paths = dask.compute(results)[0] + json_paths = [path for path in json_paths if path is not None] + + elif kerchunk_method == SupportedKerchunkMethod.auto: + # Use whatever pre-builts exist, and create the missing + # files, if any + fs = fsspec.filesystem("s3", anon=True) + results = [] + for gcs_path in gcs_component_paths: + results.append( + check_for_prebuilt_json_paths( + fs, gcs_path, return_gcs_path=True + ) + ) + s3_or_gcs_paths = dask.compute(results)[0] + + # Build any jsons that do not already exist in s3 + gcs_paths = [] + json_paths = [] + for path in s3_or_gcs_paths: + if path.split("://")[0] == "gcs": + gcs_paths.append(path) + else: + json_paths.append(path) + + if len(gcs_paths) > 0: + json_paths.extend( + build_zarr_references(gcs_paths, + json_dir, + ignore_missing_file) + ) + + return json_paths
+ + + +
+[docs] +def write_parquet_file( + filepath: Path, + overwrite_output: bool, + data: Union[pa.Table, pd.DataFrame] +): + """Write output timeseries parquet file with logic controlling + whether or not to overwrite an existing file. + + Parameters + ---------- + filepath : Path + Path to the output parquet file. + overwrite_output : bool + Flag controlling overwrite behavior. + data : Union[pa.Table, pd.DataFrame] + The output data as either a dataframe or pyarrow table. + """ + if not filepath.is_file(): + if isinstance(data, pa.Table): + pq.write_table(data, filepath) + else: + data.to_parquet(filepath) + elif filepath.is_file() and overwrite_output: + logger.info(f"Overwriting {filepath.name}") + if isinstance(data, pa.Table): + pq.write_table(data, filepath) + else: + data.to_parquet(filepath) + elif filepath.is_file() and not overwrite_output: + logger.info( + f"{filepath.name} already exists and overwrite_output=False;" + " skipping" + )
+ + + +
+[docs] +def load_gdf(filepath: Union[str, Path], **kwargs: str) -> gpd.GeoDataFrame: + """Load any supported geospatial file type into a gdf using GeoPandas.""" + try: + gdf = gpd.read_file(filepath, **kwargs) + return gdf + except Exception: + pass + try: + gdf = gpd.read_parquet(filepath, **kwargs) + return gdf + except Exception: + pass + try: + gdf = gpd.read_feather(filepath, **kwargs) + return gdf + except Exception: + raise Exception("Unsupported zone polygon file type")
+ + + +
+[docs] +def parquet_to_gdf(parquet_filepath: str) -> gpd.GeoDataFrame: + """Read parquet as GeoDataFrame.""" + gdf = gpd.read_parquet(parquet_filepath) + return gdf
+ + + +
+[docs] +def np_to_list(t): + """Convert numpy array to list.""" + return [a.tolist() for a in t]
+ + + +
+[docs] +def get_dataset( + filepath: str, ignore_missing_file: bool, **kwargs +) -> xr.Dataset: + """Retrieve a blob from the data service as xarray.Dataset. + + Parameters + ---------- + filepath : str + Path to the kerchunk json file. Can be local or remote. + ignore_missing_file : bool + Flag controlling whether to ignore missing files. + + Returns + ------- + xarray.Dataset + The data stored in the blob. + """ + try: + m = fsspec.filesystem( + "reference", fo=filepath, **kwargs + ).get_mapper() + except FileNotFoundError as e: + if not ignore_missing_file: + raise e + else: + return None + except ValueError: + raise ValueError(f"There was a problem reading {filepath}") + return xr.open_dataset(m, engine="zarr", consolidated=False)
+ + + +
+[docs] +def list_to_np(lst): + """Convert list to a tuple.""" + return tuple([np.array(a) for a in lst])
+ + + +@dask.delayed +def check_for_prebuilt_json_paths( + fs: fsspec.filesystem, gcs_path: str, return_gcs_path=False +) -> str: + """Check for existence of a pre-built kerchunk json in s3 based + on its GCS path. + + Parameters + ---------- + fs : fsspec.filesystem + S3-based filesystem. + gcs_path : str + Path to the netcdf file in GCS. + return_gcs_path : bool, optional + Flag to return GCS path of s3 is missing, by default False. + + Returns + ------- + str + Path to the json in s3 or netcdf file in GCS. + """ + s3_path = f"{NWM_S3_JSON_PATH}/{gcs_path.split('://')[1]}.json" + if fs.exists(s3_path): + return s3_path + else: + if return_gcs_path: + return gcs_path + + +@dask.delayed +def gen_json( + remote_path: str, + fs: fsspec.filesystem, + json_dir: Union[str, Path], + ignore_missing_file: bool, +) -> str: + """Helper function for creating single-file kerchunk reference JSONs. + + Parameters + ---------- + remote_path : str + Path to the file in the remote location (ie, GCS bucket). + fs : fsspec.filesystem + Fsspec filesystem mapped to GCS. + json_dir : str + Directory for saving zarr reference json files. + + Returns + ------- + str + Path to the local zarr reference json file. + """ + so = dict( + mode="rb", + anon=True, + default_fill_cache=False, + default_cache_type="first", # noqa + ) + try: + with fs.open(remote_path, **so) as infile: + p = remote_path.split("/") + date = p[3] + fname = p[5] + outf = str(Path(json_dir, f"{date}.{fname}.json")) + try: + h5chunks = SingleHdf5ToZarr(infile, + remote_path, + inline_threshold=300) + except OSError as err: + if not ignore_missing_file: + raise Exception(f"Corrupt file: {remote_path}") from err + else: + logger.warning( + ("A potentially corrupt file was encountered:") + (f"{remote_path}") + ) + return None + with open(outf, "wb") as f: + f.write(ujson.dumps(h5chunks.translate()).encode()) + except FileNotFoundError as e: + if not ignore_missing_file: + raise e + else: + logger.warning(f"A missing file was encountered: {remote_path}") + return None + return outf + + +
+[docs] +def build_zarr_references( + remote_paths: List[str], + json_dir: Union[str, Path], + ignore_missing_file: bool, +) -> list[str]: + """Build the single file zarr json reference files using kerchunk. + + Parameters + ---------- + remote_paths : List[str] + List of remote filepaths. + json_dir : str or Path + Local directory for caching json files. + + Returns + ------- + list[str] + List of paths to the zarr reference json files. + """ + json_dir_path = Path(json_dir) + if not json_dir_path.exists(): + json_dir_path.mkdir(parents=True) + + fs = fsspec.filesystem("gcs", anon=True) + + # Check to see if the jsons already exist locally + existing_jsons = [] + missing_paths = [] + for path in remote_paths: + p = path.split("/") + date = p[3] + fname = p[5] + local_path = Path(json_dir, f"{date}.{fname}.json") + if local_path.exists(): + existing_jsons.append(str(local_path)) + else: + missing_paths.append(path) + if len(missing_paths) == 0: + return sorted(existing_jsons) + + results = [] + for path in missing_paths: + results.append(gen_json(path, fs, json_dir, ignore_missing_file)) + json_paths = dask.compute(results)[0] + json_paths.extend(existing_jsons) + + if not any(json_paths): + raise FileNotFoundError( + "No NWM files for specified input configuration were found in GCS!" + ) + + json_paths = [path for path in json_paths if path is not None] + + return sorted(json_paths)
+ + + +
+[docs] +def construct_assim_paths( + gcs_dir: str, + configuration: str, + output_type: str, + dates: pd.DatetimeIndex, + t_minus: Iterable[int], + configuration_name_in_filepath: str, + cycle_z_hours: Iterable[int], + domain: str, + file_extension: str = "nc" +) -> list[str]: + """Construct paths to NWM point assimilation data based on specified + parameters. + + This function prioritizes value time over reference time so that only + files with value times falling within the specified date range are included + in the resulting file list. + + Parameters + ---------- + gcs_dir : str + Path to the NWM data on GCS. + configuration : str + Configuration type. + output_type : str + Output component of the configuration. + dates : pd.DatetimeIndex + Range of days to fetch data. + t_minus : Iterable[int] + Collection of lookback hours to include when fetching assimilation data. + configuration_name_in_filepath : str + Name of the assimilation configuration as represented in the GCS file. + Defined in const_nwm.py. + cycle_z_hours : Iterable[int] + The z-hour of the assimilation configuration per day. + Defined in const_nwm.py. + domain : str + Geographic region covered by the assimilation configuration. + Defined in const_nwm.py. + file_extension : str + File extension ("nc" or "nc.json" for remote kerchunk). + + Returns + ------- + list[str] + List of remote filepaths. + """ + component_paths = [] + + for dt in dates: + dt_str = dt.strftime("%Y%m%d") + + # Add the values starting from day 1, + # skipping value times in the previous day + if "hawaii" in configuration: + for cycle_hr in cycle_z_hours: + for tm in t_minus: + for tm2 in [0, 15, 30, 45]: + if (tm * 100 + tm2) > cycle_hr * 100: + continue + file_path = f"{gcs_dir}/nwm.{dt_str}/{configuration}/nwm.t{cycle_hr:02d}z.{configuration_name_in_filepath}.{output_type}.tm{tm:02d}{tm2:02d}.{domain}.{file_extension}" # noqa + component_paths.append(file_path) + else: + for cycle_hr in cycle_z_hours: + for tm in t_minus: + if tm > cycle_hr: + continue + file_path = f"{gcs_dir}/nwm.{dt_str}/{configuration}/nwm.t{cycle_hr:02d}z.{configuration_name_in_filepath}.{output_type}.tm{tm:02d}.{domain}.{file_extension}" # noqa + component_paths.append(file_path) + + # Now add the values from the day following the end day, + # whose value times that fall within the end day + if "extend" in configuration: + for tm in t_minus: + dt_add = dt + pd.Timedelta(cycle_hr + 24, unit="hours") + hr_add = dt_add.hour + if tm > hr_add: + dt_add_str = dt_add.strftime("%Y%m%d") + file_path = f"{gcs_dir}/nwm.{dt_add_str}/{configuration}/nwm.t{hr_add:02d}z.{configuration_name_in_filepath}.{output_type}.tm{tm:02d}.{domain}.{file_extension}" # noqa + component_paths.append(file_path) + + elif "hawaii" in configuration: + for cycle_hr2 in cycle_z_hours: + for tm in t_minus: + for tm2 in [0, 15, 30, 45]: + if cycle_hr2 > 0: + dt_add = dt + pd.Timedelta( + cycle_hr + cycle_hr2, unit="hours" + ) + hr_add = dt_add.hour + if (tm * 100 + tm2) > hr_add * 100: + dt_add_str = dt_add.strftime("%Y%m%d") + file_path = f"{gcs_dir}/nwm.{dt_add_str}/{configuration}/nwm.t{hr_add:02d}z.{configuration_name_in_filepath}.{output_type}.tm{tm:02d}{tm2:02d}.{domain}.{file_extension}" # noqa + component_paths.append(file_path) + else: + for cycle_hr2 in cycle_z_hours: + for tm in t_minus: + if cycle_hr2 > 0: + dt_add = dt + pd.Timedelta( + cycle_hr + cycle_hr2, unit="hours" + ) + hr_add = dt_add.hour + if tm > hr_add: + dt_add_str = dt_add.strftime("%Y%m%d") + file_path = f"{gcs_dir}/nwm.{dt_add_str}/{configuration}/nwm.t{hr_add:02d}z.{configuration_name_in_filepath}.{output_type}.tm{tm:02d}.{domain}.{file_extension}" # noqa + component_paths.append(file_path) + + return sorted(component_paths)
+ + + +
+[docs] +def build_remote_nwm_filelist( + configuration: str, + output_type: str, + start_dt: Union[str, datetime], + ingest_days: int, + analysis_config_dict: Dict, + t_minus_hours: Optional[Iterable[int]], + ignore_missing_file: Optional[bool], +) -> List[str]: + """Assemble a list of remote NWM files in GCS based on specified user + parameters. + + Parameters + ---------- + configuration : str + Configuration type. + output_type : str + Output component of the configuration. + start_dt : str “YYYY-MM-DD” or datetime + Date to begin data ingest. + ingest_days : int + Number of days to ingest data after start date. + t_minus_hours : Iterable[int] + Only necessary if assimilation data is requested. + Collection of lookback hours to include when fetching + assimilation data. + ignore_missing_file : bool + Flag specifying whether or not to fail if a missing + NWM file is encountered + True = skip and continue + False = fail. + + Returns + ------- + list + List of remote filepaths (strings). + """ + gcs_dir = f"gcs://{NWM_BUCKET}" + fs = fsspec.filesystem("gcs", anon=True) + dates = pd.date_range(start=start_dt, periods=ingest_days, freq="1d") + + if "assim" in configuration: + cycle_z_hours = analysis_config_dict[configuration]["cycle_z_hours"] + domain = analysis_config_dict[configuration]["domain"] + configuration_name_in_filepath = analysis_config_dict[configuration][ + "configuration_name_in_filepath" + ] + max_lookback = analysis_config_dict[configuration]["num_lookback_hrs"] + + if max(t_minus_hours) > max_lookback - 1: + raise ValueError( + f"The maximum specified t-minus hour exceeds the lookback " + f"period for this configuration: {configuration}; max t-minus: " # noqa + f"{max(t_minus_hours)} hrs; " + f"look-back period: {max_lookback} hrs" + ) + + component_paths = construct_assim_paths( + gcs_dir, + configuration, + output_type, + dates, + t_minus_hours, + configuration_name_in_filepath, + cycle_z_hours, + domain, + ) + + else: + component_paths = [] + + for dt in dates: + dt_str = dt.strftime("%Y%m%d") + file_path = ( + f"{gcs_dir}/nwm.{dt_str}/{configuration}/nwm.*.{output_type}*" + ) + result = fs.glob(file_path) + if (len(result) == 0) & (not ignore_missing_file): + raise FileNotFoundError(f"No NWM files found in {file_path}") + component_paths.extend(result) + component_paths = sorted([f"gcs://{path}" for path in component_paths]) + + return component_paths
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/loading/usgs/usgs.html b/_modules/teehr/loading/usgs/usgs.html new file mode 100644 index 00000000..d3fb50fb --- /dev/null +++ b/_modules/teehr/loading/usgs/usgs.html @@ -0,0 +1,867 @@ + + + + + + + + + + teehr.loading.usgs.usgs — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.loading.usgs.usgs

+"""Module for loading and processing USGS streamflow data."""
+import pandas as pd
+
+from typing import List, Union, Optional
+from pathlib import Path
+from datetime import datetime, timedelta
+from hydrotools.nwis_client.iv import IVDataService
+from teehr.models.loading.utils import ChunkByEnum
+from pydantic import validate_call, ConfigDict
+from teehr.loading.nwm.utils import write_parquet_file
+
+DATETIME_STR_FMT = "%Y-%m-%dT%H:%M:00+0000"
+
+
+
+[docs] +def _filter_to_hourly(df: pd.DataFrame) -> pd.DataFrame: + """Filter out data not reported on the hour.""" + df.set_index("value_time", inplace=True) + df2 = df[ + df.index.hour.isin(range(0, 24)) + & (df.index.minute == 0) + & (df.index.second == 0) + ] + df2.reset_index(level=0, allow_duplicates=True, inplace=True) + return df2
+ + + +
+[docs] +def _filter_no_data(df: pd.DataFrame, no_data_value=-999) -> pd.DataFrame: + """Filter out no data values.""" + + df2 = df[df["value"] != no_data_value] + return df2
+ + + +
+[docs] +def _convert_to_si_units(df: pd.DataFrame) -> pd.DataFrame: + """Convert streamflow values from english to metric.""" + + df["value"] = df["value"] * 0.3048**3 + df["measurement_unit"] = "m3/s" + return df
+ + + +
+[docs] +def _datetime_to_date(dt: datetime) -> datetime: + """Convert datetime to date only.""" + dt.replace( + hour=0, + minute=0, + second=0, + microsecond=0 + ) + return dt
+ + + +
+[docs] +def _format_df(df: pd.DataFrame) -> pd.DataFrame: + """Format HydroTools dataframe columns to TEEHR data model.""" + + df.rename(columns={"usgs_site_code": "location_id"}, inplace=True) + df["location_id"] = "usgs-" + df["location_id"].astype(str) + df["configuration"] = "usgs_gage_data" + df["reference_time"] = df["value_time"] + return df[[ + "location_id", + "reference_time", + "value_time", + "value", + "variable_name", + "measurement_unit", + "configuration" + ]]
+ + + +
+[docs] +def _fetch_usgs( + sites: List[str], + start_date: datetime, + end_date: datetime, + filter_to_hourly: bool = True, + filter_no_data: bool = True, + convert_to_si: bool = True +) -> pd.DataFrame: + """Fetch USGS gage data and format to TEEHR format.""" + + start_dt_str = start_date.strftime(DATETIME_STR_FMT) + end_dt_str = ( + end_date + - timedelta(minutes=1) + ).strftime(DATETIME_STR_FMT) + + # Retrieve data + service = IVDataService( + value_time_label="value_time", + enable_cache=False + ) + usgs_df = service.get( + sites=sites, + startDT=start_dt_str, + endDT=end_dt_str + ) + + if filter_to_hourly is True: + usgs_df = _filter_to_hourly(usgs_df) + if filter_no_data is True: + usgs_df = _filter_no_data(usgs_df) + if convert_to_si is True: + usgs_df = _convert_to_si_units(usgs_df) + usgs_df = _format_df(usgs_df) + + # Return the data + return usgs_df
+ + + +
+[docs] +def _format_output_filename(chunk_by: str, start_dt, end_dt) -> str: + """Format the output filename based on min and max + datetime in the dataset.""" + if chunk_by == "day": + return f"{start_dt.strftime('%Y-%m-%d')}.parquet" + else: + start = start_dt.strftime('%Y-%m-%d') + end = end_dt.strftime('%Y-%m-%d') + return f"{start}_{end}.parquet"
+ + + +
+[docs] +@validate_call(config=ConfigDict(arbitrary_types_allowed=True)) +def usgs_to_parquet( + sites: List[str], + start_date: Union[str, datetime, pd.Timestamp], + end_date: Union[str, datetime, pd.Timestamp], + output_parquet_dir: Union[str, Path], + chunk_by: Union[ChunkByEnum, None] = None, + filter_to_hourly: bool = True, + filter_no_data: bool = True, + convert_to_si: bool = True, + overwrite_output: Optional[bool] = False, +): + """Fetch USGS gage data and save as a Parquet file. + + Parameters + ---------- + sites : List[str] + List of USGS gages sites to fetch. + Must be string to preserve the leading 0. + start_date : datetime + Start time of data to fetch. + end_date : datetime + End time of data to fetch. Note, since startDt is inclusive for the + USGS service, we subtract 1 minute from this time so we don't get + overlap between consecutive calls. + output_parquet_dir : Union[str, Path] + Path of directory where parquet files will be saved. + chunk_by : Union[str, None], default = None + How to "chunk" the fetching and storing of the data. + Valid options = ["day", "site", None]. + filter_to_hourly : bool = True + Return only values that fall on the hour (i.e. drop 15 minute data). + filter_no_data : bool = True + Filter out -999 values. + convert_to_si : bool = True + Multiplies values by 0.3048**3 and sets `measurement_units` to `m3/s`. + overwrite_output : bool + Flag specifying whether or not to overwrite output files if they + already exist. True = overwrite; False = fail. + + Examples + -------- + Here we fetch five days worth of USGS hourly streamflow data, to two gages, + chunking by day. + + Import the module. + + >>> from teehr.loading.usgs.usgs import usgs_to_parquet + + Set the input variables. + + >>> SITES=["02449838", "02450825"] + >>> START_DATE=datetime(2023, 2, 20) + >>> END_DATE=datetime(2023, 2, 25) + >>> OUTPUT_PARQUET_DIR=Path(Path().home(), "temp", "usgs") + >>> CHUNK_BY="day", + >>> OVERWRITE_OUTPUT=True + + Fetch the data, writing to the specified output directory. + + >>> usgs_to_parquet( + >>> sites=SITES, + >>> start_date=START_DATE, + >>> end_date=END_DATE, + >>> output_parquet_dir=TEMP_DIR, + >>> chunk_by=CHUNK_BY, + >>> overwrite_output=OVERWRITE_OUTPUT + >>> ) + """ + + start_date = pd.Timestamp(start_date) + end_date = pd.Timestamp(end_date) + + # Check if output_parquet_dir is an existing dir + output_dir = Path(output_parquet_dir) + if not output_dir.exists(): + output_dir.mkdir(parents=True) + + # Fetch all at once + if chunk_by is None: + usgs_df = _fetch_usgs( + sites=sites, + start_date=start_date, + end_date=end_date, + filter_to_hourly=filter_to_hourly, + filter_no_data=filter_no_data, + convert_to_si=convert_to_si + ) + if len(usgs_df) > 0: + output_filepath = Path(output_parquet_dir, "usgs.parquet") + write_parquet_file( + filepath=output_filepath, + overwrite_output=overwrite_output, + data=usgs_df + ) + return + + if chunk_by == "location_id": + for site in sites: + usgs_df = _fetch_usgs( + sites=[site], + start_date=start_date, + end_date=end_date, + filter_to_hourly=filter_to_hourly, + filter_no_data=filter_no_data, + convert_to_si=convert_to_si + ) + if len(usgs_df) > 0: + output_filepath = Path( + output_parquet_dir, + f"{site}.parquet" + ) + write_parquet_file( + filepath=output_filepath, + overwrite_output=overwrite_output, + data=usgs_df + ) + return + + # TODO: Print warning if chunk_by is bigger than start and end dates? + + if chunk_by == "day": + date_intervals = pd.date_range(start_date, end_date, freq="D") + + if chunk_by == "week": + date_intervals = pd.date_range(start_date, end_date, freq="W") + + if chunk_by == "month": + date_intervals = pd.date_range(start_date, end_date, freq="M") + + if chunk_by == "year": + date_intervals = pd.date_range(start_date, end_date, freq="Y") + + # If the start date is not within the specified interval, + # it is not included, so add it here if it does not already exist. + date_intervals = date_intervals.union( + pd.DatetimeIndex([start_date]), sort=True + ) + + for i, dt_intvl in enumerate(date_intervals): + if i == 0: + start_dt = start_date + else: + start_dt = dt_intvl + + if i == len(date_intervals) - 1: + # Include data for the last day + end_dt = end_date + timedelta(hours=24) - timedelta(minutes=1) + else: + end_dt = date_intervals[i + 1] - timedelta(minutes=1) + + usgs_df = _fetch_usgs( + sites=sites, + start_date=start_dt, + end_date=end_dt, + filter_to_hourly=filter_to_hourly, + filter_no_data=filter_no_data, + convert_to_si=convert_to_si + ) + + if len(usgs_df) > 0: + + output_filename = _format_output_filename( + chunk_by, start_dt, end_dt + ) + + output_filepath = Path(output_parquet_dir, output_filename) + write_parquet_file( + filepath=output_filepath, + overwrite_output=overwrite_output, + data=usgs_df + )
+ + + +# if __name__ == "__main__": +# # Examples +# usgs_to_parquet( +# sites=[ +# "02449838", +# "02450825" +# ], +# start_date=datetime(2023, 2, 20), +# end_date=datetime(2023, 2, 25), +# output_parquet_dir=Path(Path().home(), "temp", "usgs"), +# chunk_by="location_id", +# overwrite_output=True +# ) + +# usgs_to_parquet( +# sites=[ +# "02449838", +# "02450825" +# ], +# start_date=datetime(2023, 2, 20), +# end_date=datetime(2023, 2, 25), +# output_parquet_dir=Path(Path().home(), "temp", "usgs"), +# chunk_by="day", +# overwrite_output=True +# ) + +# usgs_to_parquet( +# sites=[ +# "02449838", +# "02450825" +# ], +# start_date=datetime(2023, 2, 20), +# end_date=datetime(2023, 2, 25), +# output_parquet_dir=Path(Path().home(), "temp", "usgs"), +# overwrite_output=True +# ) +# pass +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/models/loading/nwm22_grid.html b/_modules/teehr/models/loading/nwm22_grid.html new file mode 100644 index 00000000..07a44f75 --- /dev/null +++ b/_modules/teehr/models/loading/nwm22_grid.html @@ -0,0 +1,827 @@ + + + + + + + + + + teehr.models.loading.nwm22_grid — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.models.loading.nwm22_grid

+"""Module describing NWM v2.2 grid configuration variables."""
+from enum import Enum
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+# GRID DATA VARIABLES ENUMS
+
+[docs] +class LandAssimVariablesEnum(str, Enum): + """LandAssimVariablesEnum.""" + ACCET = "ACCET" + ACSNOM = "ACSNOM" + EDIR = "EDIR" + FSNO = "FSNO" + ISNOW = "ISNOW" + QRAIN = "QRAIN" + QSNOW = "QSNOW" + SNEQV = "SNEQV" + SNLIQ = "SNLIQ" + SNOWH = "SNOWH" + SNOWT_AVG = "SNOWT_AVG" + SOILICE = "SOILICE" + SOILSAT_TOP = "SOILSAT_TOP" + SOIL_M = "SOIL_M" + SOIL_T = "SOIL_T"
+ + + +
+[docs] +class LandShortVariablesEnum(str, Enum): + """LandShortVariablesEnum.""" + ACCET = "ACCET" + SNOWT_AVG = "SNOWT_AVG" + SOILSAT_TOP = "SOILSAT_TOP" + FSNO = "FSNO" + SNOWH = "SNOWH" + SNEQV = "SNEQV"
+ + + +
+[docs] +class LandMediumVariablesEnum(str, Enum): + """LandMediumVariablesEnum.""" + FSA = "FSA" + FIRA = "FIRA" + GRDFLX = "GRDFLX" + HFX = "HFX" + LH = "LH" + UGDRNOFF = "UGDRNOFF" + ACCECAN = "ACCECAN" + ACCEDIR = "ACCEDIR" + ACCETRAN = "ACCETRAN" + TRAD = "TRAD" + SNLIQ = "SNLIQ" + SOIL_T = "SOIL_T" + SOIL_M = "SOIL_M" + SNOWH = "SNOWH" + SNEQV = "SNEQV" + ISNOW = "ISNOW" + FSNO = "FSNO" + ACSNOM = "ACSNOM" + ACCET = "ACCET" + CANWAT = "CANWAT" + SOILICE = "SOILICE" + SOILSAT_TOP = "SOILSAT_TOP" + SNOWT_AVG = "SNOWT_AVG"
+ + + +
+[docs] +class LandLongVariablesEnum(str, Enum): + """LandLongVariablesEnum.""" + UGDRNOFF = "UGDRNOFF" + SFCRNOFF = "SFCRNOFF" + SNEQV = "SNEQV" + ACSNOM = "ACSNOM" + ACCET = "ACCET" + CANWAT = "CANWAT" + SOILSAT_TOP = "SOILSAT_TOP" + SOILSAT = "SOILSAT"
+ + + +
+[docs] +class ForcingVariablesEnum(str, Enum): + """ForcingVariablesEnum.""" + U2D = "U2D" + V2D = "V2D" + T2D = "T2D" + Q2D = "Q2D" + LWDOWN = "LWDOWN" + SWDOWN = "SWDOWN" + RAINRATE = "RAINRATE" + PSFC = "PSFC"
+ + + +# OUTPUT TYPE ENUMS +
+[docs] +class ShortAndAnalysisOutputEnum(str, Enum): + """ShortAndAnalysisOutputEnum.""" + land = "land"
+ + + +
+[docs] +class MediumOutputEnum(str, Enum): + """MediumOutputEnum.""" + land_1 = "land_1" + land_2 = "land_2" + land_3 = "land_3" + land_4 = "land_4" + land_5 = "land_5" + land_6 = "land_6" + land_7 = "land_7"
+ + + +
+[docs] +class LongOutputEnum(str, Enum): + """LongOutputEnum.""" + land_1 = "land_1" + land_2 = "land_2" + land_3 = "land_3" + land_4 = "land_4"
+ + + +
+[docs] +class ForcingOutputEnum(str, Enum): + """ForcingOutputEnum.""" + forcing = "forcing"
+ + + +# OUTPUT TYPE MODELS +
+[docs] +class Analysis(BaseModel): + """Analysis.""" + output_type: ShortAndAnalysisOutputEnum + land: Optional[LandAssimVariablesEnum] = None
+ + + +
+[docs] +class ShortRange(BaseModel): + """ShortRange.""" + output_type: ShortAndAnalysisOutputEnum + land: Optional[LandShortVariablesEnum] = None
+ + + +
+[docs] +class MediumRange(BaseModel): + """MediumRange.""" + output_type: MediumOutputEnum + land_1: Optional[LandMediumVariablesEnum] = None + land_2: Optional[LandMediumVariablesEnum] = None + land_3: Optional[LandMediumVariablesEnum] = None + land_4: Optional[LandMediumVariablesEnum] = None + land_5: Optional[LandMediumVariablesEnum] = None + land_6: Optional[LandMediumVariablesEnum] = None + land_7: Optional[LandMediumVariablesEnum] = None
+ + + +
+[docs] +class LongRange(BaseModel): + """LongRange.""" + output_type: LongOutputEnum + land_1: Optional[LandLongVariablesEnum] = None + land_2: Optional[LandLongVariablesEnum] = None + land_3: Optional[LandLongVariablesEnum] = None + land_4: Optional[LandLongVariablesEnum] = None
+ + + +
+[docs] +class Forcing(BaseModel): + """Forcing.""" + output_type: ForcingOutputEnum + forcing: Optional[ForcingVariablesEnum] = None
+ + + +# CONFIGURATIONS ENUM +
+[docs] +class ConfigurationsEnum(str, Enum): + """ConfigurationsEnum.""" + analysis_assim = "analysis_assim" + analysis_assim_no_da = "analysis_assim_no_da" + analysis_assim_extend = "analysis_assim_extend" + analysis_assim_extend_no_da = "analysis_assim_extend_no_da" + analysis_assim_long = "analysis_assim_long" + analysis_assim_long_no_da = "analysis_assim_long_no_da" + analysis_assim_hawaii = "analysis_assim_hawaii" + analysis_assim_hawaii_no_da = "analysis_assim_hawaii_no_da" + analysis_assim_puertorico = "analysis_assim_puertorico" + analysis_assim_puertorico_no_da = "analysis_assim_puertorico_no_da" + short_range = "short_range" + short_range_hawaii = "short_range_hawaii" + short_range_puertorico = "short_range_puertorico" + short_range_hawaii_no_da = "short_range_hawaii_no_da" + short_range_puertorico_no_da = "short_range_puertorico_no_da" + medium_range_mem1 = "medium_range_mem1" + medium_range_mem2 = "medium_range_mem2" + medium_range_mem3 = "medium_range_mem3" + medium_range_mem4 = "medium_range_mem4" + medium_range_mem5 = "medium_range_mem5" + medium_range_mem6 = "medium_range_mem6" + medium_range_mem7 = "medium_range_mem7" + long_range_mem1 = "long_range_mem1" + long_range_mem2 = "long_range_mem2" + long_range_mem3 = "long_range_mem3" + long_range_mem4 = "long_range_mem4" + forcing_medium_range = "forcing_medium_range" + forcing_short_range = "forcing_short_range" + forcing_short_range_hawaii = "forcing_short_range_hawaii" + forcing_short_range_puertorico = "forcing_short_range_puertorico" + forcing_analysis_assim = "forcing_analysis_assim" + forcing_analysis_assim_extend = "forcing_analysis_assim_extend" + forcing_analysis_assim_hawaii = "forcing_analysis_assim_hawaii" + forcing_analysis_assim_puertorico = "forcing_analysis_assim_puertorico"
+ + + +# CONFIGURATION MODEL +
+[docs] +class GridConfigurationModel(BaseModel): + """NWM v2.2 GridConfigurationModel.""" + configuration: ConfigurationsEnum + analysis_assim: Optional[Analysis] = None + analysis_assim_no_da: Optional[Analysis] = None + analysis_assim_extend: Optional[Analysis] = None + analysis_assim_extend_no_da: Optional[Analysis] = None + analysis_assim_long: Optional[Analysis] = None + analysis_assim_long_no_da: Optional[Analysis] = None + analysis_assim_hawaii: Optional[Analysis] = None + analysis_assim_hawaii_no_da: Optional[Analysis] = None + analysis_assim_puertorico: Optional[Analysis] = None + analysis_assim_puertorico_no_da: Optional[Analysis] = None + short_range: Optional[ShortRange] = None + short_range_hawaii: Optional[ShortRange] = None + short_range_puertorico: Optional[ShortRange] = None + short_range_hawaii_no_da: Optional[ShortRange] = None + short_range_puertorico_no_da: Optional[ShortRange] = None + medium_range_mem1: Optional[MediumRange] = None + medium_range_mem2: Optional[MediumRange] = None + medium_range_mem3: Optional[MediumRange] = None + medium_range_mem4: Optional[MediumRange] = None + medium_range_mem5: Optional[MediumRange] = None + medium_range_mem6: Optional[MediumRange] = None + medium_range_mem7: Optional[MediumRange] = None + medium_range_no_da: Optional[MediumRange] = None + long_range_mem1: Optional[LongRange] = None + long_range_mem2: Optional[LongRange] = None + long_range_mem3: Optional[LongRange] = None + long_range_mem4: Optional[LongRange] = None + forcing_medium_range: Optional[Forcing] = None + forcing_short_range: Optional[Forcing] = None + forcing_short_range_hawaii: Optional[Forcing] = None + forcing_short_range_puertorico: Optional[Forcing] = None + forcing_analysis_assim: Optional[Forcing] = None + forcing_analysis_assim_extend: Optional[Forcing] = None + forcing_analysis_assim_hawaii: Optional[Forcing] = None + forcing_analysis_assim_puertorico: Optional[Forcing] = None
+ + + +if __name__ == "__main__": + # So for example: + configuration = "forcing_medium_range" + output_type = "forcing" + variable_name = "RAINRATE" + + # Assemble input parameters + vars = { + "configuration": configuration, + configuration: { + "output_type": output_type, + output_type: variable_name, + }, + } + + # import sys + # Check input parameters + # try: + cm = GridConfigurationModel.model_validate(vars) + # except ValidationError as e: + # print(e.errors()[0]["msg"]) + # sys.exit() + + config = cm.configuration.name + forecast_obj = getattr(cm, config) + out_type = forecast_obj.output_type.name + var_name = getattr(forecast_obj, out_type).name + + pass +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/models/loading/nwm22_point.html b/_modules/teehr/models/loading/nwm22_point.html new file mode 100644 index 00000000..c611c1b6 --- /dev/null +++ b/_modules/teehr/models/loading/nwm22_point.html @@ -0,0 +1,944 @@ + + + + + + + + + + teehr.models.loading.nwm22_point — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.models.loading.nwm22_point

+"""Module describing NWM v2.2 point data configuration variables."""
+from enum import Enum
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+# VARIABLE ENUMS: Potential variable names for each output_type
+
+[docs] +class ChannelRtVariableEnum(str, Enum): + """ChannelRtVariableEnum.""" + nudge = "nudge" + qBtmVertRunoff = "qBtmVertRunoff" + qBucket = "qBucket" + qSfcLatRunoff = "qSfcLatRunoff" + streamflow = "streamflow" + velocity = "velocity"
+ + + +
+[docs] +class ChannelRtNoDAVariableEnum(str, Enum): + """ChannelRtNoDAVariableEnum.""" + nudge = "nudge" + qBucket = "qBucket" + qSfcLatRunoff = "qSfcLatRunoff" + streamflow = "streamflow" + velocity = "velocity"
+ + + +
+[docs] +class ChannelRtLongVariableEnum(str, Enum): + """ChannelRtLongVariableEnum.""" + nudge = "nudge" + streamflow = "streamflow" + velocity = "velocity"
+ + + +
+[docs] +class TerrainRtVariableEnum(str, Enum): + """TerrainRtVariableEnum.""" + sfcheadsubrt = "sfcheadsubrt" + zwattablrt = "zwattablrt"
+ + + +
+[docs] +class ReservoirVariableEnum(str, Enum): + """ReservoirVariableEnum.""" + inflow = "inflow" + outflow = "outflow" + reservoir_assimiated_value = "reservoir_assimiated_value" + water_sfc_elev = "water_sfc_elev"
+ + + +# OUTPUT ENUMS: Potential output names for each configuration +
+[docs] +class ShortAndAnalysisOutputEnum(str, Enum): + """ShortAndAnalysisOutputEnum.""" + channel_rt = "channel_rt" + terrain_rt = "terrain_rt" + reservoir = "reservoir"
+ + + +
+[docs] +class Medium1OutputEnum(str, Enum): + """Medium1OutputEnum.""" + channel_rt_1 = "channel_rt_1" + terrain_rt_1 = "terrain_rt_1" + reservoir_1 = "reservoir_1"
+ + + +
+[docs] +class Medium2OutputEnum(str, Enum): + """Medium2OutputEnum.""" + channel_rt_2 = "channel_rt_2" + terrain_rt_2 = "terrain_rt_2" + reservoir_2 = "reservoir_2"
+ + + +
+[docs] +class Medium3OutputEnum(str, Enum): + """Medium3OutputEnum.""" + channel_rt_3 = "channel_rt_3" + terrain_rt_3 = "terrain_rt_3" + reservoir_3 = "reservoir_3"
+ + + +
+[docs] +class Medium4OutputEnum(str, Enum): + """Medium4OutputEnum.""" + channel_rt_4 = "channel_rt_4" + terrain_rt_4 = "terrain_rt_4" + reservoir_4 = "reservoir_4"
+ + + +
+[docs] +class Medium5OutputEnum(str, Enum): + """Medium5OutputEnum.""" + channel_rt_5 = "channel_rt_5" + terrain_rt_5 = "terrain_rt_5" + reservoir_5 = "reservoir_5"
+ + + +
+[docs] +class Medium6OutputEnum(str, Enum): + """Medium6OutputEnum.""" + channel_rt_6 = "channel_rt_6" + terrain_rt_6 = "terrain_rt_6" + reservoir_6 = "reservoir_6"
+ + + +
+[docs] +class Medium7OutputEnum(str, Enum): + """Medium7OutputEnum.""" + channel_rt_7 = "channel_rt_7" + terrain_rt_7 = "terrain_rt_7" + reservoir_7 = "reservoir_7"
+ + + +
+[docs] +class MediumNoDAEnum(str, Enum): + """MediumNoDAEnum.""" + channel_rt = "channel_rt"
+ + + +
+[docs] +class Long1OutputEnum(str, Enum): + """Long1OutputEnum.""" + channel_rt_1 = "channel_rt_1" + reservoir_1 = "reservoir_1"
+ + + +
+[docs] +class Long2OutputEnum(str, Enum): + """Long2OutputEnum.""" + channel_rt_2 = "channel_rt_2" + reservoir_2 = "reservoir_2"
+ + + +
+[docs] +class Long3OutputEnum(str, Enum): + """Long3OutputEnum.""" + channel_rt_3 = "channel_rt_3" + reservoir_3 = "reservoir_3"
+ + + +
+[docs] +class Long4OutputEnum(str, Enum): + """Long4OutputEnum.""" + channel_rt_4 = "channel_rt_4" + reservoir_4 = "reservoir_4"
+ + + +# POINT OUTPUT TYPE MODELS (needed for each configuration enum) +
+[docs] +class ShortAndAnalysis(BaseModel): + """ShortAndAnalysis.""" + output_type: ShortAndAnalysisOutputEnum + channel_rt: Optional[ChannelRtVariableEnum] = None + terrain_rt: Optional[TerrainRtVariableEnum] = None + reservoir: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class ShortAndAnalysisNoDA(BaseModel): + """ShortAndAnalysisNoDA.""" + output_type: ShortAndAnalysisOutputEnum + channel_rt: Optional[ChannelRtNoDAVariableEnum] = None + terrain_rt: Optional[TerrainRtVariableEnum] = None + reservoir: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class MediumRangeMem1(BaseModel): + """MediumRangeMem1.""" + output_type: Medium1OutputEnum + channel_rt_1: Optional[ChannelRtVariableEnum] = None + terrain_rt_1: Optional[TerrainRtVariableEnum] = None + reservoir_1: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class MediumRangeMem2(BaseModel): + """MediumRangeMem2.""" + output_type: Medium2OutputEnum + channel_rt_2: Optional[ChannelRtVariableEnum] = None + terrain_rt_2: Optional[TerrainRtVariableEnum] = None + reservoir_2: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class MediumRangeMem3(BaseModel): + """MediumRangeMem3.""" + output_type: Medium3OutputEnum + channel_rt_3: Optional[ChannelRtVariableEnum] = None + terrain_rt_3: Optional[TerrainRtVariableEnum] = None + reservoir_3: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class MediumRangeMem4(BaseModel): + """MediumRangeMem4.""" + output_type: Medium4OutputEnum + channel_rt_4: Optional[ChannelRtVariableEnum] = None + terrain_rt_4: Optional[TerrainRtVariableEnum] = None + reservoir_4: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class MediumRangeMem5(BaseModel): + """MediumRangeMem5.""" + output_type: Medium5OutputEnum + channel_rt_5: Optional[ChannelRtVariableEnum] = None + terrain_rt_5: Optional[TerrainRtVariableEnum] = None + reservoir_5: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class MediumRangeMem6(BaseModel): + """MediumRangeMem6.""" + output_type: Medium6OutputEnum + channel_rt_6: Optional[ChannelRtVariableEnum] = None + terrain_rt_6: Optional[TerrainRtVariableEnum] = None + reservoir_6: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class MediumRangeMem7(BaseModel): + """MediumRangeMem7.""" + output_type: Medium7OutputEnum + channel_rt_7: Optional[ChannelRtVariableEnum] = None + terrain_rt_7: Optional[TerrainRtVariableEnum] = None + reservoir_7: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class MediumRangeNoDA(BaseModel): + """MediumRangeNoDA.""" + output_type: MediumNoDAEnum + channel_rt: Optional[ChannelRtNoDAVariableEnum] = None
+ + + +
+[docs] +class LongRangeMem1(BaseModel): + """LongRangeMem1.""" + output_type: Long1OutputEnum + channel_rt_1: Optional[ChannelRtLongVariableEnum] = None + reservoir_1: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class LongRangeMem2(BaseModel): + """LongRangeMem2.""" + output_type: Long2OutputEnum + channel_rt_2: Optional[ChannelRtLongVariableEnum] = None + reservoir_2: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class LongRangeMem3(BaseModel): + """LongRangeMem3.""" + output_type: Long3OutputEnum + channel_rt_3: Optional[ChannelRtLongVariableEnum] = None + reservoir_3: Optional[ReservoirVariableEnum] = None
+ + + +
+[docs] +class LongRangeMem4(BaseModel): + """LongRangeMem4.""" + output_type: Long4OutputEnum + channel_rt_4: Optional[ChannelRtLongVariableEnum] = None + reservoir_4: Optional[ReservoirVariableEnum] = None
+ + + +# POINT CONFIGURATION ENUM: Potential configuration names +
+[docs] +class ConfigurationsEnum(str, Enum): + """ConfigurationsEnum.""" + analysis_assim = "analysis_assim" + analysis_assim_no_da = "analysis_assim_no_da" + analysis_assim_extend = "analysis_assim_extend" + analysis_assim_extend_no_da = "analysis_assim_extend_no_da" + analysis_assim_long = "analysis_assim_long" + analysis_assim_long_no_da = "analysis_assim_long_no_da" + analysis_assim_hawaii = "analysis_assim_hawaii" + analysis_assim_hawaii_no_da = "analysis_assim_hawaii_no_da" + analysis_assim_puertorico = "analysis_assim_puertorico" + analysis_assim_puertorico_no_da = "analysis_assim_puertorico_no_da" + short_range = "short_range" + short_range_hawaii = "short_range_hawaii" + short_range_puertorico = "short_range_puertorico" + short_range_hawaii_no_da = "short_range_hawaii_no_da" + short_range_puertorico_no_da = "short_range_puertorico_no_da" + medium_range_mem1 = "medium_range_mem1" + medium_range_mem2 = "medium_range_mem2" + medium_range_mem3 = "medium_range_mem3" + medium_range_mem4 = "medium_range_mem4" + medium_range_mem5 = "medium_range_mem5" + medium_range_mem6 = "medium_range_mem6" + medium_range_mem7 = "medium_range_mem7" + medium_range_no_da = "medium_range_no_da" + long_range_mem1 = "long_range_mem1" + long_range_mem2 = "long_range_mem2" + long_range_mem3 = "long_range_mem3" + long_range_mem4 = "long_range_mem4"
+ + + +# POINT CONFIGURATION MODEL +
+[docs] +class PointConfigurationModel(BaseModel): + """NWM v2.2 PointConfigurationModel.""" + configuration: ConfigurationsEnum = None + analysis_assim: Optional[ShortAndAnalysis] = None + analysis_assim_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_extend: Optional[ShortAndAnalysis] = None + analysis_assim_extend_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_long: Optional[ShortAndAnalysis] = None + analysis_assim_long_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_hawaii: Optional[ShortAndAnalysis] = None + analysis_assim_hawaii_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_puertorico: Optional[ShortAndAnalysis] = None + analysis_assim_puertorico_no_da: Optional[ShortAndAnalysisNoDA] = None + short_range: Optional[ShortAndAnalysis] = None + short_range_hawaii: Optional[ShortAndAnalysis] = None + short_range_puertorico: Optional[ShortAndAnalysis] = None + short_range_hawaii_no_da: Optional[ShortAndAnalysisNoDA] = None + short_range_puertorico_no_da: Optional[ShortAndAnalysisNoDA] = None + medium_range_mem1: Optional[MediumRangeMem1] = None + medium_range_mem2: Optional[MediumRangeMem2] = None + medium_range_mem3: Optional[MediumRangeMem3] = None + medium_range_mem4: Optional[MediumRangeMem4] = None + medium_range_mem5: Optional[MediumRangeMem5] = None + medium_range_mem6: Optional[MediumRangeMem6] = None + medium_range_mem7: Optional[MediumRangeMem7] = None + medium_range_no_da: Optional[MediumRangeNoDA] = None + long_range_mem1: Optional[LongRangeMem1] = None + long_range_mem2: Optional[LongRangeMem2] = None + long_range_mem3: Optional[LongRangeMem3] = None + long_range_mem4: Optional[LongRangeMem4] = None
+ + + +if __name__ == "__main__": + # So for example: + configuration = "medium_range_mem1" + output_type = "channel_rt_1" + variable_name = "streamflow" + + # Assemble input parameters + vars = { + "configuration": configuration, + configuration: { + "output_type": output_type, + output_type: variable_name, + }, + } + + # Check input parameters + cm = PointConfigurationModel.model_validate(vars) + + config = cm.configuration.name + forecast_obj = getattr(cm, config) + out_type = forecast_obj.output_type.name + var_name = getattr(forecast_obj, out_type).name + + pass +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/models/loading/nwm30_grid.html b/_modules/teehr/models/loading/nwm30_grid.html new file mode 100644 index 00000000..15dde740 --- /dev/null +++ b/_modules/teehr/models/loading/nwm30_grid.html @@ -0,0 +1,653 @@ + + + + + + + + + + teehr.models.loading.nwm30_grid — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.models.loading.nwm30_grid

+"""Module describing NWM v3.0 grid configuration variables."""
+from enum import Enum
+from typing import Optional
+
+from pydantic import BaseModel
+
+from teehr.models.loading.nwm22_grid import (
+    Analysis,
+    ShortRange,
+    MediumRange,
+    LongRange,
+    Forcing,
+)
+
+
+# CONFIGURATIONS ENUM: All possible configuration
+
+[docs] +class ConfigurationsEnum(str, Enum): + """ConfigurationsEnum.""" + analysis_assim = "analysis_assim" + analysis_assim_no_da = "analysis_assim_no_da" + analysis_assim_extend = "analysis_assim_extend" + analysis_assim_extend_no_da = "analysis_assim_extend_no_da" + analysis_assim_long = "analysis_assim_long" + analysis_assim_long_no_da = "analysis_assim_long_no_da" + analysis_assim_hawaii = "analysis_assim_hawaii" + analysis_assim_hawaii_no_da = "analysis_assim_hawaii_no_da" + analysis_assim_puertorico = "analysis_assim_puertorico" + analysis_assim_puertorico_no_da = "analysis_assim_puertorico_no_da" + short_range = "short_range" + short_range_hawaii = "short_range_hawaii" + short_range_puertorico = "short_range_puertorico" + short_range_hawaii_no_da = "short_range_hawaii_no_da" + short_range_puertorico_no_da = "short_range_puertorico_no_da" + medium_range_mem1 = "medium_range_mem1" + medium_range_mem2 = "medium_range_mem2" + medium_range_mem3 = "medium_range_mem3" + medium_range_mem4 = "medium_range_mem4" + medium_range_mem5 = "medium_range_mem5" + medium_range_mem6 = "medium_range_mem6" + medium_range_mem7 = "medium_range_mem7" + long_range_mem1 = "long_range_mem1" + long_range_mem2 = "long_range_mem2" + long_range_mem3 = "long_range_mem3" + long_range_mem4 = "long_range_mem4" + forcing_medium_range = "forcing_medium_range" + forcing_medium_range_blend = "forcing_medium_range_blend" + forcing_medium_range_alaska = "forcing_medium_range_alaska" + forcing_medium_range_blend_alaska = "forcing_medium_range_blend_alaska" + forcing_short_range = "forcing_short_range" + forcing_short_range_hawaii = "forcing_short_range_hawaii" + forcing_short_range_puertorico = "forcing_short_range_puertorico" + forcing_short_range_alaska = "forcing_short_range_alaska" + forcing_analysis_assim = "forcing_analysis_assim" + forcing_analysis_assim_extend = "forcing_analysis_assim_extend" + forcing_analysis_assim_hawaii = "forcing_analysis_assim_hawaii" + forcing_analysis_assim_puertorico = "forcing_analysis_assim_puertorico" + forcing_analysis_assim_alaska = "forcing_analysis_assim_alaska"
+ + + +# CONFIGURATION MODEL +
+[docs] +class GridConfigurationModel(BaseModel): + """NWM v3.0 GridConfigurationModel.""" + configuration: ConfigurationsEnum + analysis_assim: Optional[Analysis] = None + analysis_assim_no_da: Optional[Analysis] = None + analysis_assim_extend: Optional[Analysis] = None + analysis_assim_extend_no_da: Optional[Analysis] = None + analysis_assim_long: Optional[Analysis] = None + analysis_assim_long_no_da: Optional[Analysis] = None + analysis_assim_hawaii: Optional[Analysis] = None + analysis_assim_hawaii_no_da: Optional[Analysis] = None + analysis_assim_puertorico: Optional[Analysis] = None + analysis_assim_puertorico_no_da: Optional[Analysis] = None + short_range: Optional[ShortRange] = None + short_range_hawaii: Optional[ShortRange] = None + short_range_puertorico: Optional[ShortRange] = None + short_range_hawaii_no_da: Optional[ShortRange] = None + short_range_puertorico_no_da: Optional[ShortRange] = None + medium_range_mem1: Optional[MediumRange] = None + medium_range_mem2: Optional[MediumRange] = None + medium_range_mem3: Optional[MediumRange] = None + medium_range_mem4: Optional[MediumRange] = None + medium_range_mem5: Optional[MediumRange] = None + medium_range_mem6: Optional[MediumRange] = None + medium_range_mem7: Optional[MediumRange] = None + medium_range_no_da: Optional[MediumRange] = None + long_range_mem1: Optional[LongRange] = None + long_range_mem2: Optional[LongRange] = None + long_range_mem3: Optional[LongRange] = None + long_range_mem4: Optional[LongRange] = None + forcing_medium_range: Optional[Forcing] = None + forcing_medium_range_blend: Optional[Forcing] = None + forcing_medium_range_alaska: Optional[Forcing] = None + forcing_medium_range_blend_alaska: Optional[Forcing] = None + forcing_short_range: Optional[Forcing] = None + forcing_short_range_hawaii: Optional[Forcing] = None + forcing_short_range_puertorico: Optional[Forcing] = None + forcing_short_range_alaska: Optional[Forcing] = None + forcing_analysis_assim: Optional[Forcing] = None + forcing_analysis_assim_extend: Optional[Forcing] = None + forcing_analysis_assim_hawaii: Optional[Forcing] = None + forcing_analysis_assim_puertorico: Optional[Forcing] = None + forcing_analysis_assim_alaska: Optional[Forcing] = None
+ + + +if __name__ == "__main__": + # So for example: + configuration = "forcing_medium_range_blend_alaska" + output_type = "forcing" + variable_name = "RAINRATE" + + # Assemble input parameters + vars = { + "configuration": configuration, + configuration: { + "output_type": output_type, + output_type: variable_name, + }, + } + + # import sys + # Check input parameters + # try: + cm = GridConfigurationModel.model_validate(vars) + # except ValidationError as e: + # print(e.errors()[0]["msg"]) + # sys.exit() + + config = cm.configuration.name + forecast_obj = getattr(cm, config) + out_type = forecast_obj.output_type.name + # out_type = getattr(cm, config).output_type.name + var_name = getattr(forecast_obj, out_type).name + + pass +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/models/loading/nwm30_point.html b/_modules/teehr/models/loading/nwm30_point.html new file mode 100644 index 00000000..6e57b95e --- /dev/null +++ b/_modules/teehr/models/loading/nwm30_point.html @@ -0,0 +1,659 @@ + + + + + + + + + + teehr.models.loading.nwm30_point — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.models.loading.nwm30_point

+"""Module describing NWM v3.0 point configuration variables."""
+from enum import Enum
+from typing import Optional
+
+from pydantic import BaseModel
+
+from teehr.models.loading.nwm22_point import (
+    ShortAndAnalysis,
+    ShortAndAnalysisNoDA,
+    MediumRangeMem1,
+    MediumRangeMem2,
+    MediumRangeMem3,
+    MediumRangeMem4,
+    MediumRangeMem5,
+    MediumRangeMem6,
+    MediumRangeMem7,
+    MediumRangeNoDA,
+    LongRangeMem1,
+    LongRangeMem2,
+    LongRangeMem3,
+    LongRangeMem4
+)
+
+
+# POINT CONFIGURATION ENUM: Potential configuration names
+
+[docs] +class ConfigurationsEnum(str, Enum): + """ConfigurationsEnum.""" + analysis_assim = "analysis_assim" + analysis_assim_no_da = "analysis_assim_no_da" + analysis_assim_extend = "analysis_assim_extend" + analysis_assim_extend_no_da = "analysis_assim_extend_no_da" + analysis_assim_long = "analysis_assim_long" + analysis_assim_long_no_da = "analysis_assim_long_no_da" + analysis_assim_hawaii = "analysis_assim_hawaii" + analysis_assim_hawaii_no_da = "analysis_assim_hawaii_no_da" + analysis_assim_puertorico = "analysis_assim_puertorico" + analysis_assim_puertorico_no_da = "analysis_assim_puertorico_no_da" + analysis_assim_alaska = "analysis_assim_alaska" + analysis_assim_alaska_no_da = "analysis_assim_alaska_no_da" + analysis_assim_extend_alaska = "analysis_assim_extend_alaska" + analysis_assim_extend_alaska_no_da = "analysis_assim_extend_alaska_no_da" + short_range = "short_range" + short_range_hawaii = "short_range_hawaii" + short_range_puertorico = "short_range_puertorico" + short_range_hawaii_no_da = "short_range_hawaii_no_da" + short_range_puertorico_no_da = "short_range_puertorico_no_da" + short_range_alaska = "short_range_alaska" + medium_range_mem1 = "medium_range_mem1" + medium_range_mem2 = "medium_range_mem2" + medium_range_mem3 = "medium_range_mem3" + medium_range_mem4 = "medium_range_mem4" + medium_range_mem5 = "medium_range_mem5" + medium_range_mem6 = "medium_range_mem6" + medium_range_mem7 = "medium_range_mem7" + medium_range_no_da = "medium_range_no_da" + medium_range_alaska_mem1 = "medium_range_alaska_mem1" + medium_range_alaska_mem2 = "medium_range_alaska_mem2" + medium_range_alaska_mem3 = "medium_range_alaska_mem3" + medium_range_alaska_mem4 = "medium_range_alaska_mem4" + medium_range_alaska_mem5 = "medium_range_alaska_mem5" + medium_range_alaska_mem6 = "medium_range_alaska_mem6" + medium_range_alaska_no_da = "medium_range_alaska_no_da" + medium_range_blend = "medium_range_blend" + medium_range_blend_alaska = "medium_range_blend_alaska" + long_range_mem1 = "long_range_mem1" + long_range_mem2 = "long_range_mem2" + long_range_mem3 = "long_range_mem3" + long_range_mem4 = "long_range_mem4"
+ + + +# POINT CONFIGURATION MODEL +
+[docs] +class PointConfigurationModel(BaseModel): + """NWM v3.0 PointConfigurationModel.""" + configuration: ConfigurationsEnum = None + analysis_assim: Optional[ShortAndAnalysis] = None + analysis_assim_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_extend: Optional[ShortAndAnalysis] = None + analysis_assim_extend_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_long: Optional[ShortAndAnalysis] = None + analysis_assim_long_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_hawaii: Optional[ShortAndAnalysis] = None + analysis_assim_hawaii_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_puertorico: Optional[ShortAndAnalysis] = None + analysis_assim_puertorico_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_alaska: Optional[ShortAndAnalysis] = None + analysis_assim_alaska_no_da: Optional[ShortAndAnalysisNoDA] = None + analysis_assim_extend_alaska: Optional[ShortAndAnalysis] = None + analysis_assim_extend_alaska_no_da: Optional[ShortAndAnalysisNoDA] = None + short_range: Optional[ShortAndAnalysis] = None + short_range_hawaii: Optional[ShortAndAnalysis] = None + short_range_puertorico: Optional[ShortAndAnalysis] = None + short_range_hawaii_no_da: Optional[ShortAndAnalysisNoDA] = None + short_range_puertorico_no_da: Optional[ShortAndAnalysisNoDA] = None + short_range_alaska: Optional[ShortAndAnalysis] = None + medium_range_mem1: Optional[MediumRangeMem1] = None + medium_range_mem2: Optional[MediumRangeMem2] = None + medium_range_mem3: Optional[MediumRangeMem3] = None + medium_range_mem4: Optional[MediumRangeMem4] = None + medium_range_mem5: Optional[MediumRangeMem5] = None + medium_range_mem6: Optional[MediumRangeMem6] = None + medium_range_mem7: Optional[MediumRangeMem7] = None + medium_range_no_da: Optional[MediumRangeNoDA] = None + medium_range_alaska_mem1: Optional[MediumRangeMem1] = None + medium_range_alaska_mem2: Optional[MediumRangeMem2] = None + medium_range_alaska_mem3: Optional[MediumRangeMem3] = None + medium_range_alaska_mem4: Optional[MediumRangeMem4] = None + medium_range_alaska_mem5: Optional[MediumRangeMem5] = None + medium_range_alaska_mem6: Optional[MediumRangeMem6] = None + medium_range_alaska_no_da: Optional[ShortAndAnalysisNoDA] = None + medium_range_blend: Optional[ShortAndAnalysis] = None + medium_range_blend_alaska: Optional[ShortAndAnalysis] = None + long_range_mem1: Optional[LongRangeMem1] = None + long_range_mem2: Optional[LongRangeMem2] = None + long_range_mem3: Optional[LongRangeMem3] = None + long_range_mem4: Optional[LongRangeMem4] = None
+ + + +if __name__ == "__main__": + # So for example: + configuration = "short_range_alaska" + output_type = "channel_rt" + variable_name = "streamflow" + + # Assemble input parameters + vars = { + "configuration": configuration, + configuration: { + "output_type": output_type, + output_type: variable_name, + }, + } + + # Check input parameters + cm = PointConfigurationModel.model_validate(vars) + + config = cm.configuration.name + forecast_obj = getattr(cm, config) + out_type = forecast_obj.output_type.name + var_name = getattr(forecast_obj, out_type).name + + pass +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/models/loading/utils.html b/_modules/teehr/models/loading/utils.html new file mode 100644 index 00000000..89709c37 --- /dev/null +++ b/_modules/teehr/models/loading/utils.html @@ -0,0 +1,588 @@ + + + + + + + + + + teehr.models.loading.utils — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.models.loading.utils

+"""Module for NWM loading models."""
+from enum import Enum
+# from pydantic import BaseModel
+
+
+
+[docs] +class ChunkByEnum(str, Enum): + """ChunkByEnum.""" + day = "day" + location_id = "location_id" + week = "week" + month = "month" + year = "year"
+ + + +
+[docs] +class SupportedNWMRetroVersionsEnum(str, Enum): + """SupportedNWMRetroVersionsEnum.""" + nwm20 = "nwm20" + nwm21 = "nwm21" + nwm30 = "nwm30"
+ + + +
+[docs] +class SupportedNWMOperationalVersionsEnum(str, Enum): + """SupportedNWMOperationalVersionsEnum.""" + nwm22 = "nwm22" + nwm30 = "nwm30"
+ + + +
+[docs] +class SupportedNWMDataSourcesEnum(str, Enum): + """SupportedNWMDataSourcesEnum.""" + GCS = "GCS" + NOMADS = "NOMADS" + DSTOR = "DSTOR"
+ + + +
+[docs] +class SupportedKerchunkMethod(str, Enum): + """SupportedKerchunkMethod.""" + local = "local" + remote = "remote" + auto = "auto"
+ + + +
+[docs] +class SupportedNWMRetroDomainsEnum(str, Enum): + """SupportedNWMRetroDomainsEnum.""" + CONUS = "CONUS" + Alaska = "Alaska" + PR = "PR" + Hawaii = "Hawaii"
+ + +# class NWM20RetroConfig(BaseModel): +# URL = 's3://noaa-nwm-retro-v2-zarr-pds' +# MIN_DATE = datetime(1993, 1, 1) +# MAX_DATE = datetime(2018, 12, 31, 23) + + +# class NWM21RetroConfig(BaseModel): +# URL = "s3://noaa-nwm-retrospective-2-1-zarr-pds/chrtout.zarr/" +# MIN_DATE = pd.Timestamp(1979, 1, 1) +# MAX_DATE = pd.Timestamp(2020, 12, 31, 23) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/models/queries.html b/_modules/teehr/models/queries.html new file mode 100644 index 00000000..003baff4 --- /dev/null +++ b/_modules/teehr/models/queries.html @@ -0,0 +1,833 @@ + + + + + + + + + + teehr.models.queries — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.models.queries

+"""Module for parquet-based query models."""
+from collections.abc import Iterable
+from datetime import datetime
+from enum import Enum  # StrEnum
+from typing import List, Optional, Union
+
+from pydantic import BaseModel as PydanticBaseModel
+from pydantic import ValidationInfo, field_validator
+from pathlib import Path
+
+
+
+[docs] +class BaseModel(PydanticBaseModel): + """Basemodel configuration.""" +
+[docs] + class ConfigDict: + """ConfigDict.""" + arbitrary_types_allowed = True
+
+ + # smart_union = True # deprecated in v2 + + +
+[docs] +class FilterOperatorEnum(str, Enum): + """Filter symbols.""" + eq = "=" + gt = ">" + lt = "<" + gte = ">=" + lte = "<=" + islike = "like" + isin = "in"
+ + + +
+[docs] +class MetricEnum(str, Enum): + """Available metrics.""" + primary_count = "primary_count" + secondary_count = "secondary_count" + primary_minimum = "primary_minimum" + secondary_minimum = "secondary_minimum" + primary_maximum = "primary_maximum" + secondary_maximum = "secondary_maximum" + primary_average = "primary_average" + secondary_average = "secondary_average" + primary_sum = "primary_sum" + secondary_sum = "secondary_sum" + primary_variance = "primary_variance" + secondary_variance = "secondary_variance" + max_value_delta = "max_value_delta" + bias = "bias" + nash_sutcliffe_efficiency = "nash_sutcliffe_efficiency" + kling_gupta_efficiency = "kling_gupta_efficiency" + mean_error = "mean_error" + mean_squared_error = "mean_squared_error" + root_mean_squared_error = "root_mean_squared_error" + primary_max_value_time = "primary_max_value_time" + secondary_max_value_time = "secondary_max_value_time" + max_value_timedelta = "max_value_timedelta"
+ + + +
+[docs] +class JoinedFilterFieldEnum(str, Enum): + """Joined filter fields.""" + value_time = "value_time" + reference_time = "reference_time" + secondary_location_id = "secondary_location_id" + secondary_value = "secondary_value" + configuration = "configuration" + measurement_unit = "measurement_unit" + variable_name = "variable_name" + primary_value = "primary_value" + primary_location_id = "primary_location_id" + lead_time = "lead_time" + geometry = "geometry"
+ + + +
+[docs] +class TimeseriesFilterFieldEnum(str, Enum): + """Timeseries filter fields.""" + value_time = "value_time" + reference_time = "reference_time" + location_id = "location_id" + value = "value" + configuration = "configuration" + measurement_unit = "measurement_unit" + variable_name = "variable_name" + lead_time = "lead_time" + geometry = "geometry"
+ + + +
+[docs] +class JoinedFilter(BaseModel): + """Joined filter model.""" + column: JoinedFilterFieldEnum + operator: FilterOperatorEnum + value: Union[ + str, int, float, datetime, List[Union[str, int, float, datetime]] + ] + +
+[docs] + def is_iterable_not_str(obj): + """Check if is type Iterable and not str.""" + if isinstance(obj, Iterable) and not isinstance(obj, str): + return True + return False
+ + +
+[docs] + @field_validator("value") + def in_operator_must_have_iterable(cls, v, info: ValidationInfo): + """Ensure that an 'in' operator has an iterable type.""" + if cls.is_iterable_not_str(v) and info.data["operator"] != "in": + raise ValueError("iterable value must be used with 'in' operator") + + if info.data["operator"] == "in" and not cls.is_iterable_not_str(v): + raise ValueError( + "'in' operator can only be used with iterable value" + ) + + return v
+
+ + + +
+[docs] +class TimeseriesFilter(BaseModel): + """Timeseries filter model.""" + column: TimeseriesFilterFieldEnum + operator: FilterOperatorEnum + value: Union[ + str, int, float, datetime, List[Union[str, int, float, datetime]] + ] + +
+[docs] + def is_iterable_not_str(obj): + """Check if is type Iterable and not str.""" + if isinstance(obj, Iterable) and not isinstance(obj, str): + return True + return False
+ + +
+[docs] + @field_validator("value") + def in_operator_must_have_iterable(cls, v, info: ValidationInfo): + """Ensure that an 'in' operator has an iterable type.""" + if cls.is_iterable_not_str(v) and info.data["operator"] != "in": + raise ValueError("iterable value must be used with 'in' operator") + + if info.data["operator"] == "in" and not cls.is_iterable_not_str(v): + raise ValueError( + "'in' operator can only be used with iterable value" + ) + return v
+
+ + + +
+[docs] +class MetricQuery(BaseModel): + """Metric query model.""" + primary_filepath: Union[str, Path] + secondary_filepath: Union[str, Path] + crosswalk_filepath: Union[str, Path] + group_by: List[JoinedFilterFieldEnum] + order_by: List[JoinedFilterFieldEnum] + include_metrics: Union[List[MetricEnum], MetricEnum, str] + filters: Optional[List[JoinedFilter]] = [] + return_query: bool + geometry_filepath: Optional[Union[str, Path]] + include_geometry: bool + remove_duplicates: Optional[bool] = True + +
+[docs] + @field_validator("include_geometry") + def include_geometry_must_group_by_primary_location_id( + cls, v, info: ValidationInfo + ): + """Include_geometry must groupby primary_location_id.""" + if ( + v is True + and JoinedFilterFieldEnum.primary_location_id + not in info.data["group_by"] # noqa + ): + raise ValueError( + "`group_by` must contain `primary_location_id` " + "to include geometry in returned data" + ) + + if v is True and not info.data["geometry_filepath"]: + raise ValueError( + "`geometry_filepath` must be provided to include geometry " + "in returned data" + ) + + if ( + JoinedFilterFieldEnum.geometry in info.data["group_by"] + and v is False + ): + raise ValueError( + "group_by contains `geometry` field but `include_geometry` " + "is False, must be True" + ) + + return v
+ + +
+[docs] + @field_validator("filters") + def filter_must_be_list(cls, v): + """Filter must be a list.""" + if v is None: + return [] + return v
+
+ + + +
+[docs] +class JoinedTimeseriesQuery(BaseModel): + """Joined timeseries query model.""" + primary_filepath: Union[str, Path] + secondary_filepath: Union[str, Path] + crosswalk_filepath: Union[str, Path] + order_by: List[JoinedFilterFieldEnum] + filters: Optional[List[JoinedFilter]] = [] + return_query: bool + geometry_filepath: Optional[Union[str, Path]] + include_geometry: bool + remove_duplicates: Optional[bool] = True + +
+[docs] + @field_validator("include_geometry") + def include_geometry_must_group_by_primary_location_id( + cls, v, info: ValidationInfo + ): + """Include_geometry must groupby primary_location_id.""" + if v is True and not info.data["geometry_filepath"]: + raise ValueError( + "`geometry_filepath` must be provided to include geometry " + "in returned data" + ) + + return v
+ + +
+[docs] + @field_validator("filters") + def filter_must_be_list(cls, v): + """Filter must be a list.""" + if v is None: + return [] + return v
+
+ + + +
+[docs] +class TimeseriesQuery(BaseModel): + """Timeseries query model.""" + timeseries_filepath: Union[str, Path] + order_by: List[TimeseriesFilterFieldEnum] + filters: Optional[List[TimeseriesFilter]] = [] + return_query: bool + +
+[docs] + @field_validator("filters") + def filter_must_be_list(cls, v): + """Filter must be a list.""" + if v is None: + return [] + return v
+
+ + + +
+[docs] +class TimeseriesCharQuery(BaseModel): + """Timeseries char query model.""" + timeseries_filepath: Union[str, Path] + order_by: List[TimeseriesFilterFieldEnum] + group_by: List[TimeseriesFilterFieldEnum] + filters: Optional[List[TimeseriesFilter]] = [] + return_query: bool + +
+[docs] + @field_validator("filters") + def filter_must_be_list(cls, v): + """Filter must be a list.""" + if v is None: + return [] + return v
+
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/models/queries_database.html b/_modules/teehr/models/queries_database.html new file mode 100644 index 00000000..fb7dc233 --- /dev/null +++ b/_modules/teehr/models/queries_database.html @@ -0,0 +1,993 @@ + + + + + + + + + + teehr.models.queries_database — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.models.queries_database

+"""Module for database query models."""
+from collections.abc import Iterable
+from datetime import datetime
+from enum import Enum  # , StrEnum  if 3.11
+from typing import List, Optional, Union
+
+from pydantic import BaseModel as PydanticBaseModel
+from pydantic import ValidationInfo, field_validator, model_validator
+from pathlib import Path
+
+from teehr.models.queries import FilterOperatorEnum, MetricEnum
+
+
+
+[docs] +class BaseModel(PydanticBaseModel): + """Basemodel configuration.""" +
+[docs] + class ConfigDict: + """ConfigDict.""" + arbitrary_types_allowed = True
+
+ + # smart_union = True # deprecated in v2 + + +
+[docs] +class FieldTypeEnum(str, Enum): + """Allowable duckdb data types.""" + + BIGINT = "BIGINT" + BIT = "BIT" + BOOLEAN = "BOOLEAN" + BLOB = "BLOB" + DATE = "DATE" + DOUBLE = "DOUBLE" + DECIMAL = "DECIMAL" + FLOAT = "FLOAT" + HUGEINT = "HUGEINT" + INTEGER = "INTEGER" + INTERVAL = "INTEGER" + REAL = "REAL" + SMALLINT = "SMALLINT" + TIME = "TIME" + TIMESTAMP = "TIMESTAMP" + TINYINT = "TINYINT" + UBIGINT = "UBIGINT" + UINTEGER = "UINTEGER" + USMALLINT = "USMALLINT" + UTINYINT = "UTINYINT" + UUID = "UUID" + VARCHAR = "VARCHAR"
+ + + +
+[docs] +class JoinedFieldNameEnum(str, Enum): + """Names of fields in base joined_timeseries table.""" + + reference_time = "reference_time" + value_time = "value_time" + secondary_location_id = "secondary_location_id" + secondary_value = "secondary_value" + configuration = "configuration" + measurement_unit = "measurement_unit" + variable_name = "variable_name" + primary_value = "primary_value" + primary_location_id = "primary_location_id" + lead_time = "lead_time" + absolute_difference = "absolute_difference" + geometry = "geometry"
+ + + +
+[docs] +class TimeseriesNameEnum(str, Enum): + """Timeseries Names.""" + primary = "primary" + secondary = "secondary"
+ + + +
+[docs] +class JoinedTimeseriesFieldName(BaseModel): + """Joined Timeseries Field Name model.""" + field_name: str + +
+[docs] + @field_validator("field_name") + def field_name_must_exist_in_timeseries_table(cls, v, info: ValidationInfo): # noqa + """Field name must exist in the database table.""" + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + if v not in existing_fields: + raise ValueError( + f"The field name {v} does not exist in" + "the joined_timseries table" + ) + return v
+
+ + + +
+[docs] +class CalculateField(BaseModel): + """Calculate field model.""" + parameter_names: List[str] + new_field_name: str + new_field_type: FieldTypeEnum + + # TODO: Add field_name validator? (has already been sanitized) +
+[docs] + @field_validator("new_field_name") + def field_name_must_be_valid(cls, v): + """Must not contain special characters.""" + return v
+ + +
+[docs] + @field_validator("parameter_names") + def parameter_names_must_exist_as_fields(cls, v, info: ValidationInfo): + """Parameter name must exist in the database table.""" + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + for val in v: + if val not in existing_fields: + raise ValueError( + f"The function parameter {val} does not exist in the database" # noqa + ) + return v
+
+ + + +
+[docs] +class Filter(BaseModel): + """Filter model.""" + column: str + operator: FilterOperatorEnum + value: Union[ + str, int, float, datetime, List[Union[str, int, float, datetime]] + ] + +
+[docs] + def is_iterable_not_str(obj): + """Check if obj is iterable and not str.""" + if isinstance(obj, Iterable) and not isinstance(obj, str): + return True + return False
+ + +
+[docs] + @field_validator("value") + def in_operator_must_have_iterable( + cls, v: str, info: ValidationInfo + ) -> str: + """Ensure the 'in' operator has an iterable.""" + if cls.is_iterable_not_str(v) and info.data["operator"] != "in": + raise ValueError("iterable value must be used with 'in' operator") + + if info.data["operator"] == "in" and not cls.is_iterable_not_str(v): + raise ValueError( + "'in' operator can only be used with iterable value" + ) + + return v
+
+ + + +
+[docs] +class InsertJoinedTimeseriesQuery(BaseModel): + """InsertJoinedTimeseriesQuery model.""" + + primary_filepath: Union[str, Path] + secondary_filepath: Union[str, Path] + crosswalk_filepath: Union[str, Path] + order_by: Optional[List[JoinedFieldNameEnum]] = []
+ + + +
+[docs] +class JoinedTimeseriesQuery(BaseModel): + """JoinedTimeseriesQuery model.""" + + order_by: List[str] + filters: Optional[List[Filter]] = [] + return_query: Optional[bool] = False + include_geometry: bool + +
+[docs] + @field_validator("filters") + def filter_must_be_list(cls, v): + """Filter must be a list.""" + if v is None: + return [] + return v
+ + +
+[docs] + @field_validator("order_by") + def order_by_must_exist_as_fields(cls, v, info: ValidationInfo): + """Order_by fields must currently exist in the database.""" + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + for val in v: + if val not in existing_fields: + raise ValueError( + f"The order_by field '{val}' does not" + "exist in the database" + ) + return v
+ + +
+[docs] + @field_validator("filters") + def filters_must_exist_as_fields(cls, v, info: ValidationInfo): + """Filter fields must currently exist in the database.""" + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + for val in v: + if val.column not in existing_fields: + raise ValueError( + f"The filters field {val.column} does not" + "exist in the database" + ) + return v
+
+ + + +
+[docs] +class TimeseriesQuery(BaseModel): + """TimeseriesQuery model.""" + + order_by: List[str] + filters: Optional[List[Filter]] = [] + return_query: Optional[bool] = False + timeseries_name: TimeseriesNameEnum + +
+[docs] + @field_validator("filters") + def filter_must_be_list(cls, v): + """Filter must be a list.""" + if v is None: + return [] + return v
+ + +
+[docs] + @field_validator("order_by") + def order_by_must_exist_as_fields(cls, v, info: ValidationInfo): + """Order_by fields must be part one of the selected fields or + its alias.""" + validation_fields = [ + "location_id", + "reference_time", + "value", + "primary_value", + "secondary_value", + "value_time", + "primary_location_id", + "secondary_location_id", + "configuration", + "measurement_unit", + "variable_name" + ] + for val in v: + if val not in validation_fields: + raise ValueError( + f"The order_by field '{val}' must be a timeseries" + f" field or its alias: {validation_fields}" + ) + return v
+ + +
+[docs] + @field_validator("filters") + def filters_must_exist_as_fields(cls, v, info: ValidationInfo): + """Filter fields must currently exist in the database.""" + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + for val in v: + if val.column not in existing_fields: + raise ValueError( + f"The filters field {val.column} does not" + "exist in the database" + ) + return v
+
+ + + +
+[docs] +class TimeseriesCharQuery(BaseModel): + """Timeseries char query model.""" + order_by: List[str] + group_by: List[str] + filters: Optional[List[Filter]] = [] + return_query: Optional[bool] = False + timeseries_name: TimeseriesNameEnum + +
+[docs] + @field_validator("filters") + def filter_must_be_list(cls, v): + """Filter must be a list.""" + if v is None: + return [] + return v
+ + +
+[docs] + @field_validator("order_by") + def order_by_must_exist_as_fields_or_chars(cls, v, info: ValidationInfo): + """Order_by fields must currently exist in the database or be one of + the calculated stats.""" + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + existing_fields.extend(["count", + "min", + "max", + "average", + "sum", + "variance"]) + for val in v: + if val not in existing_fields: + raise ValueError( + f"The order_by or group_by field '{val}' does not" + "exist in the database" + ) + return v
+ + +
+[docs] + @field_validator("group_by") + def group_by_must_exist_as_fields(cls, v, info: ValidationInfo): + """Group_by fields must currently exist in the database.""" + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + for val in v: + if val not in existing_fields: + raise ValueError( + f"The order_by or group_by field '{val}' does not" + "exist in the database" + ) + return v
+ + +
+[docs] + @field_validator("group_by") + def group_by_must_contain_primary_or_secondary_id(cls, v): + """Group_by must contain primary or secondary id.""" + id_list = ["primary_location_id", "secondary_location_id"] + if not any([val in id_list for val in v]): + raise ValueError( + "Group By must contain primary or secondary" + " location id" + ) + return v
+ + +
+[docs] + @field_validator("filters") + def filters_must_exist_as_fields(cls, v, info: ValidationInfo): + """Filter fields must currently exist in the database.""" + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + for val in v: + if val.column not in existing_fields: + raise ValueError( + f"The filters field {val.column} does not" + "exist in the database" + ) + return v
+
+ + + +
+[docs] +class MetricQuery(BaseModel): + """Metric query model.""" + include_geometry: bool + group_by: List[str] + order_by: List[str] + include_metrics: Union[List[MetricEnum], MetricEnum, str] + filters: Optional[List[Filter]] = None + return_query: Optional[bool] = False + +
+[docs] + @field_validator("filters") + def filter_must_be_list(cls, v): + """Filter must be a list.""" + if v is None: + return [] + return v
+ + +
+[docs] + @model_validator(mode="before") + @classmethod + def validate_include_geometry_and_specified_fields( + cls, data, info: ValidationInfo + ): + """Validate 'include_geometry' and order_by, group_by + and filters fields. + """ + if data["include_geometry"]: + # If geometry is included, group_by must + # contain 'primary_location_id' + if JoinedFieldNameEnum.primary_location_id not in data["group_by"]: + raise ValueError( + "`group_by` must contain `primary_location_id` " + "to include geometry in returned data" + ) + + # order_by, group_by, and filter fields must + # currently exist in the database + context = info.context + if context: + existing_fields = context.get("existing_fields", set()) + for val in data["group_by"]: + if val not in existing_fields: + raise ValueError( + f"The group_by field '{val}' does not" + "exist in the database" + ) + for val in data["order_by"]: + if val not in existing_fields: + raise ValueError( + f"The order_by field '{val}' does not" + "exist in the database" + ) + if data["filters"]: + for val in data["filters"]: + if val["column"] not in existing_fields: + raise ValueError( + f"The filter field '{val}' does not" + "exist in the database" + ) + + return data
+
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/queries/duckdb.html b/_modules/teehr/queries/duckdb.html new file mode 100644 index 00000000..62e0a0ee --- /dev/null +++ b/_modules/teehr/queries/duckdb.html @@ -0,0 +1,1148 @@ + + + + + + + + + + teehr.queries.duckdb — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.queries.duckdb

+"""A module defining duckdb sql queries for parquet files."""
+import duckdb
+
+import pandas as pd
+import geopandas as gpd
+
+from typing import List, Union
+
+from teehr.models.queries import (
+    MetricQuery,
+    JoinedTimeseriesQuery,
+    TimeseriesQuery,
+    TimeseriesCharQuery,
+)
+
+import teehr.queries.utils as tqu
+import teehr.models.queries as tmq
+
+SQL_DATETIME_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+
+
+[docs] +def get_metrics( + primary_filepath: str, + secondary_filepath: str, + crosswalk_filepath: str, + group_by: List[str], + order_by: List[str], + include_metrics: Union[List[tmq.MetricEnum], "all"], + filters: Union[List[dict], None] = None, + return_query: bool = False, + geometry_filepath: Union[str, None] = None, + include_geometry: bool = False, + remove_duplicates: bool = True, +) -> Union[str, pd.DataFrame, gpd.GeoDataFrame]: + """Calculate performance metrics using a parquet query. + + Parameters + ---------- + primary_filepath : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + secondary_filepath : str + File path to the "forecast" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + crosswalk_filepath : str + File path to single crosswalk file. + group_by : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + include_metrics : List[str] + List of metrics (see below) for allowable list, or "all" to return all + Placeholder, currently ignored -> returns "all". + filters : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + return_query : bool = False + True returns the query string instead of the data. + include_geometry : bool = True + True joins the geometry to the query results. + Only works if `primary_location_id` + is included as a group_by field. + remove_duplicates : bool = True + True (default) removes joined timeseries rows with duplicate primary + values, where unique values are defined by the value_time, + secondary_reference_time, location_id, configuration, + variable_name and measurement_unit fields. + False does not check for or remove duplicate values. + This option can be used to improve performance if you are certain you + do not have duplicate primary_values. + + Returns + ------- + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + Notes + ----- + Filter, Order By and Group By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * [any user-added fields] + + Basic Metrics: + + * primary_count + * secondary_count + * primary_minimum + * secondary_minimum + * primary_maximum + * secondary_maximum + * primary_average + * secondary_average + * primary_sum + * secondary_sum + * primary_variance + * secondary_variance + * max_value_delta + + * max(secondary_value) - max(primary_value) + * bias + + * sum(primary_value - secondary_value)/count(*) + + HydroTools Metrics: + + * nash_sutcliffe_efficiency + * kling_gupta_efficiency + * coefficient_of_extrapolation + * coefficient_of_persistence + * mean_error + * mean_squared_error + * root_mean_squared_error + + Time-based Metrics: + + * primary_max_value_time + * secondary_max_value_time + * max_value_timedelta + + Examples + -------- + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + """ + + mq = MetricQuery.model_validate( + { + "primary_filepath": primary_filepath, + "secondary_filepath": secondary_filepath, + "crosswalk_filepath": crosswalk_filepath, + "group_by": group_by, + "order_by": order_by, + "include_metrics": include_metrics, + "filters": filters, + "return_query": return_query, + "include_geometry": include_geometry, + "geometry_filepath": geometry_filepath, + "remove_duplicates": remove_duplicates + } + ) + + query = f""" + WITH initial_joined AS ( + SELECT + sf.reference_time + , sf.value_time as value_time + , sf.location_id as secondary_location_id + , pf.reference_time as primary_reference_time + , sf.value as secondary_value + , sf.configuration + , sf.measurement_unit + , sf.variable_name + , pf.value as primary_value + , pf.location_id as primary_location_id + , sf.value_time - sf.reference_time as lead_time + , abs(pf.value - sf.value) as absolute_difference + FROM read_parquet('{str(mq.secondary_filepath)}') sf + JOIN read_parquet('{str(mq.crosswalk_filepath)}') cf + on cf.secondary_location_id = sf.location_id + JOIN read_parquet("{str(mq.primary_filepath)}") pf + on cf.primary_location_id = pf.location_id + and sf.value_time = pf.value_time + and sf.measurement_unit = pf.measurement_unit + and sf.variable_name = pf.variable_name + {tqu.filters_to_sql(mq.filters)} + ), + joined AS ( + {tqu._remove_duplicates_mq_cte(mq)} + ) + {tqu._nse_cte(mq)} + , metrics AS ( + SELECT + {",".join([f"joined.{gb}" for gb in mq.group_by])} + {tqu._select_primary_count(mq)} + {tqu._select_secondary_count(mq)} + {tqu._select_primary_minimum(mq)} + {tqu._select_secondary_minimum(mq)} + {tqu._select_primary_maximum(mq)} + {tqu._select_secondary_maximum(mq)} + {tqu._select_primary_average(mq)} + {tqu._select_secondary_average(mq)} + {tqu._select_primary_sum(mq)} + {tqu._select_secondary_sum(mq)} + {tqu._select_primary_variance(mq)} + {tqu._select_secondary_variance(mq)} + {tqu._select_max_value_delta(mq)} + {tqu._select_bias(mq)} + {tqu._select_nash_sutcliffe_efficiency(mq)} + {tqu._select_kling_gupta_efficiency(mq)} + {tqu._select_mean_error(mq)} + {tqu._select_mean_squared_error(mq)} + {tqu._select_root_mean_squared_error(mq)} + {tqu._select_primary_max_value_time(mq)} + {tqu._select_secondary_max_value_time(mq)} + {tqu._select_max_value_timedelta(mq)} + FROM + joined + {tqu._join_nse_cte(mq)} + GROUP BY + {",".join([f"joined.{gb}" for gb in mq.group_by])} + ) + SELECT + metrics.* + {tqu.geometry_select_clause(mq)} + FROM metrics + {tqu.metric_geometry_join_clause(mq)} + ORDER BY + {",".join([f"metrics.{ob}" for ob in mq.order_by])} + ;""" + + if mq.return_query: + return tqu.remove_empty_lines(query) + + df = duckdb.query(query).to_df() + + if mq.include_geometry: + return tqu.df_to_gdf(df) + + return df
+ + + +
+[docs] +def get_joined_timeseries( + primary_filepath: str, + secondary_filepath: str, + crosswalk_filepath: str, + order_by: List[str], + filters: Union[List[dict], None] = None, + return_query: bool = False, + geometry_filepath: Union[str, None] = None, + include_geometry: bool = False, + remove_duplicates: bool = True, +) -> Union[str, pd.DataFrame, gpd.GeoDataFrame]: + """Retrieve joined timeseries using a parquet query. + + Parameters + ---------- + primary_filepath : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + secondary_filepath : str + File path to the "forecast" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + crosswalk_filepath : str + File path to single crosswalk file. + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + filters : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + return_query : bool = False + True returns the query string instead of the data. + include_geometry : bool = True + True joins the geometry to the query results. + Only works if `primary_location_id`. + is included as a group_by field. + remove_duplicates : bool = True + True (default) removes joined timeseries rows with duplicate primary + values, where unique values are defined by the value_time, + secondary_reference_time, location_id, configuration, + variable_name and measurement_unit fields. + False does not check for or remove duplicate values. + This option can be used to improve performance if you are certain you + do not have duplicate primary_values. + + Returns + ------- + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + Notes + ----- + + Filter and Order By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + + Examples + -------- + >>> order_by = ["lead_time", "primary_location_id"] + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "'123456'" + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "'2022-01-01 00:00'" + >>> }, + >>> { + >>> "column": "lead_time", + >>> "operator": "<=", + >>> "value": "'10 days'" + >>> } + >>> ] + """ + + jtq = JoinedTimeseriesQuery.model_validate( + { + "primary_filepath": primary_filepath, + "secondary_filepath": secondary_filepath, + "crosswalk_filepath": crosswalk_filepath, + "order_by": order_by, + "filters": filters, + "return_query": return_query, + "include_geometry": include_geometry, + "geometry_filepath": geometry_filepath, + "remove_duplicates": remove_duplicates + } + ) + + query = f""" + WITH initial_joined as ( + SELECT + sf.reference_time, + sf.value_time, + sf.location_id as secondary_location_id, + sf.value as secondary_value, + sf.configuration, + sf.measurement_unit, + sf.variable_name, + pf.reference_time as primary_reference_time, + pf.value as primary_value, + pf.location_id as primary_location_id, + sf.value_time - sf.reference_time as lead_time + {tqu.geometry_select_clause(jtq)} + FROM read_parquet('{str(jtq.secondary_filepath)}') sf + JOIN read_parquet('{str(jtq.crosswalk_filepath)}') cf + on cf.secondary_location_id = sf.location_id + JOIN read_parquet("{str(jtq.primary_filepath)}") pf + on cf.primary_location_id = pf.location_id + and sf.value_time = pf.value_time + and sf.measurement_unit = pf.measurement_unit + and sf.variable_name = pf.variable_name + {tqu.geometry_join_clause(jtq)} + {tqu.filters_to_sql(jtq.filters)} + ), + joined AS ( + {tqu._remove_duplicates_jtq_cte(jtq)} + ) + SELECT + * + FROM + joined + ORDER BY + {",".join(jtq.order_by)} + ;""" + + if jtq.return_query: + return tqu.remove_empty_lines(query) + + df = duckdb.query(query).to_df() + + df["primary_location_id"] = df["primary_location_id"].astype("category") + df["secondary_location_id"] = df["secondary_location_id"].astype( + "category" + ) + df["configuration"] = df["configuration"].astype("category") + df["measurement_unit"] = df["measurement_unit"].astype("category") + df["variable_name"] = df["variable_name"].astype("category") + + if jtq.include_geometry: + return tqu.df_to_gdf(df) + + return df
+ + + +
+[docs] +def get_timeseries( + timeseries_filepath: str, + order_by: List[str], + filters: Union[List[dict], None] = None, + return_query: bool = False, +) -> Union[str, pd.DataFrame, gpd.GeoDataFrame]: + """Retrieve timeseries using a parquet query. + + Parameters + ---------- + timeseries_filepath : str + File path to the timeseries data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + filters : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + return_query : bool = False + True returns the query string instead of the data. + + Returns + ------- + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + Notes + ----- + Filter and Order By Fields: + + * value_time + * location_id + * value + * measurement_unit + * reference_time + * configuration + * variable_name + + Examples + -------- + >>> order_by = ["lead_time", "primary_location_id"] + >>> filters = [ + >>> { + >>> "column": "location_id", + >>> "operator": "in", + >>> "value": [12345, 54321] + >>> }, + >>> ] + """ + tq = TimeseriesQuery.model_validate( + { + "timeseries_filepath": timeseries_filepath, + "order_by": order_by, + "filters": filters, + "return_query": return_query, + } + ) + + query = f""" + WITH joined as ( + SELECT + sf.reference_time, + sf.value_time, + sf.location_id, + sf.value, + sf.configuration, + sf.measurement_unit, + sf.variable_name + FROM + read_parquet("{str(tq.timeseries_filepath)}") sf + {tqu.filters_to_sql(tq.filters)} + ) + SELECT * FROM + joined + ORDER BY + {",".join(tq.order_by)} + ;""" + + if tq.return_query: + return tqu.remove_empty_lines(query) + + df = duckdb.query(query).to_df() + + df["location_id"] = df["location_id"].astype("category") + df["configuration"] = df["configuration"].astype("category") + df["measurement_unit"] = df["measurement_unit"].astype("category") + df["variable_name"] = df["variable_name"].astype("category") + + return df
+ + + +
+[docs] +def get_timeseries_chars( + timeseries_filepath: str, + group_by: list[str], + order_by: List[str], + filters: Union[List[dict], None] = None, + return_query: bool = False, +) -> Union[str, pd.DataFrame, gpd.GeoDataFrame]: + """Retrieve timeseries characteristics using a parquet query. + + Parameters + ---------- + timeseries_filepath : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + group_by : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + filters : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + return_query : bool = False + True returns the query string instead of the data. + + Returns + ------- + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + Notes + ----- + Filter, Group By and Order By Fields + + * value_time + * location_id + * value + * measurement_unit + * reference_time + * configuration + * variable_name + + Examples + -------- + >>> order_by = ["lead_time", "primary_location_id"] + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "'123456'" + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "'2022-01-01 00:00'" + >>> }, + >>> { + >>> "column": "lead_time", + >>> "operator": "<=", + >>> "value": "'10 days'" + >>> } + >>> ] + """ + + tcq = TimeseriesCharQuery.model_validate( + { + "timeseries_filepath": timeseries_filepath, + "order_by": order_by, + "group_by": group_by, + "filters": filters, + "return_query": return_query, + } + ) + + join_max_time_on = tqu._join_time_on( + join="mxt", join_to="chars", join_on=tcq.group_by + ) + + order_by = [f"chars.{val}" for val in tcq.order_by] + + query = f""" + WITH fts AS ( + SELECT sf.* FROM + read_parquet('{str(tcq.timeseries_filepath)}') sf + {tqu.filters_to_sql(tcq.filters)} + ), + mxt AS ( + SELECT + {",".join(tcq.group_by)} + , value + , value_time + , ROW_NUMBER() OVER( + PARTITION BY {",".join(tcq.group_by)} + ORDER BY value DESC, value_time + ) as n + FROM fts + ), + chars AS ( + SELECT + {",".join(tcq.group_by)} + ,count(fts.value) as count + ,min(fts.value) as min + ,max(fts.value) as max + ,avg(fts.value) as average + ,sum(fts.value) as sum + ,var_pop(fts.value) as variance + FROM + fts + GROUP BY + {",".join(tcq.group_by)} + ) + SELECT + chars.* + ,mxt.value_time as max_value_time + FROM chars + {join_max_time_on} + ORDER BY + {",".join(order_by)} + ;""" + + if tcq.return_query: + return tqu.remove_empty_lines(query) + + df = duckdb.query(query).to_df() + + return df
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/queries/duckdb_database.html b/_modules/teehr/queries/duckdb_database.html new file mode 100644 index 00000000..a718bb86 --- /dev/null +++ b/_modules/teehr/queries/duckdb_database.html @@ -0,0 +1,1266 @@ + + + + + + + + + + teehr.queries.duckdb_database — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.queries.duckdb_database

+"""A module defining duckdb sql queries for a persistent database."""
+import duckdb
+
+from typing import Dict
+
+from teehr.models.queries_database import (
+    MetricQuery,
+    InsertJoinedTimeseriesQuery,
+    JoinedTimeseriesQuery,
+    TimeseriesQuery,
+    TimeseriesCharQuery,
+    JoinedTimeseriesFieldName
+)
+
+import teehr.queries.utils as tqu
+
+SQL_DATETIME_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+
+
+[docs] +def create_get_metrics_query(mq: MetricQuery) -> str: + """Build the query string to calculate performance metrics + using database queries. + + Parameters + ---------- + mq : MetricQuery + Pydantic model containing query parameters. + + Returns + ------- + str + The query string. + + Notes + ----- + Filter, Order By and Group By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * [any user-added fields] + + Basic Metrics: + + * primary_count + * secondary_count + * primary_minimum + * secondary_minimum + * primary_maximum + * secondary_maximum + * primary_average + * secondary_average + * primary_sum + * secondary_sum + * primary_variance + * secondary_variance + * max_value_delta + + * max(secondary_value) - max(primary_value) + * bias + + * sum(primary_value - secondary_value)/count(*) + + HydroTools Metrics: + + * nash_sutcliffe_efficiency + * kling_gupta_efficiency + * coefficient_of_extrapolation + * coefficient_of_persistence + * mean_error + * mean_squared_error + * root_mean_squared_error + + Time-based Metrics: + + * primary_max_value_time + * secondary_max_value_time + * max_value_timedelta + + Examples + -------- + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + """ + query = f""" + WITH joined as ( + SELECT + * + FROM joined_timeseries sf + {tqu.filters_to_sql(mq.filters)} + ) + {tqu._nse_cte(mq)} + , metrics AS ( + SELECT + {",".join([f"joined.{gb}" for gb in mq.group_by])} + {tqu._select_primary_count(mq)} + {tqu._select_secondary_count(mq)} + {tqu._select_primary_minimum(mq)} + {tqu._select_secondary_minimum(mq)} + {tqu._select_primary_maximum(mq)} + {tqu._select_secondary_maximum(mq)} + {tqu._select_primary_average(mq)} + {tqu._select_secondary_average(mq)} + {tqu._select_primary_sum(mq)} + {tqu._select_secondary_sum(mq)} + {tqu._select_primary_variance(mq)} + {tqu._select_secondary_variance(mq)} + {tqu._select_max_value_delta(mq)} + {tqu._select_bias(mq)} + {tqu._select_nash_sutcliffe_efficiency(mq)} + {tqu._select_kling_gupta_efficiency(mq)} + {tqu._select_mean_error(mq)} + {tqu._select_mean_squared_error(mq)} + {tqu._select_root_mean_squared_error(mq)} + {tqu._select_primary_max_value_time(mq)} + {tqu._select_secondary_max_value_time(mq)} + {tqu._select_max_value_timedelta(mq)} + FROM + joined + {tqu._join_nse_cte(mq)} + GROUP BY + {",".join([f"joined.{gb}" for gb in mq.group_by])} + ) + SELECT + metrics.* + {tqu.geometry_select_clause(mq)} + FROM metrics + {tqu.metric_geometry_join_clause_db(mq)} + ORDER BY + {",".join([f"metrics.{ob}" for ob in mq.order_by])} + ;""" + + return query
+ + + +
+[docs] +def create_join_and_save_timeseries_query(jtq: JoinedTimeseriesQuery) -> str: + """Load joined timeseries into a duckdb persistent database using a + database query. + + Parameters + ---------- + jtq : JoinedTimeseriesQuery + Pydantic model containing query parameters. + + Returns + ------- + str + The query string. + """ + query = f""" + WITH initial_joined as ( + SELECT + sf.reference_time, + sf.value_time, + sf.location_id as secondary_location_id, + sf.value as secondary_value, + sf.configuration, + sf.measurement_unit, + pf.reference_time as primary_reference_time, + sf.variable_name, + pf.value as primary_value, + pf.location_id as primary_location_id, + sf.value_time - sf.reference_time as lead_time, + abs(primary_value - secondary_value) as absolute_difference + FROM read_parquet('{str(jtq.secondary_filepath)}') sf + JOIN read_parquet('{str(jtq.crosswalk_filepath)}') cf + on cf.secondary_location_id = sf.location_id + JOIN read_parquet("{str(jtq.primary_filepath)}") pf + on cf.primary_location_id = pf.location_id + and sf.value_time = pf.value_time + and sf.measurement_unit = pf.measurement_unit + and sf.variable_name = pf.variable_name + ), + joined AS ( + SELECT + reference_time + , value_time + , secondary_location_id + , secondary_value + , configuration + , measurement_unit + , variable_name + , primary_value + , primary_location_id + , lead_time + , absolute_difference + FROM( + SELECT *, + row_number() + OVER( + PARTITION BY value_time, + primary_location_id, + configuration, + variable_name, + measurement_unit, + reference_time + ORDER BY primary_reference_time desc + ) AS rn + FROM initial_joined + ) + WHERE rn = 1 + ) + INSERT INTO joined_timeseries + SELECT + * + FROM + joined + ORDER BY + {",".join(jtq.order_by)} + ;""" + + return query
+ + + +
+[docs] +def describe_timeseries(timeseries_filepath: str) -> Dict: + """Retrieve descriptive stats for a time series. + + Parameters + ---------- + timeseries_filepath : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + + Returns + ------- + Dict + A dictionary of summary statistics for a timeseries. + """ + # TEST QUERIES + + # Find number of rows and unique locations + query = f""" + SELECT + COUNT ( DISTINCT + location_id + ) + AS num_location_ids, + COUNT(*) AS num_rows, + MAX(value_time) as end_date, + MIN(value_time) as start_date + FROM read_parquet("{timeseries_filepath}") + """ + df = duckdb.sql(query).to_df() + num_location_ids = df["num_location_ids"][0] + total_num_rows = df["num_rows"][0] + start_date = df["start_date"][0] + end_date = df["end_date"][0] + + # Find number of duplicates from all columns + query = f""" + SELECT + value_time, + location_id, + value, + measurement_unit, + reference_time, + configuration, + variable_name, + COUNT(*) + FROM read_parquet("{timeseries_filepath}") + GROUP BY + value_time, + location_id, + value, + measurement_unit, + reference_time, + configuration, + variable_name, + HAVING COUNT(*) > 1 + """ + df = duckdb.sql(query).to_df() + num_duplicate_rows = df.index.size + + # Find number of duplicate value_times per location_id + query = f""" + WITH find_duplicates AS ( + SELECT + value_time, + location_id, + measurement_unit, + configuration, + variable_name, + COUNT(*) AS num_duplicates + FROM read_parquet("{timeseries_filepath}") + GROUP BY + value_time, + location_id, + measurement_unit, + configuration, + variable_name, + HAVING COUNT(*) > 1 + ) + SELECT + COUNT( DISTINCT location_id) + AS num_locations_with_duplicates + FROM find_duplicates + WHERE num_duplicates > 1 + """ + df = duckdb.sql(query).to_df() + num_locations_with_duplicate_value_times = df[ + "num_locations_with_duplicates" + ][0] + + # Check time step integrity by reference_time and location_id + query = f""" + WITH value_time_diff AS ( + SELECT *, + value_time - LAG(value_time) + OVER(PARTITION BY + location_id, + reference_time + ORDER BY value_time) + AS value_time_step + FROM read_parquet("{timeseries_filepath}") + ), + missing_timesteps AS ( + SELECT + location_id, + reference_time, + MAX(value_time_step) AS max_timestep, + MIN(value_time_step) AS min_timestep, + max_timestep = min_timestep AS none_missing + FROM value_time_diff + GROUP BY location_id, reference_time + ) + SELECT + location_id, + reference_time, + none_missing + FROM missing_timesteps + WHERE none_missing != TRUE + """ + df = duckdb.sql(query).to_df() + num_locations_with_missing_timesteps = df.index.size + + output_report = { + "Number of unique location IDs": num_location_ids, + "Total number of rows": total_num_rows, + "Start Date": start_date, + "End Date": end_date, + "Number of duplicate rows": num_duplicate_rows, + "Number of location IDs with duplicate value times": + num_locations_with_duplicate_value_times, + "Number of location IDs with missing time steps": + num_locations_with_missing_timesteps, + } + + return output_report
+ + + +
+[docs] +def create_get_joined_timeseries_query( + jtq: JoinedTimeseriesQuery +) -> str: + """Retrieve joined timeseries using database query. + + Parameters + ---------- + jtq : JoinedTimeseriesQuery + Pydantic model containing query parameters. + + Returns + ------- + str + The query string. + + Notes + ----- + Filter By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * absolute_difference + * [any user-added fields] + + Order By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + + Examples + -------- + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + """ + + query = f""" + SELECT + sf.* + {tqu.geometry_select_clause(jtq)} + FROM + joined_timeseries sf + {tqu.metric_geometry_join_clause_db(jtq)} + {tqu.filters_to_sql(jtq.filters)} + ORDER BY + {",".join(jtq.order_by)} + ;""" + + return query
+ + + +
+[docs] +def create_get_timeseries_query( + tq: TimeseriesQuery +) -> str: + """Retrieve joined timeseries using database query. + + Parameters + ---------- + tq : TimeseriesQuery + Pydantic model containing query parameters. + + Returns + ------- + str + The query string. + + Notes + ----- + Filter By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * absolute_difference + * [any user-added fields] + + Order By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + + Examples + -------- + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + """ + if tq.timeseries_name == "primary": + query = f""" + SELECT + ANY_VALUE(reference_time) AS reference_time, + ANY_VALUE(primary_value) AS value, + value_time, + primary_location_id AS location_id, + 'primary' as configuration, + measurement_unit, + variable_name, + FROM + joined_timeseries sf + {tqu.filters_to_sql(tq.filters)} + GROUP BY + value_time, + primary_location_id, + --configuration, + measurement_unit, + variable_name, + ORDER BY + {",".join(tq.order_by)} + ;""" + else: + query = f""" + SELECT + reference_time, + value_time, + secondary_location_id AS location_id, + secondary_value AS value, + configuration, + measurement_unit, + variable_name, + FROM + joined_timeseries sf + {tqu.filters_to_sql(tq.filters)} + ORDER BY + {",".join(tq.order_by)} + ;""" + + return query
+ + + +
+[docs] +def create_get_timeseries_char_query(tcq: TimeseriesCharQuery) -> str: + """Retrieve joined timeseries using database query. + + Parameters + ---------- + tcq : TimeseriesCharQuery + Pydantic model containing query parameters. + + Returns + ------- + str + The query string. + + Notes + ----- + Filter, Order By and Group By Fields + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * [any user-added fields] + + Examples + -------- + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + """ + + join_max_time_on = tqu._join_time_on( + join="mxt", join_to="chars", join_on=tcq.group_by + ) + + order_by = [f"chars.{val}" for val in tcq.order_by] + + # Create the fts_clause to remove duplicates if primary time series + # has been specified + if tcq.timeseries_name == "primary": + + selected_primary_fields = ["reference_time", + "primary_value", + "value_time", + "primary_location_id", + "configuration", + "measurement_unit", + "variable_name"] + # Format any group by fields that are not selected + # by default + gb_fields = "" + for gb_fld in tcq.group_by: + if gb_fld not in selected_primary_fields: + gb_fields += f"ANY_VALUE({gb_fld}) as {gb_fld}, " + + fts_clause = f""" + SELECT + ANY_VALUE(reference_time) AS reference_time, + ANY_VALUE(primary_value) AS primary_value, + value_time, + primary_location_id, + 'primary' as configuration, + measurement_unit, + variable_name, + {gb_fields} + FROM + joined_timeseries sf + {tqu.filters_to_sql(tcq.filters)} + GROUP BY + value_time, + primary_location_id, + --configuration, + measurement_unit, + variable_name, + """ + else: + fts_clause = f""" + SELECT + * + FROM + joined_timeseries sf + {tqu.filters_to_sql(tcq.filters)} + """ + + query = f""" + WITH fts AS ( + {fts_clause} + ), + mxt AS ( + SELECT + {",".join(tcq.group_by)} + , {tcq.timeseries_name}_value + , value_time + , ROW_NUMBER() OVER( + PARTITION BY {",".join(tcq.group_by)} + ORDER BY {tcq.timeseries_name}_value DESC, value_time + ) as n + FROM fts + ), + chars AS ( + SELECT + {",".join(tcq.group_by)} + ,count(fts.{tcq.timeseries_name}_value) as count + ,min(fts.{tcq.timeseries_name}_value) as min + ,max(fts.{tcq.timeseries_name}_value) as max + ,avg(fts.{tcq.timeseries_name}_value) as average + ,sum(fts.{tcq.timeseries_name}_value) as sum + ,var_pop(fts.{tcq.timeseries_name}_value) as variance + FROM + fts + GROUP BY + {",".join(tcq.group_by)} + ) + SELECT + chars.{tcq.timeseries_name}_location_id as location_id, + chars.count, + chars.min, + chars.max, + chars.average, + chars.sum, + chars.variance, + mxt.value_time as max_value_time + FROM chars + {join_max_time_on} + ORDER BY + {",".join(order_by)} + ;""" + + return query
+ + + +
+[docs] +def create_unique_field_values_query(fn: JoinedTimeseriesFieldName) -> str: + """Create a query for identifying unique values in a field. + + Parameters + ---------- + fn : JoinedTimeseriesFieldName + Name of the field to query for unique values. + + Returns + ------- + str + The query string. + """ + query = f""" + SELECT + DISTINCT + {fn.field_name} + AS unique_{fn.field_name}_values, + FROM + joined_timeseries + ORDER BY + {fn.field_name} + """ + + return query
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/queries/pandas.html b/_modules/teehr/queries/pandas.html new file mode 100644 index 00000000..f1963c78 --- /dev/null +++ b/_modules/teehr/queries/pandas.html @@ -0,0 +1,880 @@ + + + + + + + + + + teehr.queries.pandas — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.queries.pandas

+"""A module for defining SQL queries against Pandas DataFrames."""
+import numpy as np
+import pandas as pd
+import geopandas as gpd
+# import dask.dataframe as dd
+
+from hydrotools.metrics import metrics as hm
+
+from typing import List, Union
+
+import teehr.models.queries as tmq
+import teehr.queries.duckdb as tqu
+
+
+SQL_DATETIME_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+
+
+[docs] +def get_metrics( + primary_filepath: str, + secondary_filepath: str, + crosswalk_filepath: str, + group_by: List[str], + order_by: List[str], + include_metrics: Union[List[tmq.MetricEnum], "all"], + filters: Union[List[dict], None] = None, + return_query: bool = False, + geometry_filepath: Union[str, None] = None, + include_geometry: bool = False, +) -> Union[str, pd.DataFrame, gpd.GeoDataFrame]: + """Calculate performance metrics using a Pandas or Dask DataFrame. + + Parameters + ---------- + primary_filepath : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + secondary_filepath : str + File path to the "forecast" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\\*.parquet". + crosswalk_filepath : str + File path to single crosswalk file. + group_by : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + order_by : List[str] + List of column/field names to order results by. + Must provide at least one. + include_metrics : List[str] + List of metrics (see below) for allowable list, or "all" to return all. + filters : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + return_query : bool = False + True returns the query string instead of the data. + include_geometry : bool = True + True joins the geometry to the query results. + Only works if `primary_location_id` + is included as a group_by field. + + Returns + ------- + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + Notes + ----- + Basic Metrics: + + * primary_count + * secondary_count + * primary_minimum + * secondary_minimum + * primary_maximum + * secondary_maximum + * primary_average + * secondary_average + * primary_sum + * secondary_sum + * primary_variance + * secondary_variance + * max_value_delta + + * max(secondary_value) - max(primary_value) + * bias + + * sum(primary_value - secondary_value)/count(*) + + HydroTools Metrics: + + * nash_sutcliffe_efficiency + * kling_gupta_efficiency + * coefficient_of_extrapolation + * coefficient_of_persistence + * mean_error + * mean_squared_error + * root_mean_squared_error + + Time-based Metrics: + + * primary_max_value_time + * secondary_max_value_time + * max_value_timedelta + """ + mq = tmq.MetricQuery.model_validate( + { + "primary_filepath": primary_filepath, + "secondary_filepath": secondary_filepath, + "crosswalk_filepath": crosswalk_filepath, + "group_by": group_by, + "order_by": order_by, + "include_metrics": include_metrics, + "filters": filters, + "return_query": return_query, + "include_geometry": include_geometry, + "geometry_filepath": geometry_filepath + } + ) + + if mq.return_query: + raise ValueError( + "`return query` is not a valid option " + "for `dataframe.get_metrics()`." + ) + + # This loads all the timeseries in memory + df = tqu.get_joined_timeseries( + primary_filepath=mq.primary_filepath, + secondary_filepath=mq.secondary_filepath, + crosswalk_filepath=mq.crosswalk_filepath, + order_by=mq.order_by, + filters=mq.filters, + return_query=False, + ) + + # Pandas DataFrame GroupBy approach (works). + grouped = df.groupby(mq.group_by, as_index=False, observed=False) + + calculated_metrics = grouped.apply( + calculate_group_metrics, + include_metrics=include_metrics + ) + + # Dask DataFrame GroupBy approach (does not work). + # ddf = dd.from_pandas(df, npartitions=4) + # calculated_metrics = ddf.groupby(mq.group_by).apply( + # calculate_metrics_on_groups, + # metrics=["primary_count"], + # meta={"primary_count": "int"} + # ).compute() + + if mq.include_geometry: + gdf = gpd.read_parquet(mq.geometry_filepath) + merged_gdf = gdf.merge( + calculated_metrics, + left_on="id", + right_on="primary_location_id" + ) + return merged_gdf + + return calculated_metrics
+ + + +
+[docs] +def calculate_group_metrics( + group: pd.DataFrame, + include_metrics: Union[List[str], str] +): + """Calculate metrics on a pd.DataFrame. + + Parameters + ---------- + group : pd.DataFrame + Represents a population group to calculate the metrics on. + include_metrics : List[str] + List of metrics (see below) for allowable list, or "all" to + return all. + + Returns + ------- + pd.DataFrame + A DataFrame of calculated metrics. + + Notes + ----- + This approach to calculating metrics is not as fast as + `teehr.queries.duckdb.get_metrics()` but is easier to update + and contains more metrics. It also serves as the reference + implementation for the duckdb queries. + + Basic Metrics: + + * primary_count + * secondary_count + * primary_minimum + * secondary_minimum + * primary_maximum + * secondary_maximum + * primary_average + * secondary_average + * primary_sum + * secondary_sum + * primary_variance + * secondary_variance + * max_value_delta + + * max(secondary_value) - max(primary_value) + * bias + + * sum(primary_value - secondary_value)/count(*) + + HydroTools Metrics: + + * nash_sutcliffe_efficiency + * kling_gupta_efficiency + * coefficient_of_extrapolation + * coefficient_of_persistence + * mean_error + * mean_squared_error + * root_mean_squared_error + + Time-based Metrics: + * primary_max_value_time + * secondary_max_value_time + * max_value_timedelta + """ + data = {} + + # Simple Metrics + if include_metrics == "all" or "primary_count" in include_metrics: + data["primary_count"] = len(group["primary_value"]) + + if include_metrics == "all" or "secondary_count" in include_metrics: + data["secondary_count"] = len(group["secondary_value"]) + + if include_metrics == "all" or "primary_minimum" in include_metrics: + data["primary_minimum"] = np.min(group["primary_value"]) + + if include_metrics == "all" or "secondary_minimum" in include_metrics: + data["secondary_minimum"] = np.min(group["secondary_value"]) + + if include_metrics == "all" or "primary_maximum" in include_metrics: + data["primary_maximum"] = np.max(group["primary_value"]) + + if include_metrics == "all" or "secondary_maximum" in include_metrics: + data["secondary_maximum"] = np.max(group["secondary_value"]) + + if include_metrics == "all" or "primary_average" in include_metrics: + data["primary_average"] = np.mean(group["primary_value"]) + + if include_metrics == "all" or "secondary_average" in include_metrics: + data["secondary_average"] = np.mean(group["secondary_value"]) + + if include_metrics == "all" or "primary_sum" in include_metrics: + data["primary_sum"] = np.sum(group["primary_value"]) + + if include_metrics == "all" or "secondary_sum" in include_metrics: + data["secondary_sum"] = np.sum(group["secondary_value"]) + + if include_metrics == "all" or "primary_variance" in include_metrics: + data["primary_variance"] = np.var(group["primary_value"]) + + if include_metrics == "all" or "secondary_variance" in include_metrics: + data["secondary_variance"] = np.var(group["secondary_value"]) + + if include_metrics == "all" or "bias" in include_metrics: + group["difference"] = group["secondary_value"] - group["primary_value"] + data["bias"] = np.sum(group["difference"])/len(group) + + if include_metrics == "all" or "max_value_delta" in include_metrics: + data["max_value_delta"] = ( + np.max(group["secondary_value"]) + - np.max(group["primary_value"]) + ) + + # HydroTools Forecast Metrics + if ( + include_metrics == "all" + or "nash_sutcliffe_efficiency" in include_metrics + ): + nse = hm.nash_sutcliffe_efficiency( + group["primary_value"], + group["secondary_value"] + ) + data["nash_sutcliffe_efficiency"] = nse + + if include_metrics == "all" or "kling_gupta_efficiency" in include_metrics: + kge = hm.kling_gupta_efficiency( + group["primary_value"], + group["secondary_value"] + ) + data["kling_gupta_efficiency"] = kge + + if ( + include_metrics == "all" + or "coefficient_of_extrapolation" in include_metrics + ): + coe = hm.coefficient_of_extrapolation( + group["primary_value"], + group["secondary_value"] + ) + data["coefficient_of_extrapolation"] = coe + + if ( + include_metrics == "all" + or "coefficient_of_persistence" in include_metrics + ): + cop = hm.coefficient_of_persistence( + group["primary_value"], + group["secondary_value"] + ) + data["coefficient_of_persistence"] = cop + + if include_metrics == "all" or "mean_error" in include_metrics: + me = hm.mean_error( + group["primary_value"], + group["secondary_value"] + ) + data["mean_error"] = me + + if include_metrics == "all" or "mean_squared_error" in include_metrics: + mse = hm.mean_squared_error( + group["primary_value"], + group["secondary_value"] + ) + data["mean_squared_error"] = mse + + if ( + include_metrics == "all" + or "root_mean_squared_error" in include_metrics + ): + rmse = hm.root_mean_squared_error( + group["primary_value"], + group["secondary_value"] + ) + data["root_mean_squared_error"] = rmse + + # Ensure the first occurrence of a repeated value gets selected + group = group.sort_values(by=["reference_time", "value_time"]) + + # Time-based Metrics + time_indexed_df = group.set_index("value_time") + if ( + include_metrics == "all" + or "primary_max_value_time" in include_metrics + ): + pmvt = time_indexed_df["primary_value"].idxmax() + data["primary_max_value_time"] = pmvt + + if ( + include_metrics == "all" + or "secondary_max_value_time" in include_metrics + ): + smvt = time_indexed_df["secondary_value"].idxmax() + data["secondary_max_value_time"] = smvt + + if ( + include_metrics == "all" + or "max_value_timedelta" in include_metrics + ): + pmvt = time_indexed_df["primary_value"].idxmax() + smvt = time_indexed_df["secondary_value"].idxmax() + data["max_value_timedelta"] = smvt - pmvt + return pd.Series(data)
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/queries/utils.html b/_modules/teehr/queries/utils.html new file mode 100644 index 00000000..ec466411 --- /dev/null +++ b/_modules/teehr/queries/utils.html @@ -0,0 +1,1248 @@ + + + + + + + + + + teehr.queries.utils — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.queries.utils

+"""A module defining common utilities for queries."""
+import pandas as pd
+import geopandas as gpd
+import warnings
+
+from collections.abc import Iterable
+from datetime import datetime
+from typing import List, Union
+
+import teehr.models.queries as tmq
+import teehr.models.queries_database as tmqd
+
+SQL_DATETIME_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+
+
+[docs] +def _get_datetime_list_string(values): + """Get a datetime list as a list of strings.""" + return [f"'{v.strftime(SQL_DATETIME_STR_FORMAT)}'" for v in values]
+ + + +
+[docs] +def _format_iterable_value( + values: Iterable[Union[str, int, float, datetime]] +) -> str: + """Return an SQL formatted string from list of values. + + Parameters + ---------- + values : Iterable + Contains values to be formatted as a string for SQL. Only one type of + value (str, int, float, datetime) should be used. First value in list + is used to determine value type. Values are not checked for type + consistency. + + Returns + ------- + str + An SQL formatted string from list of values. + """ + # string + if isinstance(values[0], str): + return f"""({",".join([f"'{v}'" for v in values])})""" + # int or float + elif isinstance(values[0], int) or isinstance(values[0], float): + return f"""({",".join([f"{v}" for v in values])})""" + # datetime + elif isinstance(values[0], datetime): + return f"""({",".join(_get_datetime_list_string(values))})""" + else: + warnings.warn( + "treating value as string because didn't know what else to do." + ) + return f"""({",".join([f"'{str(v)}'" for v in values])})"""
+ + + +
+[docs] +def _format_filter_item( + filter: Union[tmq.JoinedFilter, tmq.TimeseriesFilter, tmqd.Filter] +) -> str: + """Return an SQL formatted string for single filter object. + + Parameters + ---------- + filter : models.\\*Filter + A single \\*Filter object. + + Returns + ------- + str + An SQL formatted string for single filter object. + """ + column = filter.column + prepend_sf_list = [ + "value_time", + "reference_time", + "configuration", + "measurement_unit", + "variable" + ] + if column in prepend_sf_list: + column = f"sf.{column}" + + if isinstance(filter.value, str): + return f"""{column} {filter.operator} '{filter.value}'""" + elif ( + isinstance(filter.value, int) + or isinstance(filter.value, float) + ): + return f"""{column} {filter.operator} {filter.value}""" + elif isinstance(filter.value, datetime): + dt_str = filter.value.strftime(SQL_DATETIME_STR_FORMAT) + return f"""{column} {filter.operator} '{dt_str}'""" + elif ( + isinstance(filter.value, Iterable) + and not isinstance(filter.value, str) + ): + value = _format_iterable_value(filter.value) + return f"""{column} {filter.operator} {value}""" + else: + warnings.warn( + "treating value as string because didn't know what else to do." + ) + return f"""{column} {filter.operator} '{str(filter.value)}'"""
+ + + +
+[docs] +def filters_to_sql( + filters: Union[List[tmq.JoinedFilter], List[tmqd.Filter]] +) -> List[str]: + """Generate SQL where clause string from filters. + + Parameters + ---------- + filters : Union[List[tmq.JoinedFilter], List[tmqd.Filter]] + A list of Filter objects describing the filters. + + Returns + ------- + str + A where clause formatted string. + """ + if len(filters) > 0: + filter_strs = [] + for f in filters: + filter_strs.append(_format_filter_item(f)) + qry = f"""WHERE {f" AND ".join(filter_strs)}""" + return qry + + return "--no where clause"
+ + + +
+[docs] +def geometry_join_clause( + q: Union[tmq.MetricQuery, tmq.JoinedTimeseriesQuery] +) -> str: + """Generate the join clause for geometry.""" + if q.include_geometry: + return f"""JOIN read_parquet('{str(q.geometry_filepath)}') gf + on pf.location_id = gf.id + """ + return ""
+ + + +
+[docs] +def geometry_select_clause( + q: Union[tmq.MetricQuery, + tmq.JoinedTimeseriesQuery, + tmqd.MetricQuery, + tmqd.JoinedTimeseriesQuery] +) -> str: + """"Generate the geometry select clause.""" + if q.include_geometry: + return ",gf.geometry as geometry" + return ""
+ + + +
+[docs] +def geometry_joined_select_clause( + q: Union[tmq.MetricQuery, tmq.JoinedTimeseriesQuery] +) -> str: + """Generate the geometry select clause for a database.""" + if q.include_geometry: + return ", geometry" + return ""
+ + + +
+[docs] +def metric_geometry_join_clause_db( + q: Union[tmqd.MetricQuery, tmqd.JoinedTimeseriesQuery] +) -> str: + """Generate the metric geometry join clause for a database.""" + if q.include_geometry: + return """JOIN geometry gf + on primary_location_id = gf.id + """ + return ""
+ + + +
+[docs] +def metric_geometry_join_clause( + q: Union[tmq.MetricQuery, tmq.JoinedTimeseriesQuery] +) -> str: + """Generate the metric geometry join clause.""" + if q.include_geometry: + return f"""JOIN read_parquet('{str(q.geometry_filepath)}') gf + on primary_location_id = gf.id + """ + return ""
+ + + +
+[docs] +def _remove_duplicates_jtq_cte( + q: tmq.JoinedTimeseriesQuery +) -> str: + """Generate the remove duplicates CTE for the JoinedTimeseriesQuery.""" + if q.remove_duplicates: + qry = f""" + SELECT + reference_time + , value_time + , secondary_location_id + , secondary_value + , configuration + , measurement_unit + , variable_name + , primary_value + , primary_location_id + , lead_time + {geometry_joined_select_clause(q)} + FROM( + SELECT *, + row_number() + OVER( + PARTITION BY value_time, + primary_location_id, + configuration, + variable_name, + measurement_unit, + reference_time + ORDER BY primary_reference_time desc + ) AS rn + FROM initial_joined + ) + WHERE rn = 1 + """ + else: + qry = f""" + SELECT + reference_time + , value_time + , secondary_location_id + , secondary_value + , configuration + , measurement_unit + , variable_name + , primary_value + , primary_location_id + , lead_time + {geometry_joined_select_clause(q)} + FROM + initial_joined + """ + return qry
+ + + +
+[docs] +def _remove_duplicates_mq_cte( + q: tmq.MetricQuery +) -> str: + """Generate the remove duplicates CTE for the MetricQuery.""" + if q.remove_duplicates: + qry = """ + SELECT + reference_time + , value_time + , secondary_location_id + , secondary_value + , configuration + , measurement_unit + , variable_name + , primary_value + , primary_location_id + , lead_time + , absolute_difference + FROM( + SELECT *, + row_number() + OVER( + PARTITION BY value_time, + primary_location_id, + configuration, + variable_name, + measurement_unit, + reference_time + ORDER BY primary_reference_time desc + ) AS rn + FROM initial_joined + ) + WHERE rn = 1 + """ + else: + qry = """ + SELECT + reference_time + , value_time + , secondary_location_id + , secondary_value + , configuration + , measurement_unit + , variable_name + , primary_value + , primary_location_id + , lead_time + , absolute_difference + FROM + initial_joined + """ + return qry
+ + + +
+[docs] +def _join_time_on(join: str, join_to: str, join_on: List[str]): + """Generate the join time on query.""" + qry = f""" + INNER JOIN {join} + ON {f" AND ".join([f"{join}.{jo} = {join_to}.{jo}" for jo in join_on])} + AND {join}.n = 1 + """ + return qry
+ + + +
+[docs] +def _join_on(join: str, join_to: str, join_on: List[str]) -> str: + """Generate the join on query.""" + qry = f""" + INNER JOIN {join} + ON {f" AND ".join([f"{join}.{jo} = {join_to}.{jo}" for jo in join_on])} + """ + return qry
+ + + +
+[docs] +def _nse_cte(mq: Union[tmq.MetricQuery, tmqd.MetricQuery]) -> str: + """Generate the nash-sutcliffe-efficiency CTE.""" + if ( + "nash_sutcliffe_efficiency" in mq.include_metrics + or mq.include_metrics == "all" + ): + return f""" + ,nse AS ( + SELECT + {",".join(mq.group_by)} + ,AVG(primary_value) AS avg_primary_value + FROM + joined + GROUP BY + {",".join(mq.group_by)} + ) + """ + return ""
+ + + +
+[docs] +def _join_nse_cte(mq: Union[tmq.MetricQuery, tmqd.MetricQuery]) -> str: + """Generate the join nash-sutcliffe-efficiency CTE.""" + if ( + "nash_sutcliffe_efficiency" in mq.include_metrics + or mq.include_metrics == "all" + ): + return f""" + {_join_on(join="nse", join_to="joined", join_on=mq.group_by)} + """ + return ""
+ + + +
+[docs] +def _select_max_value_timedelta( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select max value timedelta query segment.""" + if ( + "max_value_timedelta" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, arg_max( + joined.value_time, + secondary_value ORDER BY joined.value_time ASC + ) + - arg_max( + joined.value_time, + primary_value ORDER BY joined.value_time ASC + ) as max_value_timedelta""" + return ""
+ + + +
+[docs] +def _select_secondary_max_value_time( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select secondary max value time query segment.""" + if ( + "secondary_max_value_time" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, arg_max( + joined.value_time, + secondary_value ORDER BY joined.value_time ASC + ) as secondary_max_value_time""" + return ""
+ + + +
+[docs] +def _select_primary_max_value_time( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select primary max value time query segment.""" + if ( + "primary_max_value_time" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, arg_max( + joined.value_time, + primary_value ORDER BY joined.value_time ASC + ) as primary_max_value_time""" + return ""
+ + + +
+[docs] +def _select_root_mean_squared_error( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select root mean squared error query segment.""" + if ( + "root_mean_squared_error" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, sqrt(sum(power(absolute_difference, 2))/count(*)) + as root_mean_squared_error + """ + return ""
+ + + +
+[docs] +def _select_mean_squared_error( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select mean squared error query segment.""" + if ( + "mean_squared_error" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, sum(power(absolute_difference, 2))/count(*) + as mean_squared_error + """ + return ""
+ + + +
+[docs] +def _select_mean_error(mq: Union[tmq.MetricQuery, tmqd.MetricQuery]) -> str: + """Generate the select mean error query segment.""" + if "mean_error" in mq.include_metrics or mq.include_metrics == "all": + return """, sum(absolute_difference)/count(*) as mean_error""" + return ""
+ + + +
+[docs] +def _select_kling_gupta_efficiency( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select kling gupta efficiency query segment.""" + if ( + "kling_gupta_efficiency" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, 1 - sqrt( + pow(corr(secondary_value, primary_value) - 1, 2) + + pow(stddev(secondary_value) + / stddev(primary_value) - 1, 2) + + pow(avg(secondary_value) / avg(primary_value) - 1, 2) + ) as kling_gupta_efficiency + """ + return ""
+ + + +
+[docs] +def _select_nash_sutcliffe_efficiency( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select nash sutcliffe efficiency query segment.""" + if ( + "nash_sutcliffe_efficiency" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, 1 - ( + sum(pow(joined.primary_value - joined.secondary_value, 2)) + / sum(pow(joined.primary_value - nse.avg_primary_value, 2)) + ) as nash_sutcliffe_efficiency + """ + return ""
+ + + +
+[docs] +def _select_bias(mq: Union[tmq.MetricQuery, tmqd.MetricQuery]) -> str: + """Generate the select bias query segment.""" + if "bias" in mq.include_metrics or mq.include_metrics == "all": + return """, sum(secondary_value - primary_value)/count(*) as bias""" + return ""
+ + + +
+[docs] +def _select_max_value_delta( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select max value delta query segment.""" + if "max_value_delta" in mq.include_metrics or mq.include_metrics == "all": + return """, max(secondary_value) - max(primary_value) + as max_value_delta + """ + return ""
+ + + +
+[docs] +def _select_primary_count(mq: Union[tmq.MetricQuery, tmqd.MetricQuery]) -> str: + """Generate the select primary count query segment.""" + if "primary_count" in mq.include_metrics or mq.include_metrics == "all": + return """, count(primary_value) as primary_count""" + return ""
+ + + +
+[docs] +def _select_secondary_count( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select secondary count query segment.""" + if "secondary_count" in mq.include_metrics or mq.include_metrics == "all": + return """, count(secondary_value) as secondary_count""" + return ""
+ + + +
+[docs] +def _select_primary_minimum( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select primary minimum query segment.""" + if "primary_minimum" in mq.include_metrics or mq.include_metrics == "all": + return """, min(primary_value) as primary_minimum""" + return ""
+ + + +
+[docs] +def _select_secondary_minimum( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select secondary minimum query segment.""" + if ( + "secondary_minimum" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, min(secondary_value) as secondary_minimum""" + return ""
+ + + +
+[docs] +def _select_primary_maximum( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select primary maximum query segment.""" + if "primary_maximum" in mq.include_metrics or mq.include_metrics == "all": + return """, max(primary_value) as primary_maximum""" + return ""
+ + + +
+[docs] +def _select_secondary_maximum( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select secondary maximum query segment.""" + if ( + "secondary_maximum" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, max(secondary_value) as secondary_maximum""" + return ""
+ + + +
+[docs] +def _select_primary_average( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select primary average query segment.""" + if "primary_average" in mq.include_metrics or mq.include_metrics == "all": + return """, avg(primary_value) as primary_average""" + return ""
+ + + +
+[docs] +def _select_secondary_average( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select secondary average query segment.""" + if ( + "secondary_average" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, avg(secondary_value) as secondary_average""" + return ""
+ + + +
+[docs] +def _select_primary_sum(mq: Union[tmq.MetricQuery, tmqd.MetricQuery]) -> str: + """Generate the select primary sum query segment.""" + if "primary_sum" in mq.include_metrics or mq.include_metrics == "all": + return """, sum(primary_value) as primary_sum""" + return ""
+ + + +
+[docs] +def _select_secondary_sum(mq: Union[tmq.MetricQuery, tmqd.MetricQuery]) -> str: + """Generate the select secondary sum query segment.""" + if "secondary_sum" in mq.include_metrics or mq.include_metrics == "all": + return """, sum(secondary_value) as secondary_sum""" + return ""
+ + + +
+[docs] +def _select_primary_variance( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select primary variance query segment.""" + if "primary_variance" in mq.include_metrics or mq.include_metrics == "all": + return """, var_pop(primary_value) as primary_variance""" + return ""
+ + + +
+[docs] +def _select_secondary_variance( + mq: Union[tmq.MetricQuery, tmqd.MetricQuery] +) -> str: + """Generate the select secondary variance query segment.""" + if ( + "secondary_variance" in mq.include_metrics + or mq.include_metrics == "all" + ): + return """, var_pop(secondary_value) as secondary_variance""" + return ""
+ + + +
+[docs] +def df_to_gdf(df: pd.DataFrame) -> gpd.GeoDataFrame: + """Convert pd.DataFrame to gpd.GeoDataFrame. + + When the `geometry` column is read from a parquet file using DuckBD + it is a bytearray in the resulting pd.DataFrame. The `geometry` needs + to be convert to bytes before GeoPandas can work with it. This function + does that. + + Parameters + ---------- + df : pd.DataFrame + DataFrame with a `geometry` column that has geometry stored as + a bytearray. + + Returns + ------- + gpd.GeoDataFrame + GeoDataFrame with a valid `geometry` column. + """ + df["geometry"] = gpd.GeoSeries.from_wkb( + df["geometry"].apply(lambda x: bytes(x)) + ) + return gpd.GeoDataFrame(df, crs="EPSG:4326", geometry="geometry")
+ + + +
+[docs] +def remove_empty_lines(text: str) -> str: + """Remove empty lines from string.""" + return "".join([s for s in text.splitlines(True) if s.strip()])
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_modules/teehr/utilities/generate_weights.html b/_modules/teehr/utilities/generate_weights.html new file mode 100644 index 00000000..6e8a66fd --- /dev/null +++ b/_modules/teehr/utilities/generate_weights.html @@ -0,0 +1,819 @@ + + + + + + + + + + teehr.utilities.generate_weights — TEEHR: Tools for Exploratory Evaluation in Hydrologic Research 0.3.2 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for teehr.utilities.generate_weights

+"""Module for generating area-based weights for grid layer pixels."""
+from typing import Union, Dict
+from pathlib import Path
+import warnings
+
+import geopandas as gpd
+import numpy as np
+import xarray as xr
+from rasterio.transform import rowcol
+import rasterio
+import pandas as pd
+import dask
+import shapely
+
+from teehr.loading.nwm.utils import load_gdf
+# from teehr.loading.nwm.const import AL_NWM_WKT
+
+
+@dask.delayed
+def vectorize(data_array: xr.DataArray) -> gpd.GeoDataFrame:
+    """
+    Convert 2D xarray.DataArray into a geopandas.GeoDataFrame.
+
+    Notes
+    -----
+    Heavily borrowed from GeoCube, see:
+    https://github.com/corteva/geocube/blob/master/geocube/vector.py#L12
+    """
+    # nodata mask
+    mask = None
+    if np.isnan(data_array.rio.nodata):
+        mask = ~data_array.isnull()
+    elif data_array.rio.nodata is not None:
+        mask = data_array != data_array.rio.nodata
+
+    # Give all pixels a unique value
+    data_array.values[:, :] = np.arange(0, data_array.values.size).reshape(
+        data_array.shape
+    )
+
+    # vectorize generator
+    vectorized_data = (
+        (value, shapely.geometry.shape(polygon))
+        for polygon, value in rasterio.features.shapes(
+            data_array,
+            transform=data_array.rio.transform(),
+            mask=mask,
+        )
+    )
+    gdf = gpd.GeoDataFrame(
+        vectorized_data,
+        columns=[data_array.name, "geometry"],
+        crs=data_array.rio.crs,
+    )
+    xx, yy = np.meshgrid(data_array.x.values, data_array.y.values)
+    gdf["x"] = xx.ravel()
+    gdf["y"] = yy.ravel()
+
+    return gdf
+
+
+@dask.delayed
+def overlay_zones(
+    grid: gpd.GeoDataFrame, zones: gpd.GeoDataFrame
+) -> gpd.GeoDataFrame:
+    """Overlay zone polygons on vectorized grid cells."""
+    with pd.option_context(
+        "mode.chained_assignment", None
+    ):  # to ignore setwithcopywarning
+        grid.loc[:, "pixel_area"] = grid.geometry.area
+        overlay_gdf = grid.overlay(zones, keep_geom_type=True)
+        overlay_gdf.loc[:, "overlay_area"] = overlay_gdf.geometry.area
+        overlay_gdf.loc[:, "weight"] = (
+            overlay_gdf.overlay_area / overlay_gdf.pixel_area
+        )
+    return overlay_gdf
+
+
+
+[docs] +def vectorize_grid( + src_da: xr.DataArray, + nodata_val: float, + crs_wkt: str, + vectorize_chunk: float = 40, +) -> gpd.GeoDataFrame: + """Vectorize pixels in the template array in chunks using dask. + + Notes + ----- + Parameter vectorize_chunk determines how many pixels will + be vectorized at one time + (thousands of pixels) + """ + src_da = src_da.persist() + max_pixels = vectorize_chunk * 1000 + num_splits = np.ceil(src_da.values.size / max_pixels).astype(int) + + # Prepare each data array + if num_splits > 0: + da_list = np.array_split(src_da, num_splits) + [da.rio.write_nodata(nodata_val, inplace=True) for da in da_list] + else: + src_da.rio.write_nodata(nodata_val, inplace=True) + da_list = [src_da] + + results = [] + for da_subset in da_list: + results.append(vectorize(da_subset)) + grid_gdf = pd.concat(dask.compute(results)[0]) + grid_gdf.crs = crs_wkt + + # Reindex to remove duplicates + grid_gdf["index"] = np.arange(len(grid_gdf.index)) + grid_gdf.set_index("index", inplace=True) + + return grid_gdf
+ + + +
+[docs] +def calculate_weights( + grid_gdf: gpd.GeoDataFrame, + zone_gdf: gpd.GeoDataFrame, + overlay_chunk: float = 250, +) -> gpd.GeoDataFrame: + """Overlay vectorized pixels and zone polygons, and calculate + areal weights, returning a geodataframe. + + Notes + ----- + Parameter overlay_chunk determines the size of the rectangular + window that spatially subsets datasets for the operation + (thousands of pixels). + """ + # Make sure geometries are valid + grid_gdf["geometry"] = grid_gdf.geometry.make_valid() + zone_gdf["geometry"] = zone_gdf.geometry.make_valid() + + xmin, ymin, xmax, ymax = zone_gdf.total_bounds + + x_steps = np.arange(xmin, xmax, overlay_chunk * 1000) + y_steps = np.arange(ymin, ymax, overlay_chunk * 1000) + + x_steps = np.append(x_steps, xmax) + y_steps = np.append(y_steps, ymax) + + results = [] + for i in range(x_steps.size - 1): + for j in range(y_steps.size - 1): + xmin = x_steps[i] + xmax = x_steps[i + 1] + + ymin = y_steps[j] + ymax = y_steps[j + 1] + + zone = zone_gdf.cx[xmin:xmax, ymin:ymax] + grid = grid_gdf.cx[xmin:xmax, ymin:ymax] + + if len(zone.index) == 0 or len(grid.index) == 0: + continue + results.append(overlay_zones(grid, zone)) + + overlay_gdf = pd.concat(dask.compute(results)[0]) + + return overlay_gdf
+ + + +
+[docs] +def generate_weights_file( + zone_polygon_filepath: Union[Path, str], + template_dataset: Union[str, Path], + variable_name: str, + output_weights_filepath: Union[str, Path], + crs_wkt: str, + unique_zone_id: str = None, + **read_args: Dict, +) -> None: + """Generate a file of row/col indices and weights for pixels intersecting + given zone polyons. + + Parameters + ---------- + zone_polygon_filepath : str + Path to the polygons geoparquet file. + template_dataset : str + Path to the grid dataset to use as a template. + variable_name : str + Name of the variable within the dataset. + output_weights_filepath : str + Path to the resultant weights file. + crs_wkt : str + Coordinate system for given domain as WKT string. + unique_zone_id : str + Name of the field in the zone polygon file containing unique IDs. + **read_args : dict, optional + Keyword arguments to be passed to GeoPandas read_file(). + read_parquet(), and read_feather() methods. + + Examples + -------- + Here we generate weights for grid pixels intersecting a given + polygon(s). The algorithm accounts for the fraction of the pixel + area that is within the polygon. We'll use the Nextgen divides/ + catchments as the polygons and a NWM v2.2 forcing file as the + template grid. + + Import the necessary modules. + + >>> from teehr.utilities.generate_weights import generate_weights_file + >>> from teehr.loading.nwm.const import CONUS_NWM_WKT + + Define the input variables. + + >>> TEST_DIR = Path("tests", "data", "nwm22") + >>> TEMP_DIR = Path("tests", "data", "temp") + >>> TEMPLATE_FILEPATH = Path(TEST_DIR, "test_template_grid.nc") + >>> ZONES_FILEPATH = Path(TEST_DIR, "test_ngen_divides.parquet") + >>> WEIGHTS_FILEPATH = Path(TEST_DIR, "test_weights_results.parquet") + + Perform the calculation, writing to the output directory, or optionally + returning the dataframe if no output path is specified. + + >>> df = generate_weights_file( + >>> zone_polygon_filepath=ZONES_FILEPATH, + >>> template_dataset=TEMPLATE_FILEPATH, + >>> variable_name="RAINRATE", + >>> crs_wkt=CONUS_NWM_WKT, + >>> output_weights_filepath=None, + >>> unique_zone_id="id", + >>> ) + """ + + zone_gdf = load_gdf(zone_polygon_filepath, **read_args) + zone_gdf = zone_gdf.to_crs(crs_wkt) + + ds = xr.open_dataset(template_dataset) + src_da = ds[variable_name] + src_da = src_da.rio.write_crs(crs_wkt, inplace=True) + grid_transform = src_da.rio.transform() + nodata_val = src_da.rio.nodata + + # Get the subset of the grid that intersects the total zone bounds + bbox = tuple(zone_gdf.total_bounds) + src_da = src_da.sel(x=slice(bbox[0], bbox[2]), y=slice(bbox[1], bbox[3]))[ + 0 + ] + src_da = src_da.astype("float32") + src_da["x"] = np.float32(src_da.x.values) + src_da["y"] = np.float32(src_da.y.values) + + # Vectorize source grid pixels + grid_gdf = vectorize_grid(src_da, nodata_val, crs_wkt) + + # Overlay and calculate areal weights of pixels within each zone + # Note: Temporarily suppress the dask UserWarning: "Large object detected + # in task graph" until a better approach is found + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + weights_gdf = calculate_weights(grid_gdf, zone_gdf) + weights_gdf = weights_gdf.drop_duplicates( + subset=["x", "y", unique_zone_id] + ) + + # Convert x-y to row-col using original transform + rows, cols = rowcol( + grid_transform, weights_gdf.x.values, weights_gdf.y.values + ) + weights_gdf["row"] = rows + weights_gdf["col"] = cols + + if unique_zone_id: + df = weights_gdf[["row", "col", "weight", unique_zone_id]].copy() + df.rename(columns={unique_zone_id: "location_id"}, inplace=True) + else: + df = weights_gdf[["row", "col", "weight"]] + df["location_id"] = weights_gdf.index.values + + if output_weights_filepath: + df.to_parquet(output_weights_filepath) + df = None + + return df
+ + + +# if __name__ == "__main__": +# # Local testing +# zone_polygon_filepath = "/mnt/data/wbd/one_alaska_huc10.parquet" +# template_dataset = "/mnt/data/ciroh/nwm_temp/nwm.20231101_forcing_analysis_assim_alaska_nwm.t00z.analysis_assim.forcing.tm01.alaska.nc" # noqa +# variable_name = "RAINRATE" +# unique_zone_id = "huc10" +# output_weights_filepath = ( +# "/mnt/sf_shared/data/ciroh/one_huc10_alaska_weights.parquet" +# ) + +# generate_weights_file( +# zone_polygon_filepath=zone_polygon_filepath, +# template_dataset=template_dataset, +# variable_name=variable_name, +# output_weights_filepath=output_weights_filepath, +# crs_wkt=AL_NWM_WKT, +# unique_zone_id=unique_zone_id +# ) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/_sources/autoapi/index.rst.txt b/_sources/autoapi/index.rst.txt new file mode 100644 index 00000000..f3f84b89 --- /dev/null +++ b/_sources/autoapi/index.rst.txt @@ -0,0 +1,14 @@ +.. _autoapi: + +API Reference +============= + +This page contains auto-generated API reference documentation [#f1]_. + +.. toctree:: + :titlesonly: + :maxdepth: 3 + + /autoapi/teehr/index + +.. [#f1] Created with `sphinx-autoapi `_ \ No newline at end of file diff --git a/_sources/autoapi/teehr/api/index.rst.txt b/_sources/autoapi/teehr/api/index.rst.txt new file mode 100644 index 00000000..372a21c9 --- /dev/null +++ b/_sources/autoapi/teehr/api/index.rst.txt @@ -0,0 +1,38 @@ +:py:mod:`teehr.api` +=================== + +.. py:module:: teehr.api + +.. autoapi-nested-parse:: + + + TEEHR API init. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + main/index.rst + + diff --git a/_sources/autoapi/teehr/api/main/index.rst.txt b/_sources/autoapi/teehr/api/main/index.rst.txt new file mode 100644 index 00000000..b4ee9b5f --- /dev/null +++ b/_sources/autoapi/teehr/api/main/index.rst.txt @@ -0,0 +1,405 @@ +:py:mod:`teehr.api.main` +======================== + +.. py:module:: teehr.api.main + +.. autoapi-nested-parse:: + + Module for database queries via FastAPI. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.api.main.format_response + teehr.api.main.read_root + teehr.api.main.get_datasets + teehr.api.main.read_dataset_by_id + teehr.api.main.get_metric_fields + teehr.api.main.get_data_fields + teehr.api.main.get_filter_operators + teehr.api.main.get_metrics_by_query + teehr.api.main.get_timeseries_by_query + teehr.api.main.get_timeseries_chars_by_query + teehr.api.main.get_unique_field_vals + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.api.main.app + teehr.api.main.origins + teehr.api.main.datasets + + +.. py:data:: app + + + +.. py:data:: origins + :value: ['http://localhost', 'http://localhost:5173', 'http://localhost:8000'] + + + +.. py:data:: datasets + + + +.. py:function:: format_response(df: Union[geopandas.GeoDataFrame, pandas.DataFrame]) -> List[Dict] + + + Convert the query response format from the dataframe to a dict. + + + :Parameters: + + **df** : Union[gpd.GeoDataFrame, pd.DataFrame] + Dataframe or Geodataframe. + + :Returns: + + List[Dict] + A list of dictionaries. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: read_root() + + + Root message. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_datasets() + :async: + + + Get datasets. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: read_dataset_by_id(dataset_id: str) -> Dict + :async: + + + Get a dataset by its ID. + + + :Parameters: + + **dataset_id** : str + Dataset ID. + + :Returns: + + Dict + Dataset filepaths. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_metric_fields(dataset_id: str) -> List[str] + :async: + + + Get metric fields. + + + :Parameters: + + **dataset_id** : str + Dataset ID. + + :Returns: + + List + List of available metrics. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_data_fields(dataset_id: str) -> List[Dict] + :async: + + + Get data fields and datatypes. + + + :Parameters: + + **dataset_id** : str + Dataset ID. + + :Returns: + + List + A list of dictionaries of database fields and their types. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_filter_operators(dataset_id: str) -> Dict + :async: + + + Get filter operators. + + + :Parameters: + + **dataset_id** : str + Dataset ID. + + :Returns: + + Dict + A dictionary of filter operator keywords and their symbols. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_metrics_by_query(dataset_id: str, api_metrics_query: teehr.models.queries_database.MetricQuery) -> List[Dict] + :async: + + + Get metrics by query. + + + :Parameters: + + **dataset_id** : str + Dataset ID. + + **api_metrics_query** : MetricQuery + Pydantic model of query variables. + + :Returns: + + List[Dict] + A list of dictionaries of query results. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_timeseries_by_query(dataset_id: str, api_timeseries_query: teehr.models.queries_database.TimeseriesQuery) -> List[Dict] + :async: + + + Get timeseries by query. + + + :Parameters: + + **dataset_id** : str + Dataset ID. + + **api_timeseries_query** : TimeseriesQuery + Pydantic model of query variables. + + :Returns: + + List[Dict] + A list of dictionaries of query results. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_timeseries_chars_by_query(dataset_id: str, api_timeseries_char_query: teehr.models.queries_database.TimeseriesCharQuery) -> List[Dict] + :async: + + + Get time series characteristics by query. + + + :Parameters: + + **dataset_id** : str + Dataset ID. + + **api_timeseries_char_query** : TimeseriesCharQuery + Pydantic model of query variables. + + :Returns: + + List[Dict] + A list of dictionaries of query results. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_unique_field_vals(dataset_id: str, api_field_name: teehr.models.queries_database.JoinedTimeseriesFieldName) -> List[Dict] + :async: + + + Get unique field names in the joined_timeseries table. + + + :Parameters: + + **dataset_id** : str + Dataset ID. + + **api_field_name** : JoinedTimeseriesFieldName + Pydantic model of query variables. + + :Returns: + + List[Dict] + Pydantic model of query variables. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/database/index.rst.txt b/_sources/autoapi/teehr/database/index.rst.txt new file mode 100644 index 00000000..31c652ff --- /dev/null +++ b/_sources/autoapi/teehr/database/index.rst.txt @@ -0,0 +1,38 @@ +:py:mod:`teehr.database` +======================== + +.. py:module:: teehr.database + +.. autoapi-nested-parse:: + + + TEEHR database init. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + teehr_dataset/index.rst + + diff --git a/_sources/autoapi/teehr/database/teehr_dataset/index.rst.txt b/_sources/autoapi/teehr/database/teehr_dataset/index.rst.txt new file mode 100644 index 00000000..332f25bf --- /dev/null +++ b/_sources/autoapi/teehr/database/teehr_dataset/index.rst.txt @@ -0,0 +1,1094 @@ +:py:mod:`teehr.database.teehr_dataset` +====================================== + +.. py:module:: teehr.database.teehr_dataset + +.. autoapi-nested-parse:: + + Defines the TEEHR dataset class and pre-processing methods. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + teehr.database.teehr_dataset.TEEHRDatasetAPI + teehr.database.teehr_dataset.TEEHRDatasetDB + + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.database.teehr_dataset.logger + + +.. py:data:: logger + + + +.. py:class:: TEEHRDatasetAPI(database_filepath: Union[str, pathlib.Path]) + + + + Create an instance of a TEEHRDataset class for API-based queries and initialize a study area database. + + + + + + + + + + + + + + + + .. rubric:: Methods + + + + ================================================================================================= ========== + **__init__(database_filepath: Union[str, Path])** Establish a connection to an existing study area database. + **profile_query(query: str)** A helper function to profile query performance (runs EXPLAIN ANALYZE). + **query(query: str, format: str = None, create_function_args: Dict = None)** Submit an SQL query string against the database. + **get_joined_timeseries_schema()** Get field names and field data types from the joined_timeseries, table as a pandas dataframe. + **describe_inputs(primary_filepath: Union[str, Path], secondary_filepath: Union[str, Path])** Get descriptive statistics on the primary and secondary timeseries by reading the parquet files as a pandas dataframe. + **get_metrics(mq: MetricQuery)** Calculate performance metrics using database queries. + **get_timeseries(tq: TimeseriesQuery)** Retrieve timeseries using a database query. + **get_timeseries_chars(tcq: TimeseriesCharQuery)** Retrieve timeseries characteristics using database query. + **get_unique_field_values(fn: JoinedTimeseriesFieldName)** Get unique values for a given field as a pandas dataframe. + ================================================================================================= ========== + + .. + !! processed by numpydoc !! + .. py:method:: profile_query(query: str) + + + A helper function to profile query performance, + which runs EXPLAIN ANALYZE and prints the output to screen. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: query(query: str, format: str = None) + + + Run an SQL query against the class's database. + + Return formats include: + + * A pandas dataframe (format='df') + * Results bprinted to the screen (format='raw') + * A DuckDBPyRelation, a symbolic representation of the SQL query + (format='relation'). + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_joined_timeseries_schema() -> pandas.DataFrame + + + Get field names and field data types from the joined_timeseries table. + + + + :Returns: + + pd.DataFrame + Includes column_name, column_type, null, key, default, + and extra columns. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _sanitize_field_name(field_name: str) -> str + :staticmethod: + + + Replace unallowed characters from user-defined field names with an + underscore. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: describe_inputs(primary_filepath: Union[str, pathlib.Path], secondary_filepath: Union[str, pathlib.Path]) -> pandas.DataFrame + :staticmethod: + + + Get descriptive statistics on the primary and secondary + timeseries by reading the parquet files. + + + :Parameters: + + **primary_filepath** : Union[str, Path] + Path to the primary time series parquet file. + + **secondary_filepath** : Union[str, Path] + Path to the primary time series parquet file. + + :Returns: + + pd.DataFrame + A dataframe of descriptive statistics for each time series. + + Output includes: + + * Number of unique location IDs + * Total number of rows + * Start date + * End date + * Number of duplicate rows + * Number of location IDs with duplicate value times + * Number of location IDs with missing time steps + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _check_if_geometry_is_inserted() + + + Make sure the geometry table is not empty. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _validate_query_model(query_model: Any) -> Any + + + Validate pydantic query models based on the existing fields + in the joined_timeseries table. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_metrics(mq: teehr.models.queries_database.MetricQuery) -> Union[pandas.DataFrame, geopandas.GeoDataFrame, str] + + + Calculate performance metrics using database queries. + + + :Parameters: + + **mq** : MetricQuery + Pydantic model containing query parameters. + + :Returns: + + Union[pd.DataFrame, gpd.GeoDataFrame, str] + A DataFrame or optionally a GeoDataFrame containing query results, + or the query itself as a string. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_get_metrics_query` + Create the get metrics query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_joined_timeseries(jtq: teehr.models.queries_database.JoinedTimeseriesQuery) -> Union[pandas.DataFrame, geopandas.GeoDataFrame, str] + + + Retrieve joined timeseries using database query. + + + :Parameters: + + **jtq** : JoinedTimeseriesQuery + Pydantic model containing query parameters. + + :Returns: + + Union[pd.DataFrame, gpd.GeoDataFrame, str] + A DataFrame or GeoDataFrame of query results + or the query itself as a string. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_get_joined_timeseries_query` + Create the get joined timeseries query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_timeseries(tq: teehr.models.queries_database.TimeseriesQuery) -> Union[pandas.DataFrame, str] + + + Retrieve timeseries using database query. + + + :Parameters: + + **tq** : TimeseriesQuery + Pydantic model containing query parameters. + + :Returns: + + Union[pd.DataFrame, str] + A DataFrame of query results or the query itself as a string. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_get_timeseries_query` + Create the get timeseries query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_timeseries_chars(tcq: teehr.models.queries_database.TimeseriesCharQuery) -> Union[str, pandas.DataFrame] + + + Retrieve timeseries characteristics using database query. + + + :Parameters: + + **tcq** : TimeseriesCharQuery + Pydantic model containing query parameters. + + :Returns: + + Union[str, pd.DataFrame] + A DataFrame of time series characteristics including: + + - location_id + - count + - min + - max + - average + - sum + - variance + + or, the query itself as a string. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_get_timeseries_char_query` + Create the get timeseries characteristics query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_unique_field_values(fn: teehr.models.queries_database.JoinedTimeseriesFieldName) -> pandas.DataFrame + + + Get unique values for a given field. + + + :Parameters: + + **fn** : JoinedTimeseriesFieldName + Pydantic model containing the joined_timeseries table field name. + + :Returns: + + pd.DataFrame + A dataframe containing unique values for the given field. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_unique_field_values_query` + Create the get unique field values query. + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: TEEHRDatasetDB(database_filepath: Union[str, pathlib.Path]) + + + Bases: :py:obj:`TEEHRDatasetAPI` + + + Extends TEEHRDatasetAPI class with additional functionality for local database-based queries. + + + + + + + + + + + + + + + + .. rubric:: Methods + + + + ===================================================================================================================================================================================================================================================================================================================== ========== + **insert_geometry(geometry_filepath: Union[str, Path])** Inserts geometry from a parquet file into a separate database table named 'geometry'. + **insert_joined_timeseries(primary_filepath: Union[str, Path], secondary_filepath: Union[str, Path], crosswalk_filepath: Union[str, Path], order_by: List[str] = [ "reference_time", "primary_location_id", ], drop_added_fields=False, )** Joins the primary and secondary timeseries from parquet files and inserts into the database as the joined_timeseries table. + **join_attributes(attributes_filepath: Union[str, Path])** Joins attributes from the provided attribute table(s) to new fields in the joined_timeseries table + **calculate_field(new_field_name: str, new_field_type: str, parameter_names: List[str], user_defined_function: Callable, replace: bool = True, )** Calculate a new field in joined_timeseries based on existing fields and a user-defined function. + **profile_query(query: str)** A helper function to profile query performance (runs EXPLAIN ANALYZE). Inherited from TEEHRDatasetAPI. + **query(query: str, format: str = None, create_function_args: Dict = None)** Submit an SQL query string against the database. Inherited from TEEHRDatasetAPI. + **get_joined_timeseries_schema() -> pd.DataFrame** Get field names and field data types from the joined_timeseries, table as a pandas dataframe. Inherited from TEEHRDatasetAPI. + **describe_inputs( primary_filepath: Union[str, Path], secondary_filepath: Union[str, Path] ) -> pd.DataFrame** Get descriptive statistics on the primary and secondary timeseries by reading the parquet files as a pandas dataframe. Inherited from TEEHRDatasetAPI. + **get_metrics( group_by: List[str], order_by: List[str], include_metrics: Union[List[MetricEnum], "all"], filters: Union[List[dict], None] = None, include_geometry: bool = False, return_query: bool = False, ) -> Union[str, pd.DataFrame, gpd.GeoDataFrame]:** Calculate performance metrics using database queries Overrides TEEHRDatasetAPI. + **get_timeseries( order_by: List[str], timeseries_name: str, filters: Union[List[dict], None] = None, return_query: bool = False, ) -> Union[pd.DataFrame, str] Retrieve timeseries using a database query.** Overrides TEEHRDatasetAPI. + **get_timeseries_chars( group_by: List[str], order_by: List[str], timeseries_name: str, filters: Union[List[dict], None] = None, return_query: bool = False, ) -> Union[str, pd.DataFrame]** Retrieve timeseries characteristics using database query Overrides TEEHRDatasetAPI. + **get_unique_field_values(fn: JoinedTimeseriesFieldName) -> pd.DataFrame** Get unique values for a given field as a pandas dataframe. Overrides TEEHRDatasetAPI. Use get_joined_timeseries_schema() to see existing table fields. + ===================================================================================================================================================================================================================================================================================================================== ========== + + .. + !! processed by numpydoc !! + .. py:method:: query(query: str, read_only: bool = False, format: str = None, create_function_args: Dict = None) + + + Run query against the class's database. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _initialize_database_tables() + + + Create the persistent study database and empty table(s). + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _drop_joined_timeseries_field(field_name: str) + + + Drop the specified field by name from joined_timeseries table. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _validate_joined_timeseries_base_fields(drop_added_fields: bool) + + + Ensure that no user-defined fields have been added or base fields + have been dropped. This is necessary in order to add multiple + configurations into the joined_timeseries table. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: insert_geometry(geometry_filepath: Union[str, pathlib.Path]) + + + Insert geometry from a parquet file into a separate + database table named 'geometry'. + + + :Parameters: + + **geometry_filepath** : Union[str, Path] + Path to the geometry file. + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: insert_joined_timeseries(primary_filepath: Union[str, pathlib.Path], secondary_filepath: Union[str, pathlib.Path], crosswalk_filepath: Union[str, pathlib.Path], order_by: List[str] = ['primary_location_id', 'configuration', 'variable_name', 'measurement_unit', 'value_time'], drop_added_fields=False) + + + Join the primary and secondary timeseries read from parquet files + and inserts into the database as the joined_timeseries table. + + + :Parameters: + + **primary_filepath** : Union[str, Path] + File path to the "observed" data. String must include path to + file(s) and can include wildcards. For example, + "/path/to/parquet/\*.parquet". + + **secondary_filepath** : Union[str, Path] + File path to the "forecast" data. String must include path to + file(s) and can include wildcards. For example, + "/path/to/parquet/\*.parquet". + + **crosswalk_filepath** : Union[str, Path] + File path to single crosswalk file. + + **order_by** : List[str], optional + List of column/field names to order results by, + by default ["reference_time", "primary_location_id"]. + + **drop_added_fields** : bool, optional + A flag to determine whether to drop any user-defined fields that + have been added to the table (True), or raise an error if added + fields exist (False). By default False. + + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_join_and_save_timeseries_query` + Create the join and save timeseries query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _get_unique_attributes(attributes_filepath: str) -> List + + + Get a list of unique attributes and attribute units from the + provided attribute table(s). + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _add_field_name_to_joined_timeseries(field_name: str, field_dtype='VARCHAR') + + + Add a field name to joined_timeseries + if it does not already exist. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: join_attributes(attributes_filepath: Union[str, pathlib.Path]) + + + Join attributes from the provided attribute table(s) to new + fields in the joined_timeseries table. + + + :Parameters: + + **attributes_filepath** : Union[str, Path] + File path to the "attributes" data. String must include path to + file(s) and can include wildcards. For example, + "/path/to/parquet/\*.parquet". + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: calculate_field(new_field_name: str, new_field_type: str, parameter_names: List[str], user_defined_function: Callable, replace: bool = True) + + + Calculate a new field in joined_timeseries based on existing + fields and a user-defined function. + + + :Parameters: + + **new_field_name** : str + Name of new field to be added to joined_timeseries. + + **new_field_type** : str + Data type of the new field. + + **parameter_names** : List[str] + Arguments to your user function, + must be exisiting joined_timeseries fields. + + **user_defined_function** : Callable + Function to apply. + + **replace** : bool + If replace is True and new_field_name already exists, it is + dropped before being recalculated and re-added. + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: _validate_query_model(query_model: Any, data: Dict) -> Any + + + Validate the query based on existing table fields. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_metrics(group_by: List[str], order_by: List[str], include_metrics: Union[List[teehr.models.queries.MetricEnum], str] = 'all', filters: Union[List[dict], None] = None, include_geometry: bool = False, return_query: bool = False) -> Union[str, pandas.DataFrame, geopandas.GeoDataFrame] + + + Calculate performance metrics using database queries. + + + :Parameters: + + **group_by** : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **include_metrics** : Union[List[MetricEnum], str] + List of metrics (see below) for allowable list, or "all" to return + all. Placeholder, currently ignored -> returns "all". + + **filters** : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data + that is included in metrics. + + **include_geometry** : bool, optional + True joins the geometry to the query results. + Only works if `primary_location_id` + is included as a group_by field, by default False. + + **return_query** : bool, optional + True returns the query string instead of the data, + by default False. + + :Returns: + + Union[pd.DataFrame, gpd.GeoDataFrame, str] + A DataFrame or optionally a GeoDataFrame containing query results, + or the query itself as a string. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_get_metrics_query` + Create the get metrics query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_joined_timeseries(order_by: List[str], filters: Union[List[dict], None] = None, include_geometry: bool = False, return_query: bool = False) -> Union[pandas.DataFrame, geopandas.GeoDataFrame, str] + + + Retrieve joined timeseries using database query. + + + :Parameters: + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **filters** : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data + that is included in metrics. + + **include_geometry** : bool + True joins the geometry to the query results. + Only works if `primary_location_id` + is included as a group_by field. + + **return_query** : bool = False + True returns the query string instead of the data. + + :Returns: + + Union[pd.DataFrame, gpd.GeoDataFrame str] + A DataFrame or GeoDataFrame of query results + or the query itself as a string. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_get_joined_timeseries_query` + Create the get joined timeseries query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_timeseries(order_by: List[str], timeseries_name: str, filters: Union[List[dict], None] = None, return_query: bool = False) -> Union[pandas.DataFrame, str] + + + Retrieve timeseries using database query. + + + :Parameters: + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **timeseries_name** : str + Name of the time series to query ('primary' or 'secondary'). + + **filters** : Union[List[dict], None], optional + List of dictionaries describing the "where" clause to limit data + that is included in metrics, by default None. + + **return_query** : bool, optional + True returns the query string instead of the data, + by default False. + + :Returns: + + Union[pd.DataFrame, str] + A DataFrame of query results or the query itself as a string. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_get_timeseries_query` + Create the get timeseries query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_timeseries_chars(group_by: List[str], order_by: List[str], timeseries_name: str, filters: Union[List[dict], None] = None, return_query: bool = False) -> Union[str, pandas.DataFrame] + + + Retrieve timeseries characteristics using database query. + + + :Parameters: + + **group_by** : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **timeseries_name** : str + Name of the time series to query (primary or secondary). + + **filters** : Union[List[dict], None], optional + List of dictionaries describing the "where" clause to limit data + that is included in metrics., by default None. + + **return_query** : bool, optional + True returns the query string instead of the data, + by default False. + + :Returns: + + Union[str, pd.DataFrame] + A DataFrame of time series characteristics including: + - count + - min + - max + - average + - sum + - variance + + or, the query itself as a string. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_get_timeseries_char_query` + Create the get timeseries characteristics query. + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: get_unique_field_values(field_name: str) -> pandas.DataFrame + + + Get unique values for a given field. + + + :Parameters: + + **field_name** : str + Name of the joined_timeseries field. + + :Returns: + + pd.DataFrame + A dataframe containing unique values for the given field. + + + + + + + + .. seealso:: + + + :obj:`teehr.queries.duckdb_database.create_unique_field_values_query` + Create the get unique field values query. + + + + + + + + .. + !! processed by numpydoc !! + + diff --git a/_sources/autoapi/teehr/index.rst.txt b/_sources/autoapi/teehr/index.rst.txt new file mode 100644 index 00000000..0ba7f887 --- /dev/null +++ b/_sources/autoapi/teehr/index.rst.txt @@ -0,0 +1,83 @@ +:py:mod:`teehr` +=============== + +.. py:module:: teehr + +.. autoapi-nested-parse:: + + + TEEHR logger implementation. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Subpackages +----------- +.. toctree:: + :titlesonly: + :maxdepth: 3 + + api/index.rst + database/index.rst + loading/index.rst + models/index.rst + queries/index.rst + utilities/index.rst + + +Package Contents +---------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.add_stderr_logger + + + +.. py:function:: add_stderr_logger(level: int = logging.DEBUG) -> logging.StreamHandler + + + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + + Taken from the urllib3 package. + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/loading/index.rst.txt b/_sources/autoapi/teehr/loading/index.rst.txt new file mode 100644 index 00000000..358d4637 --- /dev/null +++ b/_sources/autoapi/teehr/loading/index.rst.txt @@ -0,0 +1,40 @@ +:py:mod:`teehr.loading` +======================= + +.. py:module:: teehr.loading + +.. autoapi-nested-parse:: + + + TEEHR Loading init. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Subpackages +----------- +.. toctree:: + :titlesonly: + :maxdepth: 3 + + nextgen/index.rst + nwm/index.rst + usgs/index.rst + + diff --git a/_sources/autoapi/teehr/loading/nextgen/index.rst.txt b/_sources/autoapi/teehr/loading/nextgen/index.rst.txt new file mode 100644 index 00000000..000843c7 --- /dev/null +++ b/_sources/autoapi/teehr/loading/nextgen/index.rst.txt @@ -0,0 +1,38 @@ +:py:mod:`teehr.loading.nextgen` +=============================== + +.. py:module:: teehr.loading.nextgen + +.. autoapi-nested-parse:: + + + Loading for Nextgen data -- Work In Progress. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + ngen/index.rst + + diff --git a/_sources/autoapi/teehr/loading/nextgen/ngen/index.rst.txt b/_sources/autoapi/teehr/loading/nextgen/ngen/index.rst.txt new file mode 100644 index 00000000..42c0a73f --- /dev/null +++ b/_sources/autoapi/teehr/loading/nextgen/ngen/index.rst.txt @@ -0,0 +1,19 @@ +:py:mod:`teehr.loading.nextgen.ngen` +==================================== + +.. py:module:: teehr.loading.nextgen.ngen + +.. autoapi-nested-parse:: + + Library of code to ingest NGEN outputs to the TEEHR parquet data model format. + + This code would basically just be a \*.csv parser the write to parquet files. + Given how variable the ngen output can be at this point, it is not worth + writing robust converter code. A few helper functions below. + + See also examples/loading/ngen_to_parquet.ipynb + + .. + !! processed by numpydoc !! + + diff --git a/_sources/autoapi/teehr/loading/nwm/const/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/const/index.rst.txt new file mode 100644 index 00000000..77a83f96 --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/const/index.rst.txt @@ -0,0 +1,62 @@ +:py:mod:`teehr.loading.nwm.const` +================================= + +.. py:module:: teehr.loading.nwm.const + +.. autoapi-nested-parse:: + + Module for defining constants related to loading NWM data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +.. py:data:: NWM_BUCKET + :value: 'national-water-model' + + + +.. py:data:: NWM22_UNIT_LOOKUP + + + +.. py:data:: NWM30_START_DATE + + + +.. py:data:: NWM_S3_JSON_PATH + :value: 's3://ciroh-nwm-zarr-copy' + + + +.. py:data:: NWM22_ANALYSIS_CONFIG + + + +.. py:data:: NWM30_ANALYSIS_CONFIG + + + +.. py:data:: CONUS_NWM_WKT + :value: 'PROJCS["Lambert_Conformal_Conic",GEOGCS["GCS_Sphere",DATUM["D_Sphere",SPHEROID["Sphere",6370000.0...' + + + +.. py:data:: HI_NWM_WKT + :value: 'PROJCS["Lambert_Conformal_Conic",GEOGCS["GCS_Sphere",DATUM["D_Sphere",SPHEROID["Sphere",6370000.0...' + + + +.. py:data:: PR_NWM_WKT + :value: 'PROJCS["Sphere_Lambert_Conformal_Conic",GEOGCS["GCS_Sphere",DATUM["D_Sphere",SPHEROID["Sphere",63...' + + + +.. py:data:: AL_NWM_WKT + :value: 'PROJCS["Sphere_Stereographic",GEOGCS["Sphere",DATUM["Sphere",SPHEROID["unnamed",6370000,0]],...' + + + diff --git a/_sources/autoapi/teehr/loading/nwm/grid_utils/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/grid_utils/index.rst.txt new file mode 100644 index 00000000..3eaa02d5 --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/grid_utils/index.rst.txt @@ -0,0 +1,99 @@ +:py:mod:`teehr.loading.nwm.grid_utils` +====================================== + +.. py:module:: teehr.loading.nwm.grid_utils + +.. autoapi-nested-parse:: + + Module defining shared functions for processing NWM grid data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.grid_utils.compute_zonal_mean + teehr.loading.nwm.grid_utils.process_single_file + teehr.loading.nwm.grid_utils.fetch_and_format_nwm_grids + + + +.. py:function:: compute_zonal_mean(da: xarray.DataArray, weights_filepath: str) -> pandas.DataFrame + + + Compute zonal mean of area-weighted pixels for given + zones and weights. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: process_single_file(row: Tuple, configuration: str, variable_name: str, weights_filepath: str, ignore_missing_file: bool, units_format_dict: Dict) -> pandas.DataFrame + + + Fetch a single json reference file and format to a dataframe using the TEEHR data model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: fetch_and_format_nwm_grids(json_paths: List[str], configuration: str, variable_name: str, output_parquet_dir: str, zonal_weights_filepath: str, ignore_missing_file: bool, units_format_dict: Dict, overwrite_output: bool) + + + Read in the single reference jsons, subset the NWM data based on + provided IDs and format and save the data as a parquet files. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/loading/nwm/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/index.rst.txt new file mode 100644 index 00000000..5c3601c8 --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/index.rst.txt @@ -0,0 +1,45 @@ +:py:mod:`teehr.loading.nwm` +=========================== + +.. py:module:: teehr.loading.nwm + +.. autoapi-nested-parse:: + + + Functionality for fetching and formatting NWM data. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + const/index.rst + grid_utils/index.rst + nwm_grids/index.rst + nwm_points/index.rst + point_utils/index.rst + retrospective_grids/index.rst + retrospective_points/index.rst + utils/index.rst + + diff --git a/_sources/autoapi/teehr/loading/nwm/nwm_grids/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/nwm_grids/index.rst.txt new file mode 100644 index 00000000..4759ae3a --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/nwm_grids/index.rst.txt @@ -0,0 +1,172 @@ +:py:mod:`teehr.loading.nwm.nwm_grids` +===================================== + +.. py:module:: teehr.loading.nwm.nwm_grids + +.. autoapi-nested-parse:: + + Module for loading and processing NWM gridded data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.nwm_grids.nwm_grids_to_parquet + + + +.. py:function:: nwm_grids_to_parquet(configuration: str, output_type: str, variable_name: str, start_date: Union[str, datetime.datetime], ingest_days: int, zonal_weights_filepath: str, json_dir: Union[str, pathlib.Path], output_parquet_dir: Union[str, pathlib.Path], nwm_version: teehr.models.loading.utils.SupportedNWMOperationalVersionsEnum, data_source: Optional[teehr.models.loading.utils.SupportedNWMDataSourcesEnum] = 'GCS', kerchunk_method: Optional[teehr.models.loading.utils.SupportedKerchunkMethod] = 'local', t_minus_hours: Optional[Iterable[int]] = None, ignore_missing_file: Optional[bool] = True, overwrite_output: Optional[bool] = False) + + + Fetch NWM gridded data, calculate zonal statistics (currently only + mean is available) of selected variable for given zones, convert + and save to TEEHR tabular format. + + + :Parameters: + + **configuration** : str + NWM forecast category. + (e.g., "analysis_assim", "short_range", ...). + + **output_type** : str + Output component of the configuration. + (e.g., "channel_rt", "reservoir", ...). + + **variable_name** : str + Name of the NWM data variable to download. + (e.g., "streamflow", "velocity", ...). + + **start_date** : str or datetime + Date to begin data ingest. + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + + **ingest_days** : int + Number of days to ingest data after start date. + + **zonal_weights_filepath** : str + Path to the array containing fraction of pixel overlap + for each zone. + + **json_dir** : str + Directory path for saving json reference files. + + **output_parquet_dir** : str + Path to the directory for the final parquet files. + + **nwm_version** : SupportedNWMOperationalVersionsEnum + The NWM operational version. + "nwm22", or "nwm30". + + **data_source** : Optional[SupportedNWMDataSourcesEnum] + Specifies the remote location from which to fetch the data + "GCS" (default), "NOMADS", or "DSTOR". + Currently only "GCS" is implemented. + + **kerchunk_method** : Optional[SupportedKerchunkMethod] + When data_source = "GCS", specifies the preference in creating Kerchunk + reference json files. "local" (default) will create new json files from + netcdf files in GCS and save to a local directory if they do not already + exist locally, in which case the creation is skipped. "remote" - read the + CIROH pre-generated jsons from s3, ignoring any that are unavailable. + "auto" - read the CIROH pre-generated jsons from s3, and create any that + are unavailable, storing locally. + + **t_minus_hours** : Optional[Iterable[int]] + Specifies the look-back hours to include if an assimilation + configuration is specified. + + **ignore_missing_file** : bool + Flag specifying whether or not to fail if a missing NWM file is encountered + True = skip and continue; False = fail. + + **overwrite_output** : bool + Flag specifying whether or not to overwrite output files if they already + exist. True = overwrite; False = fail. + + + + + + + + + .. seealso:: + + + :obj:`teehr.utilities.generate_weights.generate_weights_file` + Weighted average. + + + .. rubric:: Notes + + The NWM configuration variables, including configuration, output_type, and + variable_name are stored as a pydantic model in grid_config_models.py + + Forecast and assimilation data is grouped and saved one file per reference + time, using the file name convention "YYYYMMDDTHHZ". The tabular output + parquet files follow the timeseries data model described in the + :ref:`data model `. + + + .. rubric:: Examples + + Here we will calculate mean areal precipitation using NWM forcing data for + some watersheds (polygons) a using pre-calculated weights file + (see: :func:`generate_weights_file() + ` for weights calculation). + + Import the necessary module. + + >>> import teehr.loading.nwm.nwm_grids as tlg + + Specify the input variables. + + >>> CONFIGURATION = "forcing_short_range" + >>> OUTPUT_TYPE = "forcing" + >>> VARIABLE_NAME = "RAINRATE" + >>> START_DATE = "2020-12-18" + >>> INGEST_DAYS = 1 + >>> ZONAL_WEIGHTS_FILEPATH = Path(Path.home(), "nextgen_03S_weights.parquet") + >>> JSON_DIR = Path(Path.home(), "temp/parquet/jsons/") + >>> OUTPUT_DIR = Path(Path.home(), "temp/parquet") + >>> NWM_VERSION = "nwm22" + >>> DATA_SOURCE = "GCS" + >>> KERCHUNK_METHOD = "auto" + >>> T_MINUS = [0, 1, 2] + >>> IGNORE_MISSING_FILE = True + >>> OVERWRITE_OUTPUT = True + + Perform the calculations, writing to the specified directory. + + >>> tlg.nwm_grids_to_parquet( + >>> configuration=CONFIGURATION, + >>> output_type=OUTPUT_TYPE, + >>> variable_name=VARIABLE_NAME, + >>> start_date=START_DATE, + >>> ingest_days=INGEST_DAYS, + >>> zonal_weights_filepath=ZONAL_WEIGHTS_FILEPATH, + >>> json_dir=JSON_DIR, + >>> output_parquet_dir=OUTPUT_DIR, + >>> nwm_version=NWM_VERSION, + >>> data_source=DATA_SOURCE, + >>> kerchunk_method=KERCHUNK_METHOD, + >>> t_minus_hours=T_MINUS, + >>> ignore_missing_file=IGNORE_MISSING_FILE, + >>> overwrite_output=OVERWRITE_OUTPUT + >>> ) + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/loading/nwm/nwm_points/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/nwm_points/index.rst.txt new file mode 100644 index 00000000..0ae2b93f --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/nwm_points/index.rst.txt @@ -0,0 +1,180 @@ +:py:mod:`teehr.loading.nwm.nwm_points` +====================================== + +.. py:module:: teehr.loading.nwm.nwm_points + +.. autoapi-nested-parse:: + + Module for fetchning and processing NWM point data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.nwm_points.nwm_to_parquet + + + +.. py:function:: nwm_to_parquet(configuration: str, output_type: str, variable_name: str, start_date: Union[str, datetime.datetime], ingest_days: int, location_ids: List[int], json_dir: Union[str, pathlib.Path], output_parquet_dir: Union[str, pathlib.Path], nwm_version: teehr.models.loading.utils.SupportedNWMOperationalVersionsEnum, data_source: Optional[teehr.models.loading.utils.SupportedNWMDataSourcesEnum] = 'GCS', kerchunk_method: Optional[teehr.models.loading.utils.SupportedKerchunkMethod] = 'local', t_minus_hours: Optional[List[int]] = None, process_by_z_hour: Optional[bool] = True, stepsize: Optional[int] = 100, ignore_missing_file: Optional[bool] = True, overwrite_output: Optional[bool] = False) + + + Fetch NWM point data, format to tabular TEEHR data model, + and save to parquet. + + + :Parameters: + + **configuration** : str + NWM forecast category. + (e.g., "analysis_assim", "short_range", ...). + + **output_type** : str + Output component of the configuration. + (e.g., "channel_rt", "reservoir", ...). + + **variable_name** : str + Name of the NWM data variable to download. + (e.g., "streamflow", "velocity", ...). + + **start_date** : str or datetime + Date to begin data ingest. + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + + **ingest_days** : int + Number of days to ingest data after start date. + + **location_ids** : List[int] + Array specifying NWM IDs of interest. + + **json_dir** : str + Directory path for saving json reference files. + + **output_parquet_dir** : str + Path to the directory for the final parquet files. + + **nwm_version** : SupportedNWMOperationalVersionsEnum + The NWM operational version + "nwm22", or "nwm30". + + **data_source** : Optional[SupportedNWMDataSourcesEnum] + Specifies the remote location from which to fetch the data + "GCS" (default), "NOMADS", or "DSTOR" + Currently only "GCS" is implemented. + + **kerchunk_method** : Optional[SupportedKerchunkMethod] + When data_source = "GCS", specifies the preference in creating Kerchunk + reference json files. "local" (default) will create new json files from + netcdf files in GCS and save to a local directory if they do not already + exist locally, in which case the creation is skipped. "remote" - read the + CIROH pre-generated jsons from s3, ignoring any that are unavailable. + "auto" - read the CIROH pre-generated jsons from s3, and create any that + are unavailable, storing locally. + + **t_minus_hours** : Optional[List[int]] + Specifies the look-back hours to include if an assimilation + configuration is specified. + + **process_by_z_hour** : Optional[bool] + A boolean flag that determines the method of grouping files + for processing. The default is True, which groups by day and z_hour. + False groups files sequentially into chunks, whose size is determined + by stepsize. This allows users to process more data potentially more + efficiently, but runs to risk of splitting up forecasts into separate + output files. + + **stepsize** : Optional[int] + The number of json files to process at one time. Used if + process_by_z_hour is set to False. Default value is 100. Larger values + can result in greater efficiency but require more memory. + + **ignore_missing_file** : Optional[bool] + Flag specifying whether or not to fail if a missing NWM file is encountered + True = skip and continue. + False = fail. + + **overwrite_output** : Optional[bool] + Flag specifying whether or not to overwrite output files if they already + exist. True = overwrite; False = fail. + + + + + + + + + + .. rubric:: Notes + + The NWM configuration variables, including configuration, output_type, and + variable_name are stored as pydantic models in point_config_models.py + + Forecast and assimilation data is grouped and saved one file per reference + time, using the file name convention "YYYYMMDDTHHZ". The tabular output + parquet files follow the timeseries data model described in the + :ref:`data model `. + + + .. rubric:: Examples + + Here we fetch operational streamflow forecasts for NWM v2.2 from GCS, and + save the output in the TEEHR :ref:`data model ` format. + + Import the necessary module. + + >>> import teehr.loading.nwm.nwm_points as tlp + + Specify the input variables. + + >>> CONFIGURATION = "short_range" + >>> OUTPUT_TYPE = "channel_rt" + >>> VARIABLE_NAME = "streamflow" + >>> START_DATE = "2023-03-18" + >>> INGEST_DAYS = 1 + >>> JSON_DIR = Path(Path.home(), "temp/parquet/jsons/") + >>> OUTPUT_DIR = Path(Path.home(), "temp/parquet") + >>> NWM_VERSION = "nwm22" + >>> DATA_SOURCE = "GCS" + >>> KERCHUNK_METHOD = "auto" + >>> T_MINUS = [0, 1, 2] + >>> IGNORE_MISSING_FILE = True + >>> OVERWRITE_OUTPUT = True + >>> PROCESS_BY_Z_HOUR = True + >>> STEPSIZE = 100 + >>> LOCATION_IDS = [7086109, 7040481, 7053819] + + Fetch and format the data, writing to the specified directory. + + >>> tlp.nwm_to_parquet( + >>> configuration=CONFIGURATION, + >>> output_type=OUTPUT_TYPE, + >>> variable_name=VARIABLE_NAME, + >>> start_date=START_DATE, + >>> ingest_days=INGEST_DAYS, + >>> location_ids=LOCATION_IDS, + >>> json_dir=JSON_DIR, + >>> output_parquet_dir=OUTPUT_DIR, + >>> nwm_version=NWM_VERSION, + >>> data_source=DATA_SOURCE, + >>> kerchunk_method=KERCHUNK_METHOD, + >>> t_minus_hours=T_MINUS, + >>> process_by_z_hour=PROCESS_BY_Z_HOUR, + >>> stepsize=STEPSIZE, + >>> ignore_missing_file=IGNORE_MISSING_FILE, + >>> overwrite_output=OVERWRITE_OUTPUT, + >>> ) + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/loading/nwm/point_utils/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/point_utils/index.rst.txt new file mode 100644 index 00000000..65e58ec4 --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/point_utils/index.rst.txt @@ -0,0 +1,138 @@ +:py:mod:`teehr.loading.nwm.point_utils` +======================================= + +.. py:module:: teehr.loading.nwm.point_utils + +.. autoapi-nested-parse:: + + Module defining shared functions for processing NWM point data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.point_utils.file_chunk_loop + teehr.loading.nwm.point_utils.process_chunk_of_files + teehr.loading.nwm.point_utils.fetch_and_format_nwm_points + + + +.. py:function:: file_chunk_loop(row: Tuple, location_ids: numpy.array, variable_name: str, configuration: str, schema: pyarrow.Schema, ignore_missing_file: bool, units_format_dict: Dict, nwm_version: str) + + + Fetch NWM values and convert to tabular format for a single json. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: process_chunk_of_files(df: pandas.DataFrame, location_ids: Iterable[int], configuration: str, variable_name: str, output_parquet_dir: str, process_by_z_hour: bool, ignore_missing_file: bool, units_format_dict: Dict, overwrite_output: bool, nwm_version: str) + + + Assemble a table for a chunk of NWM files. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: fetch_and_format_nwm_points(json_paths: List[str], location_ids: Iterable[int], configuration: str, variable_name: str, output_parquet_dir: str, process_by_z_hour: bool, stepsize: int, ignore_missing_file: bool, units_format_dict: Dict, overwrite_output: bool, nwm_version: str) + + + Read in the single reference jsons, subset the + NWM data based on provided IDs and formats and save + the data as parquet files using Dask. + + + :Parameters: + + **json_paths** : list + List of the single json reference filepaths. + + **location_ids** : Iterable[int] + Array specifying NWM IDs of interest. + + **configuration** : str + NWM forecast category. + + **variable_name** : str + Name of the NWM data variable to download. + + **output_parquet_dir** : str + Path to the directory for the final parquet files. + + **process_by_z_hour** : bool + A boolean flag that determines the method of grouping files + for processing. + + **stepsize** : int + The number of json files to process at one time. + + **ignore_missing_file** : bool + Flag specifying whether or not to fail if a missing NWM + file is encountered + True = skip and continue + False = fail. + + **units_format_dict** : Dict, + Dictionary of unit formats. + + **overwrite_output** : bool + Flag specifying whether or not to overwrite output files if + they already exist. True = overwrite; False = fail. + + **nwm_version** : str + Specified NWM version. + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/loading/nwm/retrospective_grids/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/retrospective_grids/index.rst.txt new file mode 100644 index 00000000..fe6dfa52 --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/retrospective_grids/index.rst.txt @@ -0,0 +1,224 @@ +:py:mod:`teehr.loading.nwm.retrospective_grids` +=============================================== + +.. py:module:: teehr.loading.nwm.retrospective_grids + +.. autoapi-nested-parse:: + + A module for loading retrospective NWM gridded data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.retrospective_grids.get_data_array + teehr.loading.nwm.retrospective_grids.process_group + teehr.loading.nwm.retrospective_grids.construct_nwm21_json_paths + teehr.loading.nwm.retrospective_grids.compute_zonal_mean + teehr.loading.nwm.retrospective_grids.process_single_file + teehr.loading.nwm.retrospective_grids.nwm_retro_grids_to_parquet + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.retrospective_grids.t0 + + +.. py:function:: get_data_array(var_da: xarray.DataArray, rows: numpy.array, cols: numpy.array) + + + Read the forcing variable into memory for the grouped + timesteps and zone rows and cols. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: process_group(da_i: xarray.DataArray, rows: numpy.array, cols: numpy.array, weights_df: pandas.DataFrame, weight_vals: numpy.array, variable_name: str, units_format_dict: Dict, nwm_version: str) + + + Fetch a chunk of NWM v3.0 gridded data, compute weighted + values for each zone, and format to dataframe. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: construct_nwm21_json_paths(start_date: Union[str, datetime.datetime], end_date: Union[str, datetime.datetime]) + + + Construct the remote paths for the NWM v2.1 zarr json files + within the specified start and end dates. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: compute_zonal_mean(var_da: xarray.DataArray, weights_filepath: str, time_dt: pandas.Timestamp) -> pandas.DataFrame + + + Compute the zonal mean for given zones and weights. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: process_single_file(row: Tuple, variable_name: str, weights_filepath: str, ignore_missing_file: bool, units_format_dict: Dict, nwm_version: str) + + + Compute the zonal mean for a single json reference file and format + to a dataframe using the TEEHR data model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: nwm_retro_grids_to_parquet(nwm_version: teehr.models.loading.utils.SupportedNWMRetroVersionsEnum, variable_name: teehr.models.loading.nwm22_grid.ForcingVariablesEnum, zonal_weights_filepath: Union[str, pathlib.Path], start_date: Union[str, datetime.datetime, pandas.Timestamp], end_date: Union[str, datetime.datetime, pandas.Timestamp], output_parquet_dir: Union[str, pathlib.Path], chunk_by: Union[teehr.models.loading.utils.ChunkByEnum, None] = None, overwrite_output: Optional[bool] = False, domain: Optional[teehr.models.loading.utils.SupportedNWMRetroDomainsEnum] = 'CONUS') + + + Fetch NWM v2.1 or v3.0 gridded data, summarize to zones using + a pre-computed weight file, and save as a Parquet file. + + + :Parameters: + + **nwm_version** : SupportedNWMRetroVersionsEnum + NWM retrospective version to fetch. + Currently `nwm21` and `nwm30` supported. + + **variable_name** : str + Name of the NWM forcing data variable to download. + (e.g., "PRECIP", "PSFC", "Q2D", ...). + + **zonal_weights_filepath** : str, + Path to the array containing fraction of pixel overlap + for each zone. + + **start_date** : Union[str, datetime, pd.Timestamp] + Date to begin data ingest. + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + Rounds down to beginning of day. + + **end_date** : Union[str, datetime, pd.Timestamp], + Last date to fetch. Rounds up to end of day. + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + + **output_parquet_dir** : Union[str, Path], + Directory where output will be saved. + + **chunk_by** : Union[ChunkByEnum, None] = None, + If None (default) saves all timeseries to a single file, otherwise + the data is processed using the specified parameter. + Can be: 'location_id', 'day', 'week', 'month', or 'year'. + + **overwrite_output** : bool = False, + Whether output should overwrite files if they exist. Default is False. + + **domain** : str = "CONUS" + Geographical domain when NWM version is v3.0. + Acceptable values are "Alaska", "CONUS" (default), "Hawaii", and "PR". + Only used when NWM version equals v3.0. + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:data:: t0 + + + diff --git a/_sources/autoapi/teehr/loading/nwm/retrospective_points/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/retrospective_points/index.rst.txt new file mode 100644 index 00000000..809b6e68 --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/retrospective_points/index.rst.txt @@ -0,0 +1,249 @@ +:py:mod:`teehr.loading.nwm.retrospective_points` +================================================ + +.. py:module:: teehr.loading.nwm.retrospective_points + +.. autoapi-nested-parse:: + + A module for loading retrospective NWM point data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.retrospective_points.validate_start_end_date + teehr.loading.nwm.retrospective_points.da_to_df + teehr.loading.nwm.retrospective_points.datetime_to_date + teehr.loading.nwm.retrospective_points.format_grouped_filename + teehr.loading.nwm.retrospective_points.nwm_retro_to_parquet + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.retrospective_points.NWM20_MIN_DATE + teehr.loading.nwm.retrospective_points.NWM20_MAX_DATE + teehr.loading.nwm.retrospective_points.NWM21_MIN_DATE + teehr.loading.nwm.retrospective_points.NWM21_MAX_DATE + teehr.loading.nwm.retrospective_points.NWM30_MIN_DATE + teehr.loading.nwm.retrospective_points.NWM30_MAX_DATE + + +.. py:data:: NWM20_MIN_DATE + + + +.. py:data:: NWM20_MAX_DATE + + + +.. py:data:: NWM21_MIN_DATE + + + +.. py:data:: NWM21_MAX_DATE + + + +.. py:data:: NWM30_MIN_DATE + + + +.. py:data:: NWM30_MAX_DATE + + + +.. py:function:: validate_start_end_date(nwm_version: teehr.models.loading.utils.SupportedNWMRetroVersionsEnum, start_date: Union[str, datetime.datetime], end_date: Union[str, datetime.datetime]) + + + Validate the start and end dates by NWM version. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: da_to_df(nwm_version: teehr.models.loading.utils.SupportedNWMRetroVersionsEnum, da: xarray.DataArray) -> pandas.DataFrame + + + Format NWM retrospective data to TEEHR format. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: datetime_to_date(dt: datetime.datetime) -> datetime.datetime + + + Convert datetime to date only. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: format_grouped_filename(ds_i: xarray.Dataset) -> str + + + Format the output filename based on min and max + datetime in the dataset. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: nwm_retro_to_parquet(nwm_version: teehr.models.loading.utils.SupportedNWMRetroVersionsEnum, variable_name: str, location_ids: List[int], start_date: Union[str, datetime.datetime, pandas.Timestamp], end_date: Union[str, datetime.datetime, pandas.Timestamp], output_parquet_dir: Union[str, pathlib.Path], chunk_by: Union[teehr.models.loading.utils.ChunkByEnum, None] = None, overwrite_output: Optional[bool] = False, domain: Optional[teehr.models.loading.utils.SupportedNWMRetroDomainsEnum] = 'CONUS') + + + Fetch NWM retrospective at NWM COMIDs and store as Parquet file. + + + :Parameters: + + **nwm_version** : SupportedNWMRetroVersionsEnum + NWM retrospective version to fetch. + Currently `nwm20`, `nwm21`, and `nwm30` supported. + + **variable_name** : str + Name of the NWM data variable to download. + (e.g., "streamflow", "velocity", ...). + + **location_ids** : Iterable[int], + NWM feature_ids to fetch. + + **start_date** : Union[str, datetime, pd.Timestamp] + Date to begin data ingest. + Str formats can include YYYY-MM-DD or MM/DD/YYYY + Rounds down to beginning of day. + + **end_date** : Union[str, datetime, pd.Timestamp], + Last date to fetch. Rounds up to end of day + Str formats can include YYYY-MM-DD or MM/DD/YYYY. + + **output_parquet_dir** : Union[str, Path], + Directory where output will be saved. + + **chunk_by** : Union[ChunkByEnum, None] = None, + If None (default) saves all timeseries to a single file, otherwise + the data is processed using the specified parameter. + Can be: 'location_id', 'day', 'week', 'month', or 'year'. + + **overwrite_output** : bool = False, + Whether output should overwrite files if they exist. Default is False. + + **domain** : str = "CONUS" + Geographical domain when NWM version is v3.0. + Acceptable values are "Alaska", "CONUS" (default), "Hawaii", and "PR". + Only used when NWM version equals `nwm30`. + + + + + + + + + + + + .. rubric:: Examples + + Here we fetch and format retrospective NWM v2.0 streamflow data + for two locations. + + Import the module. + + >>> import teehr.loading.nwm.retrospective_points as nwm_retro + + Specify the input variables. + + >>> NWM_VERSION = "nwm20" + >>> VARIABLE_NAME = "streamflow" + >>> START_DATE = datetime(2000, 1, 1) + >>> END_DATE = datetime(2000, 1, 2, 23) + >>> LOCATION_IDS = [7086109, 7040481] + >>> OUTPUT_ROOT = Path(Path().home(), "temp") + >>> OUTPUT_DIR = Path(OUTPUT_ROOT, "nwm20_retrospective") + + Fetch and format the data, writing to the specified directory. + + >>> nwm_retro.nwm_retro_to_parquet( + >>> nwm_version=NWM_VERSION, + >>> variable_name=VARIABLE_NAME, + >>> start_date=START_DATE, + >>> end_date=END_DATE, + >>> location_ids=LOCATION_IDS, + >>> output_parquet_dir=OUTPUT_DIR + >>> ) + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/loading/nwm/utils/index.rst.txt b/_sources/autoapi/teehr/loading/nwm/utils/index.rst.txt new file mode 100644 index 00000000..02a00aaa --- /dev/null +++ b/_sources/autoapi/teehr/loading/nwm/utils/index.rst.txt @@ -0,0 +1,498 @@ +:py:mod:`teehr.loading.nwm.utils` +================================= + +.. py:module:: teehr.loading.nwm.utils + +.. autoapi-nested-parse:: + + Module defining common utilities for fetching and processing NWM data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.utils.check_dates_against_nwm_version + teehr.loading.nwm.utils.generate_json_paths + teehr.loading.nwm.utils.write_parquet_file + teehr.loading.nwm.utils.load_gdf + teehr.loading.nwm.utils.parquet_to_gdf + teehr.loading.nwm.utils.np_to_list + teehr.loading.nwm.utils.get_dataset + teehr.loading.nwm.utils.list_to_np + teehr.loading.nwm.utils.check_for_prebuilt_json_paths + teehr.loading.nwm.utils.gen_json + teehr.loading.nwm.utils.build_zarr_references + teehr.loading.nwm.utils.construct_assim_paths + teehr.loading.nwm.utils.build_remote_nwm_filelist + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.nwm.utils.logger + + +.. py:data:: logger + + + +.. py:function:: check_dates_against_nwm_version(nwm_version: str, start_date: Union[str, datetime.datetime], ingest_days: int) + + + Make sure start/end dates work with specified NWM version. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: generate_json_paths(kerchunk_method: str, gcs_component_paths: List[str], json_dir: str, ignore_missing_file: bool) -> List[str] + + + Generate remote and/or local paths to Kerchunk reference json files + depending on the specified method. + + + :Parameters: + + **kerchunk_method** : str + Specifies the preference in creating Kerchunk reference json files. + + **gcs_component_paths** : List[str] + Paths to NWM netcdf files in GCS. + + **json_dir** : str + Local directory for caching created json files. + + **ignore_missing_file** : bool + Flag specifying whether or not to fail if a missing + NWM file is encountered. + + :Returns: + + List[str] + List of filepaths to json files locally and/or in s3. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: write_parquet_file(filepath: pathlib.Path, overwrite_output: bool, data: Union[pyarrow.Table, pandas.DataFrame]) + + + Write output timeseries parquet file with logic controlling + whether or not to overwrite an existing file. + + + :Parameters: + + **filepath** : Path + Path to the output parquet file. + + **overwrite_output** : bool + Flag controlling overwrite behavior. + + **data** : Union[pa.Table, pd.DataFrame] + The output data as either a dataframe or pyarrow table. + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: load_gdf(filepath: Union[str, pathlib.Path], **kwargs: str) -> geopandas.GeoDataFrame + + + Load any supported geospatial file type into a gdf using GeoPandas. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: parquet_to_gdf(parquet_filepath: str) -> geopandas.GeoDataFrame + + + Read parquet as GeoDataFrame. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: np_to_list(t) + + + Convert numpy array to list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_dataset(filepath: str, ignore_missing_file: bool, **kwargs) -> xarray.Dataset + + + Retrieve a blob from the data service as xarray.Dataset. + + + :Parameters: + + **filepath** : str + Path to the kerchunk json file. Can be local or remote. + + **ignore_missing_file** : bool + Flag controlling whether to ignore missing files. + + :Returns: + + xarray.Dataset + The data stored in the blob. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: list_to_np(lst) + + + Convert list to a tuple. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: check_for_prebuilt_json_paths(fs: fsspec.filesystem, gcs_path: str, return_gcs_path=False) -> str + + + Check for existence of a pre-built kerchunk json in s3 based + on its GCS path. + + + :Parameters: + + **fs** : fsspec.filesystem + S3-based filesystem. + + **gcs_path** : str + Path to the netcdf file in GCS. + + **return_gcs_path** : bool, optional + Flag to return GCS path of s3 is missing, by default False. + + :Returns: + + str + Path to the json in s3 or netcdf file in GCS. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: gen_json(remote_path: str, fs: fsspec.filesystem, json_dir: Union[str, pathlib.Path], ignore_missing_file: bool) -> str + + + Helper function for creating single-file kerchunk reference JSONs. + + + :Parameters: + + **remote_path** : str + Path to the file in the remote location (ie, GCS bucket). + + **fs** : fsspec.filesystem + Fsspec filesystem mapped to GCS. + + **json_dir** : str + Directory for saving zarr reference json files. + + :Returns: + + str + Path to the local zarr reference json file. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: build_zarr_references(remote_paths: List[str], json_dir: Union[str, pathlib.Path], ignore_missing_file: bool) -> list[str] + + + Build the single file zarr json reference files using kerchunk. + + + :Parameters: + + **remote_paths** : List[str] + List of remote filepaths. + + **json_dir** : str or Path + Local directory for caching json files. + + :Returns: + + list[str] + List of paths to the zarr reference json files. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: construct_assim_paths(gcs_dir: str, configuration: str, output_type: str, dates: pandas.DatetimeIndex, t_minus: Iterable[int], configuration_name_in_filepath: str, cycle_z_hours: Iterable[int], domain: str, file_extension: str = 'nc') -> list[str] + + + Construct paths to NWM point assimilation data based on specified + parameters. + + This function prioritizes value time over reference time so that only + files with value times falling within the specified date range are included + in the resulting file list. + + :Parameters: + + **gcs_dir** : str + Path to the NWM data on GCS. + + **configuration** : str + Configuration type. + + **output_type** : str + Output component of the configuration. + + **dates** : pd.DatetimeIndex + Range of days to fetch data. + + **t_minus** : Iterable[int] + Collection of lookback hours to include when fetching assimilation data. + + **configuration_name_in_filepath** : str + Name of the assimilation configuration as represented in the GCS file. + Defined in const_nwm.py. + + **cycle_z_hours** : Iterable[int] + The z-hour of the assimilation configuration per day. + Defined in const_nwm.py. + + **domain** : str + Geographic region covered by the assimilation configuration. + Defined in const_nwm.py. + + **file_extension** : str + File extension ("nc" or "nc.json" for remote kerchunk). + + :Returns: + + list[str] + List of remote filepaths. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: build_remote_nwm_filelist(configuration: str, output_type: str, start_dt: Union[str, datetime.datetime], ingest_days: int, analysis_config_dict: Dict, t_minus_hours: Optional[Iterable[int]], ignore_missing_file: Optional[bool]) -> List[str] + + + Assemble a list of remote NWM files in GCS based on specified user + parameters. + + + :Parameters: + + **configuration** : str + Configuration type. + + **output_type** : str + Output component of the configuration. + + **start_dt** : str “YYYY-MM-DD” or datetime + Date to begin data ingest. + + **ingest_days** : int + Number of days to ingest data after start date. + + **t_minus_hours** : Iterable[int] + Only necessary if assimilation data is requested. + Collection of lookback hours to include when fetching + assimilation data. + + **ignore_missing_file** : bool + Flag specifying whether or not to fail if a missing + NWM file is encountered + True = skip and continue + False = fail. + + :Returns: + + list + List of remote filepaths (strings). + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/loading/usgs/index.rst.txt b/_sources/autoapi/teehr/loading/usgs/index.rst.txt new file mode 100644 index 00000000..5ad631a5 --- /dev/null +++ b/_sources/autoapi/teehr/loading/usgs/index.rst.txt @@ -0,0 +1,38 @@ +:py:mod:`teehr.loading.usgs` +============================ + +.. py:module:: teehr.loading.usgs + +.. autoapi-nested-parse:: + + + USGS loading. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + usgs/index.rst + + diff --git a/_sources/autoapi/teehr/loading/usgs/usgs/index.rst.txt b/_sources/autoapi/teehr/loading/usgs/usgs/index.rst.txt new file mode 100644 index 00000000..42f68f97 --- /dev/null +++ b/_sources/autoapi/teehr/loading/usgs/usgs/index.rst.txt @@ -0,0 +1,292 @@ +:py:mod:`teehr.loading.usgs.usgs` +================================= + +.. py:module:: teehr.loading.usgs.usgs + +.. autoapi-nested-parse:: + + Module for loading and processing USGS streamflow data. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.usgs.usgs._filter_to_hourly + teehr.loading.usgs.usgs._filter_no_data + teehr.loading.usgs.usgs._convert_to_si_units + teehr.loading.usgs.usgs._datetime_to_date + teehr.loading.usgs.usgs._format_df + teehr.loading.usgs.usgs._fetch_usgs + teehr.loading.usgs.usgs._format_output_filename + teehr.loading.usgs.usgs.usgs_to_parquet + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.loading.usgs.usgs.DATETIME_STR_FMT + + +.. py:data:: DATETIME_STR_FMT + :value: '%Y-%m-%dT%H:%M:00+0000' + + + +.. py:function:: _filter_to_hourly(df: pandas.DataFrame) -> pandas.DataFrame + + + Filter out data not reported on the hour. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _filter_no_data(df: pandas.DataFrame, no_data_value=-999) -> pandas.DataFrame + + + Filter out no data values. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _convert_to_si_units(df: pandas.DataFrame) -> pandas.DataFrame + + + Convert streamflow values from english to metric. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _datetime_to_date(dt: datetime.datetime) -> datetime.datetime + + + Convert datetime to date only. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _format_df(df: pandas.DataFrame) -> pandas.DataFrame + + + Format HydroTools dataframe columns to TEEHR data model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _fetch_usgs(sites: List[str], start_date: datetime.datetime, end_date: datetime.datetime, filter_to_hourly: bool = True, filter_no_data: bool = True, convert_to_si: bool = True) -> pandas.DataFrame + + + Fetch USGS gage data and format to TEEHR format. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _format_output_filename(chunk_by: str, start_dt, end_dt) -> str + + + Format the output filename based on min and max + datetime in the dataset. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: usgs_to_parquet(sites: List[str], start_date: Union[str, datetime.datetime, pandas.Timestamp], end_date: Union[str, datetime.datetime, pandas.Timestamp], output_parquet_dir: Union[str, pathlib.Path], chunk_by: Union[teehr.models.loading.utils.ChunkByEnum, None] = None, filter_to_hourly: bool = True, filter_no_data: bool = True, convert_to_si: bool = True, overwrite_output: Optional[bool] = False) + + + Fetch USGS gage data and save as a Parquet file. + + + :Parameters: + + **sites** : List[str] + List of USGS gages sites to fetch. + Must be string to preserve the leading 0. + + **start_date** : datetime + Start time of data to fetch. + + **end_date** : datetime + End time of data to fetch. Note, since startDt is inclusive for the + USGS service, we subtract 1 minute from this time so we don't get + overlap between consecutive calls. + + **output_parquet_dir** : Union[str, Path] + Path of directory where parquet files will be saved. + + **chunk_by** : Union[str, None], default = None + How to "chunk" the fetching and storing of the data. + Valid options = ["day", "site", None]. + + **filter_to_hourly** : bool = True + Return only values that fall on the hour (i.e. drop 15 minute data). + + **filter_no_data** : bool = True + Filter out -999 values. + + **convert_to_si** : bool = True + Multiplies values by 0.3048**3 and sets `measurement_units` to `m3/s`. + + **overwrite_output** : bool + Flag specifying whether or not to overwrite output files if they + already exist. True = overwrite; False = fail. + + + + + + + + + + + + .. rubric:: Examples + + Here we fetch five days worth of USGS hourly streamflow data, to two gages, + chunking by day. + + Import the module. + + >>> from teehr.loading.usgs.usgs import usgs_to_parquet + + Set the input variables. + + >>> SITES=["02449838", "02450825"] + >>> START_DATE=datetime(2023, 2, 20) + >>> END_DATE=datetime(2023, 2, 25) + >>> OUTPUT_PARQUET_DIR=Path(Path().home(), "temp", "usgs") + >>> CHUNK_BY="day", + >>> OVERWRITE_OUTPUT=True + + Fetch the data, writing to the specified output directory. + + >>> usgs_to_parquet( + >>> sites=SITES, + >>> start_date=START_DATE, + >>> end_date=END_DATE, + >>> output_parquet_dir=TEMP_DIR, + >>> chunk_by=CHUNK_BY, + >>> overwrite_output=OVERWRITE_OUTPUT + >>> ) + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/models/index.rst.txt b/_sources/autoapi/teehr/models/index.rst.txt new file mode 100644 index 00000000..93afdf3a --- /dev/null +++ b/_sources/autoapi/teehr/models/index.rst.txt @@ -0,0 +1,48 @@ +:py:mod:`teehr.models` +====================== + +.. py:module:: teehr.models + +.. autoapi-nested-parse:: + + + Models init. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Subpackages +----------- +.. toctree:: + :titlesonly: + :maxdepth: 3 + + loading/index.rst + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + queries/index.rst + queries_database/index.rst + + diff --git a/_sources/autoapi/teehr/models/loading/index.rst.txt b/_sources/autoapi/teehr/models/loading/index.rst.txt new file mode 100644 index 00000000..84131a92 --- /dev/null +++ b/_sources/autoapi/teehr/models/loading/index.rst.txt @@ -0,0 +1,42 @@ +:py:mod:`teehr.models.loading` +============================== + +.. py:module:: teehr.models.loading + +.. autoapi-nested-parse:: + + + NWM loading models. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + nwm22_grid/index.rst + nwm22_point/index.rst + nwm30_grid/index.rst + nwm30_point/index.rst + utils/index.rst + + diff --git a/_sources/autoapi/teehr/models/loading/nwm22_grid/index.rst.txt b/_sources/autoapi/teehr/models/loading/nwm22_grid/index.rst.txt new file mode 100644 index 00000000..e797f2c1 --- /dev/null +++ b/_sources/autoapi/teehr/models/loading/nwm22_grid/index.rst.txt @@ -0,0 +1,1280 @@ +:py:mod:`teehr.models.loading.nwm22_grid` +========================================= + +.. py:module:: teehr.models.loading.nwm22_grid + +.. autoapi-nested-parse:: + + Module describing NWM v2.2 grid configuration variables. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.nwm22_grid.LandAssimVariablesEnum + teehr.models.loading.nwm22_grid.LandShortVariablesEnum + teehr.models.loading.nwm22_grid.LandMediumVariablesEnum + teehr.models.loading.nwm22_grid.LandLongVariablesEnum + teehr.models.loading.nwm22_grid.ForcingVariablesEnum + teehr.models.loading.nwm22_grid.ShortAndAnalysisOutputEnum + teehr.models.loading.nwm22_grid.MediumOutputEnum + teehr.models.loading.nwm22_grid.LongOutputEnum + teehr.models.loading.nwm22_grid.ForcingOutputEnum + teehr.models.loading.nwm22_grid.Analysis + teehr.models.loading.nwm22_grid.ShortRange + teehr.models.loading.nwm22_grid.MediumRange + teehr.models.loading.nwm22_grid.LongRange + teehr.models.loading.nwm22_grid.Forcing + teehr.models.loading.nwm22_grid.ConfigurationsEnum + teehr.models.loading.nwm22_grid.GridConfigurationModel + + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.nwm22_grid.configuration + + +.. py:class:: LandAssimVariablesEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + LandAssimVariablesEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: ACCET + :value: 'ACCET' + + + + .. py:attribute:: ACSNOM + :value: 'ACSNOM' + + + + .. py:attribute:: EDIR + :value: 'EDIR' + + + + .. py:attribute:: FSNO + :value: 'FSNO' + + + + .. py:attribute:: ISNOW + :value: 'ISNOW' + + + + .. py:attribute:: QRAIN + :value: 'QRAIN' + + + + .. py:attribute:: QSNOW + :value: 'QSNOW' + + + + .. py:attribute:: SNEQV + :value: 'SNEQV' + + + + .. py:attribute:: SNLIQ + :value: 'SNLIQ' + + + + .. py:attribute:: SNOWH + :value: 'SNOWH' + + + + .. py:attribute:: SNOWT_AVG + :value: 'SNOWT_AVG' + + + + .. py:attribute:: SOILICE + :value: 'SOILICE' + + + + .. py:attribute:: SOILSAT_TOP + :value: 'SOILSAT_TOP' + + + + .. py:attribute:: SOIL_M + :value: 'SOIL_M' + + + + .. py:attribute:: SOIL_T + :value: 'SOIL_T' + + + + +.. py:class:: LandShortVariablesEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + LandShortVariablesEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: ACCET + :value: 'ACCET' + + + + .. py:attribute:: SNOWT_AVG + :value: 'SNOWT_AVG' + + + + .. py:attribute:: SOILSAT_TOP + :value: 'SOILSAT_TOP' + + + + .. py:attribute:: FSNO + :value: 'FSNO' + + + + .. py:attribute:: SNOWH + :value: 'SNOWH' + + + + .. py:attribute:: SNEQV + :value: 'SNEQV' + + + + +.. py:class:: LandMediumVariablesEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + LandMediumVariablesEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: FSA + :value: 'FSA' + + + + .. py:attribute:: FIRA + :value: 'FIRA' + + + + .. py:attribute:: GRDFLX + :value: 'GRDFLX' + + + + .. py:attribute:: HFX + :value: 'HFX' + + + + .. py:attribute:: LH + :value: 'LH' + + + + .. py:attribute:: UGDRNOFF + :value: 'UGDRNOFF' + + + + .. py:attribute:: ACCECAN + :value: 'ACCECAN' + + + + .. py:attribute:: ACCEDIR + :value: 'ACCEDIR' + + + + .. py:attribute:: ACCETRAN + :value: 'ACCETRAN' + + + + .. py:attribute:: TRAD + :value: 'TRAD' + + + + .. py:attribute:: SNLIQ + :value: 'SNLIQ' + + + + .. py:attribute:: SOIL_T + :value: 'SOIL_T' + + + + .. py:attribute:: SOIL_M + :value: 'SOIL_M' + + + + .. py:attribute:: SNOWH + :value: 'SNOWH' + + + + .. py:attribute:: SNEQV + :value: 'SNEQV' + + + + .. py:attribute:: ISNOW + :value: 'ISNOW' + + + + .. py:attribute:: FSNO + :value: 'FSNO' + + + + .. py:attribute:: ACSNOM + :value: 'ACSNOM' + + + + .. py:attribute:: ACCET + :value: 'ACCET' + + + + .. py:attribute:: CANWAT + :value: 'CANWAT' + + + + .. py:attribute:: SOILICE + :value: 'SOILICE' + + + + .. py:attribute:: SOILSAT_TOP + :value: 'SOILSAT_TOP' + + + + .. py:attribute:: SNOWT_AVG + :value: 'SNOWT_AVG' + + + + +.. py:class:: LandLongVariablesEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + LandLongVariablesEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: UGDRNOFF + :value: 'UGDRNOFF' + + + + .. py:attribute:: SFCRNOFF + :value: 'SFCRNOFF' + + + + .. py:attribute:: SNEQV + :value: 'SNEQV' + + + + .. py:attribute:: ACSNOM + :value: 'ACSNOM' + + + + .. py:attribute:: ACCET + :value: 'ACCET' + + + + .. py:attribute:: CANWAT + :value: 'CANWAT' + + + + .. py:attribute:: SOILSAT_TOP + :value: 'SOILSAT_TOP' + + + + .. py:attribute:: SOILSAT + :value: 'SOILSAT' + + + + +.. py:class:: ForcingVariablesEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ForcingVariablesEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: U2D + :value: 'U2D' + + + + .. py:attribute:: V2D + :value: 'V2D' + + + + .. py:attribute:: T2D + :value: 'T2D' + + + + .. py:attribute:: Q2D + :value: 'Q2D' + + + + .. py:attribute:: LWDOWN + :value: 'LWDOWN' + + + + .. py:attribute:: SWDOWN + :value: 'SWDOWN' + + + + .. py:attribute:: RAINRATE + :value: 'RAINRATE' + + + + .. py:attribute:: PSFC + :value: 'PSFC' + + + + +.. py:class:: ShortAndAnalysisOutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ShortAndAnalysisOutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: land + :value: 'land' + + + + +.. py:class:: MediumOutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + MediumOutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: land_1 + :value: 'land_1' + + + + .. py:attribute:: land_2 + :value: 'land_2' + + + + .. py:attribute:: land_3 + :value: 'land_3' + + + + .. py:attribute:: land_4 + :value: 'land_4' + + + + .. py:attribute:: land_5 + :value: 'land_5' + + + + .. py:attribute:: land_6 + :value: 'land_6' + + + + .. py:attribute:: land_7 + :value: 'land_7' + + + + +.. py:class:: LongOutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + LongOutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: land_1 + :value: 'land_1' + + + + .. py:attribute:: land_2 + :value: 'land_2' + + + + .. py:attribute:: land_3 + :value: 'land_3' + + + + .. py:attribute:: land_4 + :value: 'land_4' + + + + +.. py:class:: ForcingOutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ForcingOutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: forcing + :value: 'forcing' + + + + +.. py:class:: Analysis(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + Analysis. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: ShortAndAnalysisOutputEnum + + + + .. py:attribute:: land + :type: Optional[LandAssimVariablesEnum] + + + + +.. py:class:: ShortRange(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + ShortRange. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: ShortAndAnalysisOutputEnum + + + + .. py:attribute:: land + :type: Optional[LandShortVariablesEnum] + + + + +.. py:class:: MediumRange(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRange. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: MediumOutputEnum + + + + .. py:attribute:: land_1 + :type: Optional[LandMediumVariablesEnum] + + + + .. py:attribute:: land_2 + :type: Optional[LandMediumVariablesEnum] + + + + .. py:attribute:: land_3 + :type: Optional[LandMediumVariablesEnum] + + + + .. py:attribute:: land_4 + :type: Optional[LandMediumVariablesEnum] + + + + .. py:attribute:: land_5 + :type: Optional[LandMediumVariablesEnum] + + + + .. py:attribute:: land_6 + :type: Optional[LandMediumVariablesEnum] + + + + .. py:attribute:: land_7 + :type: Optional[LandMediumVariablesEnum] + + + + +.. py:class:: LongRange(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + LongRange. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: LongOutputEnum + + + + .. py:attribute:: land_1 + :type: Optional[LandLongVariablesEnum] + + + + .. py:attribute:: land_2 + :type: Optional[LandLongVariablesEnum] + + + + .. py:attribute:: land_3 + :type: Optional[LandLongVariablesEnum] + + + + .. py:attribute:: land_4 + :type: Optional[LandLongVariablesEnum] + + + + +.. py:class:: Forcing(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + Forcing. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: ForcingOutputEnum + + + + .. py:attribute:: forcing + :type: Optional[ForcingVariablesEnum] + + + + +.. py:class:: ConfigurationsEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ConfigurationsEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: analysis_assim + :value: 'analysis_assim' + + + + .. py:attribute:: analysis_assim_no_da + :value: 'analysis_assim_no_da' + + + + .. py:attribute:: analysis_assim_extend + :value: 'analysis_assim_extend' + + + + .. py:attribute:: analysis_assim_extend_no_da + :value: 'analysis_assim_extend_no_da' + + + + .. py:attribute:: analysis_assim_long + :value: 'analysis_assim_long' + + + + .. py:attribute:: analysis_assim_long_no_da + :value: 'analysis_assim_long_no_da' + + + + .. py:attribute:: analysis_assim_hawaii + :value: 'analysis_assim_hawaii' + + + + .. py:attribute:: analysis_assim_hawaii_no_da + :value: 'analysis_assim_hawaii_no_da' + + + + .. py:attribute:: analysis_assim_puertorico + :value: 'analysis_assim_puertorico' + + + + .. py:attribute:: analysis_assim_puertorico_no_da + :value: 'analysis_assim_puertorico_no_da' + + + + .. py:attribute:: short_range + :value: 'short_range' + + + + .. py:attribute:: short_range_hawaii + :value: 'short_range_hawaii' + + + + .. py:attribute:: short_range_puertorico + :value: 'short_range_puertorico' + + + + .. py:attribute:: short_range_hawaii_no_da + :value: 'short_range_hawaii_no_da' + + + + .. py:attribute:: short_range_puertorico_no_da + :value: 'short_range_puertorico_no_da' + + + + .. py:attribute:: medium_range_mem1 + :value: 'medium_range_mem1' + + + + .. py:attribute:: medium_range_mem2 + :value: 'medium_range_mem2' + + + + .. py:attribute:: medium_range_mem3 + :value: 'medium_range_mem3' + + + + .. py:attribute:: medium_range_mem4 + :value: 'medium_range_mem4' + + + + .. py:attribute:: medium_range_mem5 + :value: 'medium_range_mem5' + + + + .. py:attribute:: medium_range_mem6 + :value: 'medium_range_mem6' + + + + .. py:attribute:: medium_range_mem7 + :value: 'medium_range_mem7' + + + + .. py:attribute:: long_range_mem1 + :value: 'long_range_mem1' + + + + .. py:attribute:: long_range_mem2 + :value: 'long_range_mem2' + + + + .. py:attribute:: long_range_mem3 + :value: 'long_range_mem3' + + + + .. py:attribute:: long_range_mem4 + :value: 'long_range_mem4' + + + + .. py:attribute:: forcing_medium_range + :value: 'forcing_medium_range' + + + + .. py:attribute:: forcing_short_range + :value: 'forcing_short_range' + + + + .. py:attribute:: forcing_short_range_hawaii + :value: 'forcing_short_range_hawaii' + + + + .. py:attribute:: forcing_short_range_puertorico + :value: 'forcing_short_range_puertorico' + + + + .. py:attribute:: forcing_analysis_assim + :value: 'forcing_analysis_assim' + + + + .. py:attribute:: forcing_analysis_assim_extend + :value: 'forcing_analysis_assim_extend' + + + + .. py:attribute:: forcing_analysis_assim_hawaii + :value: 'forcing_analysis_assim_hawaii' + + + + .. py:attribute:: forcing_analysis_assim_puertorico + :value: 'forcing_analysis_assim_puertorico' + + + + +.. py:class:: GridConfigurationModel(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + NWM v2.2 GridConfigurationModel. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: configuration + :type: ConfigurationsEnum + + + + .. py:attribute:: analysis_assim + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_no_da + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_extend + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_extend_no_da + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_long + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_long_no_da + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_hawaii + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_hawaii_no_da + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_puertorico + :type: Optional[Analysis] + + + + .. py:attribute:: analysis_assim_puertorico_no_da + :type: Optional[Analysis] + + + + .. py:attribute:: short_range + :type: Optional[ShortRange] + + + + .. py:attribute:: short_range_hawaii + :type: Optional[ShortRange] + + + + .. py:attribute:: short_range_puertorico + :type: Optional[ShortRange] + + + + .. py:attribute:: short_range_hawaii_no_da + :type: Optional[ShortRange] + + + + .. py:attribute:: short_range_puertorico_no_da + :type: Optional[ShortRange] + + + + .. py:attribute:: medium_range_mem1 + :type: Optional[MediumRange] + + + + .. py:attribute:: medium_range_mem2 + :type: Optional[MediumRange] + + + + .. py:attribute:: medium_range_mem3 + :type: Optional[MediumRange] + + + + .. py:attribute:: medium_range_mem4 + :type: Optional[MediumRange] + + + + .. py:attribute:: medium_range_mem5 + :type: Optional[MediumRange] + + + + .. py:attribute:: medium_range_mem6 + :type: Optional[MediumRange] + + + + .. py:attribute:: medium_range_mem7 + :type: Optional[MediumRange] + + + + .. py:attribute:: medium_range_no_da + :type: Optional[MediumRange] + + + + .. py:attribute:: long_range_mem1 + :type: Optional[LongRange] + + + + .. py:attribute:: long_range_mem2 + :type: Optional[LongRange] + + + + .. py:attribute:: long_range_mem3 + :type: Optional[LongRange] + + + + .. py:attribute:: long_range_mem4 + :type: Optional[LongRange] + + + + .. py:attribute:: forcing_medium_range + :type: Optional[Forcing] + + + + .. py:attribute:: forcing_short_range + :type: Optional[Forcing] + + + + .. py:attribute:: forcing_short_range_hawaii + :type: Optional[Forcing] + + + + .. py:attribute:: forcing_short_range_puertorico + :type: Optional[Forcing] + + + + .. py:attribute:: forcing_analysis_assim + :type: Optional[Forcing] + + + + .. py:attribute:: forcing_analysis_assim_extend + :type: Optional[Forcing] + + + + .. py:attribute:: forcing_analysis_assim_hawaii + :type: Optional[Forcing] + + + + .. py:attribute:: forcing_analysis_assim_puertorico + :type: Optional[Forcing] + + + + +.. py:data:: configuration + :value: 'forcing_medium_range' + + + diff --git a/_sources/autoapi/teehr/models/loading/nwm22_point/index.rst.txt b/_sources/autoapi/teehr/models/loading/nwm22_point/index.rst.txt new file mode 100644 index 00000000..c3170e3a --- /dev/null +++ b/_sources/autoapi/teehr/models/loading/nwm22_point/index.rst.txt @@ -0,0 +1,1746 @@ +:py:mod:`teehr.models.loading.nwm22_point` +========================================== + +.. py:module:: teehr.models.loading.nwm22_point + +.. autoapi-nested-parse:: + + Module describing NWM v2.2 point data configuration variables. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.nwm22_point.ChannelRtVariableEnum + teehr.models.loading.nwm22_point.ChannelRtNoDAVariableEnum + teehr.models.loading.nwm22_point.ChannelRtLongVariableEnum + teehr.models.loading.nwm22_point.TerrainRtVariableEnum + teehr.models.loading.nwm22_point.ReservoirVariableEnum + teehr.models.loading.nwm22_point.ShortAndAnalysisOutputEnum + teehr.models.loading.nwm22_point.Medium1OutputEnum + teehr.models.loading.nwm22_point.Medium2OutputEnum + teehr.models.loading.nwm22_point.Medium3OutputEnum + teehr.models.loading.nwm22_point.Medium4OutputEnum + teehr.models.loading.nwm22_point.Medium5OutputEnum + teehr.models.loading.nwm22_point.Medium6OutputEnum + teehr.models.loading.nwm22_point.Medium7OutputEnum + teehr.models.loading.nwm22_point.MediumNoDAEnum + teehr.models.loading.nwm22_point.Long1OutputEnum + teehr.models.loading.nwm22_point.Long2OutputEnum + teehr.models.loading.nwm22_point.Long3OutputEnum + teehr.models.loading.nwm22_point.Long4OutputEnum + teehr.models.loading.nwm22_point.ShortAndAnalysis + teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA + teehr.models.loading.nwm22_point.MediumRangeMem1 + teehr.models.loading.nwm22_point.MediumRangeMem2 + teehr.models.loading.nwm22_point.MediumRangeMem3 + teehr.models.loading.nwm22_point.MediumRangeMem4 + teehr.models.loading.nwm22_point.MediumRangeMem5 + teehr.models.loading.nwm22_point.MediumRangeMem6 + teehr.models.loading.nwm22_point.MediumRangeMem7 + teehr.models.loading.nwm22_point.MediumRangeNoDA + teehr.models.loading.nwm22_point.LongRangeMem1 + teehr.models.loading.nwm22_point.LongRangeMem2 + teehr.models.loading.nwm22_point.LongRangeMem3 + teehr.models.loading.nwm22_point.LongRangeMem4 + teehr.models.loading.nwm22_point.ConfigurationsEnum + teehr.models.loading.nwm22_point.PointConfigurationModel + + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.nwm22_point.configuration + + +.. py:class:: ChannelRtVariableEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ChannelRtVariableEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: nudge + :value: 'nudge' + + + + .. py:attribute:: qBtmVertRunoff + :value: 'qBtmVertRunoff' + + + + .. py:attribute:: qBucket + :value: 'qBucket' + + + + .. py:attribute:: qSfcLatRunoff + :value: 'qSfcLatRunoff' + + + + .. py:attribute:: streamflow + :value: 'streamflow' + + + + .. py:attribute:: velocity + :value: 'velocity' + + + + +.. py:class:: ChannelRtNoDAVariableEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ChannelRtNoDAVariableEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: nudge + :value: 'nudge' + + + + .. py:attribute:: qBucket + :value: 'qBucket' + + + + .. py:attribute:: qSfcLatRunoff + :value: 'qSfcLatRunoff' + + + + .. py:attribute:: streamflow + :value: 'streamflow' + + + + .. py:attribute:: velocity + :value: 'velocity' + + + + +.. py:class:: ChannelRtLongVariableEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ChannelRtLongVariableEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: nudge + :value: 'nudge' + + + + .. py:attribute:: streamflow + :value: 'streamflow' + + + + .. py:attribute:: velocity + :value: 'velocity' + + + + +.. py:class:: TerrainRtVariableEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + TerrainRtVariableEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: sfcheadsubrt + :value: 'sfcheadsubrt' + + + + .. py:attribute:: zwattablrt + :value: 'zwattablrt' + + + + +.. py:class:: ReservoirVariableEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ReservoirVariableEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: inflow + :value: 'inflow' + + + + .. py:attribute:: outflow + :value: 'outflow' + + + + .. py:attribute:: reservoir_assimiated_value + :value: 'reservoir_assimiated_value' + + + + .. py:attribute:: water_sfc_elev + :value: 'water_sfc_elev' + + + + +.. py:class:: ShortAndAnalysisOutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ShortAndAnalysisOutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt + :value: 'channel_rt' + + + + .. py:attribute:: terrain_rt + :value: 'terrain_rt' + + + + .. py:attribute:: reservoir + :value: 'reservoir' + + + + +.. py:class:: Medium1OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Medium1OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_1 + :value: 'channel_rt_1' + + + + .. py:attribute:: terrain_rt_1 + :value: 'terrain_rt_1' + + + + .. py:attribute:: reservoir_1 + :value: 'reservoir_1' + + + + +.. py:class:: Medium2OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Medium2OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_2 + :value: 'channel_rt_2' + + + + .. py:attribute:: terrain_rt_2 + :value: 'terrain_rt_2' + + + + .. py:attribute:: reservoir_2 + :value: 'reservoir_2' + + + + +.. py:class:: Medium3OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Medium3OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_3 + :value: 'channel_rt_3' + + + + .. py:attribute:: terrain_rt_3 + :value: 'terrain_rt_3' + + + + .. py:attribute:: reservoir_3 + :value: 'reservoir_3' + + + + +.. py:class:: Medium4OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Medium4OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_4 + :value: 'channel_rt_4' + + + + .. py:attribute:: terrain_rt_4 + :value: 'terrain_rt_4' + + + + .. py:attribute:: reservoir_4 + :value: 'reservoir_4' + + + + +.. py:class:: Medium5OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Medium5OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_5 + :value: 'channel_rt_5' + + + + .. py:attribute:: terrain_rt_5 + :value: 'terrain_rt_5' + + + + .. py:attribute:: reservoir_5 + :value: 'reservoir_5' + + + + +.. py:class:: Medium6OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Medium6OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_6 + :value: 'channel_rt_6' + + + + .. py:attribute:: terrain_rt_6 + :value: 'terrain_rt_6' + + + + .. py:attribute:: reservoir_6 + :value: 'reservoir_6' + + + + +.. py:class:: Medium7OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Medium7OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_7 + :value: 'channel_rt_7' + + + + .. py:attribute:: terrain_rt_7 + :value: 'terrain_rt_7' + + + + .. py:attribute:: reservoir_7 + :value: 'reservoir_7' + + + + +.. py:class:: MediumNoDAEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + MediumNoDAEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt + :value: 'channel_rt' + + + + +.. py:class:: Long1OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Long1OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_1 + :value: 'channel_rt_1' + + + + .. py:attribute:: reservoir_1 + :value: 'reservoir_1' + + + + +.. py:class:: Long2OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Long2OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_2 + :value: 'channel_rt_2' + + + + .. py:attribute:: reservoir_2 + :value: 'reservoir_2' + + + + +.. py:class:: Long3OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Long3OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_3 + :value: 'channel_rt_3' + + + + .. py:attribute:: reservoir_3 + :value: 'reservoir_3' + + + + +.. py:class:: Long4OutputEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Long4OutputEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: channel_rt_4 + :value: 'channel_rt_4' + + + + .. py:attribute:: reservoir_4 + :value: 'reservoir_4' + + + + +.. py:class:: ShortAndAnalysis(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + ShortAndAnalysis. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: ShortAndAnalysisOutputEnum + + + + .. py:attribute:: channel_rt + :type: Optional[ChannelRtVariableEnum] + + + + .. py:attribute:: terrain_rt + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: ShortAndAnalysisNoDA(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + ShortAndAnalysisNoDA. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: ShortAndAnalysisOutputEnum + + + + .. py:attribute:: channel_rt + :type: Optional[ChannelRtNoDAVariableEnum] + + + + .. py:attribute:: terrain_rt + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: MediumRangeMem1(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRangeMem1. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Medium1OutputEnum + + + + .. py:attribute:: channel_rt_1 + :type: Optional[ChannelRtVariableEnum] + + + + .. py:attribute:: terrain_rt_1 + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir_1 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: MediumRangeMem2(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRangeMem2. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Medium2OutputEnum + + + + .. py:attribute:: channel_rt_2 + :type: Optional[ChannelRtVariableEnum] + + + + .. py:attribute:: terrain_rt_2 + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir_2 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: MediumRangeMem3(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRangeMem3. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Medium3OutputEnum + + + + .. py:attribute:: channel_rt_3 + :type: Optional[ChannelRtVariableEnum] + + + + .. py:attribute:: terrain_rt_3 + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir_3 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: MediumRangeMem4(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRangeMem4. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Medium4OutputEnum + + + + .. py:attribute:: channel_rt_4 + :type: Optional[ChannelRtVariableEnum] + + + + .. py:attribute:: terrain_rt_4 + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir_4 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: MediumRangeMem5(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRangeMem5. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Medium5OutputEnum + + + + .. py:attribute:: channel_rt_5 + :type: Optional[ChannelRtVariableEnum] + + + + .. py:attribute:: terrain_rt_5 + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir_5 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: MediumRangeMem6(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRangeMem6. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Medium6OutputEnum + + + + .. py:attribute:: channel_rt_6 + :type: Optional[ChannelRtVariableEnum] + + + + .. py:attribute:: terrain_rt_6 + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir_6 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: MediumRangeMem7(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRangeMem7. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Medium7OutputEnum + + + + .. py:attribute:: channel_rt_7 + :type: Optional[ChannelRtVariableEnum] + + + + .. py:attribute:: terrain_rt_7 + :type: Optional[TerrainRtVariableEnum] + + + + .. py:attribute:: reservoir_7 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: MediumRangeNoDA(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + MediumRangeNoDA. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: MediumNoDAEnum + + + + .. py:attribute:: channel_rt + :type: Optional[ChannelRtNoDAVariableEnum] + + + + +.. py:class:: LongRangeMem1(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + LongRangeMem1. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Long1OutputEnum + + + + .. py:attribute:: channel_rt_1 + :type: Optional[ChannelRtLongVariableEnum] + + + + .. py:attribute:: reservoir_1 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: LongRangeMem2(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + LongRangeMem2. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Long2OutputEnum + + + + .. py:attribute:: channel_rt_2 + :type: Optional[ChannelRtLongVariableEnum] + + + + .. py:attribute:: reservoir_2 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: LongRangeMem3(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + LongRangeMem3. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Long3OutputEnum + + + + .. py:attribute:: channel_rt_3 + :type: Optional[ChannelRtLongVariableEnum] + + + + .. py:attribute:: reservoir_3 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: LongRangeMem4(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + LongRangeMem4. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: output_type + :type: Long4OutputEnum + + + + .. py:attribute:: channel_rt_4 + :type: Optional[ChannelRtLongVariableEnum] + + + + .. py:attribute:: reservoir_4 + :type: Optional[ReservoirVariableEnum] + + + + +.. py:class:: ConfigurationsEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ConfigurationsEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: analysis_assim + :value: 'analysis_assim' + + + + .. py:attribute:: analysis_assim_no_da + :value: 'analysis_assim_no_da' + + + + .. py:attribute:: analysis_assim_extend + :value: 'analysis_assim_extend' + + + + .. py:attribute:: analysis_assim_extend_no_da + :value: 'analysis_assim_extend_no_da' + + + + .. py:attribute:: analysis_assim_long + :value: 'analysis_assim_long' + + + + .. py:attribute:: analysis_assim_long_no_da + :value: 'analysis_assim_long_no_da' + + + + .. py:attribute:: analysis_assim_hawaii + :value: 'analysis_assim_hawaii' + + + + .. py:attribute:: analysis_assim_hawaii_no_da + :value: 'analysis_assim_hawaii_no_da' + + + + .. py:attribute:: analysis_assim_puertorico + :value: 'analysis_assim_puertorico' + + + + .. py:attribute:: analysis_assim_puertorico_no_da + :value: 'analysis_assim_puertorico_no_da' + + + + .. py:attribute:: short_range + :value: 'short_range' + + + + .. py:attribute:: short_range_hawaii + :value: 'short_range_hawaii' + + + + .. py:attribute:: short_range_puertorico + :value: 'short_range_puertorico' + + + + .. py:attribute:: short_range_hawaii_no_da + :value: 'short_range_hawaii_no_da' + + + + .. py:attribute:: short_range_puertorico_no_da + :value: 'short_range_puertorico_no_da' + + + + .. py:attribute:: medium_range_mem1 + :value: 'medium_range_mem1' + + + + .. py:attribute:: medium_range_mem2 + :value: 'medium_range_mem2' + + + + .. py:attribute:: medium_range_mem3 + :value: 'medium_range_mem3' + + + + .. py:attribute:: medium_range_mem4 + :value: 'medium_range_mem4' + + + + .. py:attribute:: medium_range_mem5 + :value: 'medium_range_mem5' + + + + .. py:attribute:: medium_range_mem6 + :value: 'medium_range_mem6' + + + + .. py:attribute:: medium_range_mem7 + :value: 'medium_range_mem7' + + + + .. py:attribute:: medium_range_no_da + :value: 'medium_range_no_da' + + + + .. py:attribute:: long_range_mem1 + :value: 'long_range_mem1' + + + + .. py:attribute:: long_range_mem2 + :value: 'long_range_mem2' + + + + .. py:attribute:: long_range_mem3 + :value: 'long_range_mem3' + + + + .. py:attribute:: long_range_mem4 + :value: 'long_range_mem4' + + + + +.. py:class:: PointConfigurationModel(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + NWM v2.2 PointConfigurationModel. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: configuration + :type: ConfigurationsEnum + + + + .. py:attribute:: analysis_assim + :type: Optional[ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_no_da + :type: Optional[ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_extend + :type: Optional[ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_extend_no_da + :type: Optional[ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_long + :type: Optional[ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_long_no_da + :type: Optional[ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_hawaii + :type: Optional[ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_hawaii_no_da + :type: Optional[ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_puertorico + :type: Optional[ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_puertorico_no_da + :type: Optional[ShortAndAnalysisNoDA] + + + + .. py:attribute:: short_range + :type: Optional[ShortAndAnalysis] + + + + .. py:attribute:: short_range_hawaii + :type: Optional[ShortAndAnalysis] + + + + .. py:attribute:: short_range_puertorico + :type: Optional[ShortAndAnalysis] + + + + .. py:attribute:: short_range_hawaii_no_da + :type: Optional[ShortAndAnalysisNoDA] + + + + .. py:attribute:: short_range_puertorico_no_da + :type: Optional[ShortAndAnalysisNoDA] + + + + .. py:attribute:: medium_range_mem1 + :type: Optional[MediumRangeMem1] + + + + .. py:attribute:: medium_range_mem2 + :type: Optional[MediumRangeMem2] + + + + .. py:attribute:: medium_range_mem3 + :type: Optional[MediumRangeMem3] + + + + .. py:attribute:: medium_range_mem4 + :type: Optional[MediumRangeMem4] + + + + .. py:attribute:: medium_range_mem5 + :type: Optional[MediumRangeMem5] + + + + .. py:attribute:: medium_range_mem6 + :type: Optional[MediumRangeMem6] + + + + .. py:attribute:: medium_range_mem7 + :type: Optional[MediumRangeMem7] + + + + .. py:attribute:: medium_range_no_da + :type: Optional[MediumRangeNoDA] + + + + .. py:attribute:: long_range_mem1 + :type: Optional[LongRangeMem1] + + + + .. py:attribute:: long_range_mem2 + :type: Optional[LongRangeMem2] + + + + .. py:attribute:: long_range_mem3 + :type: Optional[LongRangeMem3] + + + + .. py:attribute:: long_range_mem4 + :type: Optional[LongRangeMem4] + + + + +.. py:data:: configuration + :value: 'medium_range_mem1' + + + diff --git a/_sources/autoapi/teehr/models/loading/nwm30_grid/index.rst.txt b/_sources/autoapi/teehr/models/loading/nwm30_grid/index.rst.txt new file mode 100644 index 00000000..7f56e2c1 --- /dev/null +++ b/_sources/autoapi/teehr/models/loading/nwm30_grid/index.rst.txt @@ -0,0 +1,492 @@ +:py:mod:`teehr.models.loading.nwm30_grid` +========================================= + +.. py:module:: teehr.models.loading.nwm30_grid + +.. autoapi-nested-parse:: + + Module describing NWM v3.0 grid configuration variables. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.nwm30_grid.ConfigurationsEnum + teehr.models.loading.nwm30_grid.GridConfigurationModel + + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.nwm30_grid.configuration + + +.. py:class:: ConfigurationsEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ConfigurationsEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: analysis_assim + :value: 'analysis_assim' + + + + .. py:attribute:: analysis_assim_no_da + :value: 'analysis_assim_no_da' + + + + .. py:attribute:: analysis_assim_extend + :value: 'analysis_assim_extend' + + + + .. py:attribute:: analysis_assim_extend_no_da + :value: 'analysis_assim_extend_no_da' + + + + .. py:attribute:: analysis_assim_long + :value: 'analysis_assim_long' + + + + .. py:attribute:: analysis_assim_long_no_da + :value: 'analysis_assim_long_no_da' + + + + .. py:attribute:: analysis_assim_hawaii + :value: 'analysis_assim_hawaii' + + + + .. py:attribute:: analysis_assim_hawaii_no_da + :value: 'analysis_assim_hawaii_no_da' + + + + .. py:attribute:: analysis_assim_puertorico + :value: 'analysis_assim_puertorico' + + + + .. py:attribute:: analysis_assim_puertorico_no_da + :value: 'analysis_assim_puertorico_no_da' + + + + .. py:attribute:: short_range + :value: 'short_range' + + + + .. py:attribute:: short_range_hawaii + :value: 'short_range_hawaii' + + + + .. py:attribute:: short_range_puertorico + :value: 'short_range_puertorico' + + + + .. py:attribute:: short_range_hawaii_no_da + :value: 'short_range_hawaii_no_da' + + + + .. py:attribute:: short_range_puertorico_no_da + :value: 'short_range_puertorico_no_da' + + + + .. py:attribute:: medium_range_mem1 + :value: 'medium_range_mem1' + + + + .. py:attribute:: medium_range_mem2 + :value: 'medium_range_mem2' + + + + .. py:attribute:: medium_range_mem3 + :value: 'medium_range_mem3' + + + + .. py:attribute:: medium_range_mem4 + :value: 'medium_range_mem4' + + + + .. py:attribute:: medium_range_mem5 + :value: 'medium_range_mem5' + + + + .. py:attribute:: medium_range_mem6 + :value: 'medium_range_mem6' + + + + .. py:attribute:: medium_range_mem7 + :value: 'medium_range_mem7' + + + + .. py:attribute:: long_range_mem1 + :value: 'long_range_mem1' + + + + .. py:attribute:: long_range_mem2 + :value: 'long_range_mem2' + + + + .. py:attribute:: long_range_mem3 + :value: 'long_range_mem3' + + + + .. py:attribute:: long_range_mem4 + :value: 'long_range_mem4' + + + + .. py:attribute:: forcing_medium_range + :value: 'forcing_medium_range' + + + + .. py:attribute:: forcing_medium_range_blend + :value: 'forcing_medium_range_blend' + + + + .. py:attribute:: forcing_medium_range_alaska + :value: 'forcing_medium_range_alaska' + + + + .. py:attribute:: forcing_medium_range_blend_alaska + :value: 'forcing_medium_range_blend_alaska' + + + + .. py:attribute:: forcing_short_range + :value: 'forcing_short_range' + + + + .. py:attribute:: forcing_short_range_hawaii + :value: 'forcing_short_range_hawaii' + + + + .. py:attribute:: forcing_short_range_puertorico + :value: 'forcing_short_range_puertorico' + + + + .. py:attribute:: forcing_short_range_alaska + :value: 'forcing_short_range_alaska' + + + + .. py:attribute:: forcing_analysis_assim + :value: 'forcing_analysis_assim' + + + + .. py:attribute:: forcing_analysis_assim_extend + :value: 'forcing_analysis_assim_extend' + + + + .. py:attribute:: forcing_analysis_assim_hawaii + :value: 'forcing_analysis_assim_hawaii' + + + + .. py:attribute:: forcing_analysis_assim_puertorico + :value: 'forcing_analysis_assim_puertorico' + + + + .. py:attribute:: forcing_analysis_assim_alaska + :value: 'forcing_analysis_assim_alaska' + + + + +.. py:class:: GridConfigurationModel(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + NWM v3.0 GridConfigurationModel. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: configuration + :type: ConfigurationsEnum + + + + .. py:attribute:: analysis_assim + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_no_da + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_extend + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_extend_no_da + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_long + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_long_no_da + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_hawaii + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_hawaii_no_da + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_puertorico + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: analysis_assim_puertorico_no_da + :type: Optional[teehr.models.loading.nwm22_grid.Analysis] + + + + .. py:attribute:: short_range + :type: Optional[teehr.models.loading.nwm22_grid.ShortRange] + + + + .. py:attribute:: short_range_hawaii + :type: Optional[teehr.models.loading.nwm22_grid.ShortRange] + + + + .. py:attribute:: short_range_puertorico + :type: Optional[teehr.models.loading.nwm22_grid.ShortRange] + + + + .. py:attribute:: short_range_hawaii_no_da + :type: Optional[teehr.models.loading.nwm22_grid.ShortRange] + + + + .. py:attribute:: short_range_puertorico_no_da + :type: Optional[teehr.models.loading.nwm22_grid.ShortRange] + + + + .. py:attribute:: medium_range_mem1 + :type: Optional[teehr.models.loading.nwm22_grid.MediumRange] + + + + .. py:attribute:: medium_range_mem2 + :type: Optional[teehr.models.loading.nwm22_grid.MediumRange] + + + + .. py:attribute:: medium_range_mem3 + :type: Optional[teehr.models.loading.nwm22_grid.MediumRange] + + + + .. py:attribute:: medium_range_mem4 + :type: Optional[teehr.models.loading.nwm22_grid.MediumRange] + + + + .. py:attribute:: medium_range_mem5 + :type: Optional[teehr.models.loading.nwm22_grid.MediumRange] + + + + .. py:attribute:: medium_range_mem6 + :type: Optional[teehr.models.loading.nwm22_grid.MediumRange] + + + + .. py:attribute:: medium_range_mem7 + :type: Optional[teehr.models.loading.nwm22_grid.MediumRange] + + + + .. py:attribute:: medium_range_no_da + :type: Optional[teehr.models.loading.nwm22_grid.MediumRange] + + + + .. py:attribute:: long_range_mem1 + :type: Optional[teehr.models.loading.nwm22_grid.LongRange] + + + + .. py:attribute:: long_range_mem2 + :type: Optional[teehr.models.loading.nwm22_grid.LongRange] + + + + .. py:attribute:: long_range_mem3 + :type: Optional[teehr.models.loading.nwm22_grid.LongRange] + + + + .. py:attribute:: long_range_mem4 + :type: Optional[teehr.models.loading.nwm22_grid.LongRange] + + + + .. py:attribute:: forcing_medium_range + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_medium_range_blend + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_medium_range_alaska + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_medium_range_blend_alaska + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_short_range + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_short_range_hawaii + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_short_range_puertorico + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_short_range_alaska + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_analysis_assim + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_analysis_assim_extend + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_analysis_assim_hawaii + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_analysis_assim_puertorico + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + .. py:attribute:: forcing_analysis_assim_alaska + :type: Optional[teehr.models.loading.nwm22_grid.Forcing] + + + + +.. py:data:: configuration + :value: 'forcing_medium_range_blend_alaska' + + + diff --git a/_sources/autoapi/teehr/models/loading/nwm30_point/index.rst.txt b/_sources/autoapi/teehr/models/loading/nwm30_point/index.rst.txt new file mode 100644 index 00000000..d5735103 --- /dev/null +++ b/_sources/autoapi/teehr/models/loading/nwm30_point/index.rst.txt @@ -0,0 +1,507 @@ +:py:mod:`teehr.models.loading.nwm30_point` +========================================== + +.. py:module:: teehr.models.loading.nwm30_point + +.. autoapi-nested-parse:: + + Module describing NWM v3.0 point configuration variables. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.nwm30_point.ConfigurationsEnum + teehr.models.loading.nwm30_point.PointConfigurationModel + + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.nwm30_point.configuration + + +.. py:class:: ConfigurationsEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ConfigurationsEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: analysis_assim + :value: 'analysis_assim' + + + + .. py:attribute:: analysis_assim_no_da + :value: 'analysis_assim_no_da' + + + + .. py:attribute:: analysis_assim_extend + :value: 'analysis_assim_extend' + + + + .. py:attribute:: analysis_assim_extend_no_da + :value: 'analysis_assim_extend_no_da' + + + + .. py:attribute:: analysis_assim_long + :value: 'analysis_assim_long' + + + + .. py:attribute:: analysis_assim_long_no_da + :value: 'analysis_assim_long_no_da' + + + + .. py:attribute:: analysis_assim_hawaii + :value: 'analysis_assim_hawaii' + + + + .. py:attribute:: analysis_assim_hawaii_no_da + :value: 'analysis_assim_hawaii_no_da' + + + + .. py:attribute:: analysis_assim_puertorico + :value: 'analysis_assim_puertorico' + + + + .. py:attribute:: analysis_assim_puertorico_no_da + :value: 'analysis_assim_puertorico_no_da' + + + + .. py:attribute:: analysis_assim_alaska + :value: 'analysis_assim_alaska' + + + + .. py:attribute:: analysis_assim_alaska_no_da + :value: 'analysis_assim_alaska_no_da' + + + + .. py:attribute:: analysis_assim_extend_alaska + :value: 'analysis_assim_extend_alaska' + + + + .. py:attribute:: analysis_assim_extend_alaska_no_da + :value: 'analysis_assim_extend_alaska_no_da' + + + + .. py:attribute:: short_range + :value: 'short_range' + + + + .. py:attribute:: short_range_hawaii + :value: 'short_range_hawaii' + + + + .. py:attribute:: short_range_puertorico + :value: 'short_range_puertorico' + + + + .. py:attribute:: short_range_hawaii_no_da + :value: 'short_range_hawaii_no_da' + + + + .. py:attribute:: short_range_puertorico_no_da + :value: 'short_range_puertorico_no_da' + + + + .. py:attribute:: short_range_alaska + :value: 'short_range_alaska' + + + + .. py:attribute:: medium_range_mem1 + :value: 'medium_range_mem1' + + + + .. py:attribute:: medium_range_mem2 + :value: 'medium_range_mem2' + + + + .. py:attribute:: medium_range_mem3 + :value: 'medium_range_mem3' + + + + .. py:attribute:: medium_range_mem4 + :value: 'medium_range_mem4' + + + + .. py:attribute:: medium_range_mem5 + :value: 'medium_range_mem5' + + + + .. py:attribute:: medium_range_mem6 + :value: 'medium_range_mem6' + + + + .. py:attribute:: medium_range_mem7 + :value: 'medium_range_mem7' + + + + .. py:attribute:: medium_range_no_da + :value: 'medium_range_no_da' + + + + .. py:attribute:: medium_range_alaska_mem1 + :value: 'medium_range_alaska_mem1' + + + + .. py:attribute:: medium_range_alaska_mem2 + :value: 'medium_range_alaska_mem2' + + + + .. py:attribute:: medium_range_alaska_mem3 + :value: 'medium_range_alaska_mem3' + + + + .. py:attribute:: medium_range_alaska_mem4 + :value: 'medium_range_alaska_mem4' + + + + .. py:attribute:: medium_range_alaska_mem5 + :value: 'medium_range_alaska_mem5' + + + + .. py:attribute:: medium_range_alaska_mem6 + :value: 'medium_range_alaska_mem6' + + + + .. py:attribute:: medium_range_alaska_no_da + :value: 'medium_range_alaska_no_da' + + + + .. py:attribute:: medium_range_blend + :value: 'medium_range_blend' + + + + .. py:attribute:: medium_range_blend_alaska + :value: 'medium_range_blend_alaska' + + + + .. py:attribute:: long_range_mem1 + :value: 'long_range_mem1' + + + + .. py:attribute:: long_range_mem2 + :value: 'long_range_mem2' + + + + .. py:attribute:: long_range_mem3 + :value: 'long_range_mem3' + + + + .. py:attribute:: long_range_mem4 + :value: 'long_range_mem4' + + + + +.. py:class:: PointConfigurationModel(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + NWM v3.0 PointConfigurationModel. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: configuration + :type: ConfigurationsEnum + + + + .. py:attribute:: analysis_assim + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_extend + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_extend_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_long + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_long_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_hawaii + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_hawaii_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_puertorico + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_puertorico_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_alaska + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_alaska_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: analysis_assim_extend_alaska + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: analysis_assim_extend_alaska_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: short_range + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: short_range_hawaii + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: short_range_puertorico + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: short_range_hawaii_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: short_range_puertorico_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: short_range_alaska + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: medium_range_mem1 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem1] + + + + .. py:attribute:: medium_range_mem2 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem2] + + + + .. py:attribute:: medium_range_mem3 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem3] + + + + .. py:attribute:: medium_range_mem4 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem4] + + + + .. py:attribute:: medium_range_mem5 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem5] + + + + .. py:attribute:: medium_range_mem6 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem6] + + + + .. py:attribute:: medium_range_mem7 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem7] + + + + .. py:attribute:: medium_range_no_da + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeNoDA] + + + + .. py:attribute:: medium_range_alaska_mem1 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem1] + + + + .. py:attribute:: medium_range_alaska_mem2 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem2] + + + + .. py:attribute:: medium_range_alaska_mem3 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem3] + + + + .. py:attribute:: medium_range_alaska_mem4 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem4] + + + + .. py:attribute:: medium_range_alaska_mem5 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem5] + + + + .. py:attribute:: medium_range_alaska_mem6 + :type: Optional[teehr.models.loading.nwm22_point.MediumRangeMem6] + + + + .. py:attribute:: medium_range_alaska_no_da + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysisNoDA] + + + + .. py:attribute:: medium_range_blend + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: medium_range_blend_alaska + :type: Optional[teehr.models.loading.nwm22_point.ShortAndAnalysis] + + + + .. py:attribute:: long_range_mem1 + :type: Optional[teehr.models.loading.nwm22_point.LongRangeMem1] + + + + .. py:attribute:: long_range_mem2 + :type: Optional[teehr.models.loading.nwm22_point.LongRangeMem2] + + + + .. py:attribute:: long_range_mem3 + :type: Optional[teehr.models.loading.nwm22_point.LongRangeMem3] + + + + .. py:attribute:: long_range_mem4 + :type: Optional[teehr.models.loading.nwm22_point.LongRangeMem4] + + + + +.. py:data:: configuration + :value: 'short_range_alaska' + + + diff --git a/_sources/autoapi/teehr/models/loading/utils/index.rst.txt b/_sources/autoapi/teehr/models/loading/utils/index.rst.txt new file mode 100644 index 00000000..8c23ae72 --- /dev/null +++ b/_sources/autoapi/teehr/models/loading/utils/index.rst.txt @@ -0,0 +1,287 @@ +:py:mod:`teehr.models.loading.utils` +==================================== + +.. py:module:: teehr.models.loading.utils + +.. autoapi-nested-parse:: + + Module for NWM loading models. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + teehr.models.loading.utils.ChunkByEnum + teehr.models.loading.utils.SupportedNWMRetroVersionsEnum + teehr.models.loading.utils.SupportedNWMOperationalVersionsEnum + teehr.models.loading.utils.SupportedNWMDataSourcesEnum + teehr.models.loading.utils.SupportedKerchunkMethod + teehr.models.loading.utils.SupportedNWMRetroDomainsEnum + + + + +.. py:class:: ChunkByEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + ChunkByEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: day + :value: 'day' + + + + .. py:attribute:: location_id + :value: 'location_id' + + + + .. py:attribute:: week + :value: 'week' + + + + .. py:attribute:: month + :value: 'month' + + + + .. py:attribute:: year + :value: 'year' + + + + +.. py:class:: SupportedNWMRetroVersionsEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + SupportedNWMRetroVersionsEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: nwm20 + :value: 'nwm20' + + + + .. py:attribute:: nwm21 + :value: 'nwm21' + + + + .. py:attribute:: nwm30 + :value: 'nwm30' + + + + +.. py:class:: SupportedNWMOperationalVersionsEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + SupportedNWMOperationalVersionsEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: nwm22 + :value: 'nwm22' + + + + .. py:attribute:: nwm30 + :value: 'nwm30' + + + + +.. py:class:: SupportedNWMDataSourcesEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + SupportedNWMDataSourcesEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: GCS + :value: 'GCS' + + + + .. py:attribute:: NOMADS + :value: 'NOMADS' + + + + .. py:attribute:: DSTOR + :value: 'DSTOR' + + + + +.. py:class:: SupportedKerchunkMethod + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + SupportedKerchunkMethod. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: local + :value: 'local' + + + + .. py:attribute:: remote + :value: 'remote' + + + + .. py:attribute:: auto + :value: 'auto' + + + + +.. py:class:: SupportedNWMRetroDomainsEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + SupportedNWMRetroDomainsEnum. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: CONUS + :value: 'CONUS' + + + + .. py:attribute:: Alaska + :value: 'Alaska' + + + + .. py:attribute:: PR + :value: 'PR' + + + + .. py:attribute:: Hawaii + :value: 'Hawaii' + + + + diff --git a/_sources/autoapi/teehr/models/queries/index.rst.txt b/_sources/autoapi/teehr/models/queries/index.rst.txt new file mode 100644 index 00000000..bfd54cc9 --- /dev/null +++ b/_sources/autoapi/teehr/models/queries/index.rst.txt @@ -0,0 +1,1007 @@ +:py:mod:`teehr.models.queries` +============================== + +.. py:module:: teehr.models.queries + +.. autoapi-nested-parse:: + + Module for parquet-based query models. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + teehr.models.queries.BaseModel + teehr.models.queries.FilterOperatorEnum + teehr.models.queries.MetricEnum + teehr.models.queries.JoinedFilterFieldEnum + teehr.models.queries.TimeseriesFilterFieldEnum + teehr.models.queries.JoinedFilter + teehr.models.queries.TimeseriesFilter + teehr.models.queries.MetricQuery + teehr.models.queries.JoinedTimeseriesQuery + teehr.models.queries.TimeseriesQuery + teehr.models.queries.TimeseriesCharQuery + + + + +.. py:class:: BaseModel(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + Basemodel configuration. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:class:: ConfigDict + + + + ConfigDict. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: arbitrary_types_allowed + :value: True + + + + + +.. py:class:: FilterOperatorEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Filter symbols. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: eq + :value: '=' + + + + .. py:attribute:: gt + :value: '>' + + + + .. py:attribute:: lt + :value: '<' + + + + .. py:attribute:: gte + :value: '>=' + + + + .. py:attribute:: lte + :value: '<=' + + + + .. py:attribute:: islike + :value: 'like' + + + + .. py:attribute:: isin + :value: 'in' + + + + +.. py:class:: MetricEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Available metrics. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: primary_count + :value: 'primary_count' + + + + .. py:attribute:: secondary_count + :value: 'secondary_count' + + + + .. py:attribute:: primary_minimum + :value: 'primary_minimum' + + + + .. py:attribute:: secondary_minimum + :value: 'secondary_minimum' + + + + .. py:attribute:: primary_maximum + :value: 'primary_maximum' + + + + .. py:attribute:: secondary_maximum + :value: 'secondary_maximum' + + + + .. py:attribute:: primary_average + :value: 'primary_average' + + + + .. py:attribute:: secondary_average + :value: 'secondary_average' + + + + .. py:attribute:: primary_sum + :value: 'primary_sum' + + + + .. py:attribute:: secondary_sum + :value: 'secondary_sum' + + + + .. py:attribute:: primary_variance + :value: 'primary_variance' + + + + .. py:attribute:: secondary_variance + :value: 'secondary_variance' + + + + .. py:attribute:: max_value_delta + :value: 'max_value_delta' + + + + .. py:attribute:: bias + :value: 'bias' + + + + .. py:attribute:: nash_sutcliffe_efficiency + :value: 'nash_sutcliffe_efficiency' + + + + .. py:attribute:: kling_gupta_efficiency + :value: 'kling_gupta_efficiency' + + + + .. py:attribute:: mean_error + :value: 'mean_error' + + + + .. py:attribute:: mean_squared_error + :value: 'mean_squared_error' + + + + .. py:attribute:: root_mean_squared_error + :value: 'root_mean_squared_error' + + + + .. py:attribute:: primary_max_value_time + :value: 'primary_max_value_time' + + + + .. py:attribute:: secondary_max_value_time + :value: 'secondary_max_value_time' + + + + .. py:attribute:: max_value_timedelta + :value: 'max_value_timedelta' + + + + +.. py:class:: JoinedFilterFieldEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Joined filter fields. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: value_time + :value: 'value_time' + + + + .. py:attribute:: reference_time + :value: 'reference_time' + + + + .. py:attribute:: secondary_location_id + :value: 'secondary_location_id' + + + + .. py:attribute:: secondary_value + :value: 'secondary_value' + + + + .. py:attribute:: configuration + :value: 'configuration' + + + + .. py:attribute:: measurement_unit + :value: 'measurement_unit' + + + + .. py:attribute:: variable_name + :value: 'variable_name' + + + + .. py:attribute:: primary_value + :value: 'primary_value' + + + + .. py:attribute:: primary_location_id + :value: 'primary_location_id' + + + + .. py:attribute:: lead_time + :value: 'lead_time' + + + + .. py:attribute:: geometry + :value: 'geometry' + + + + +.. py:class:: TimeseriesFilterFieldEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Timeseries filter fields. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: value_time + :value: 'value_time' + + + + .. py:attribute:: reference_time + :value: 'reference_time' + + + + .. py:attribute:: location_id + :value: 'location_id' + + + + .. py:attribute:: value + :value: 'value' + + + + .. py:attribute:: configuration + :value: 'configuration' + + + + .. py:attribute:: measurement_unit + :value: 'measurement_unit' + + + + .. py:attribute:: variable_name + :value: 'variable_name' + + + + .. py:attribute:: lead_time + :value: 'lead_time' + + + + .. py:attribute:: geometry + :value: 'geometry' + + + + +.. py:class:: JoinedFilter(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Joined filter model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: column + :type: JoinedFilterFieldEnum + + + + .. py:attribute:: operator + :type: FilterOperatorEnum + + + + .. py:attribute:: value + :type: Union[str, int, float, datetime.datetime, List[Union[str, int, float, datetime.datetime]]] + + + + .. py:method:: is_iterable_not_str() + + + Check if is type Iterable and not str. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: in_operator_must_have_iterable(v, info: pydantic.ValidationInfo) + + + Ensure that an 'in' operator has an iterable type. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: TimeseriesFilter(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Timeseries filter model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: column + :type: TimeseriesFilterFieldEnum + + + + .. py:attribute:: operator + :type: FilterOperatorEnum + + + + .. py:attribute:: value + :type: Union[str, int, float, datetime.datetime, List[Union[str, int, float, datetime.datetime]]] + + + + .. py:method:: is_iterable_not_str() + + + Check if is type Iterable and not str. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: in_operator_must_have_iterable(v, info: pydantic.ValidationInfo) + + + Ensure that an 'in' operator has an iterable type. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: MetricQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Metric query model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: primary_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: secondary_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: crosswalk_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: group_by + :type: List[JoinedFilterFieldEnum] + + + + .. py:attribute:: order_by + :type: List[JoinedFilterFieldEnum] + + + + .. py:attribute:: include_metrics + :type: Union[List[MetricEnum], MetricEnum, str] + + + + .. py:attribute:: filters + :type: Optional[List[JoinedFilter]] + :value: [] + + + + .. py:attribute:: return_query + :type: bool + + + + .. py:attribute:: geometry_filepath + :type: Optional[Union[str, pathlib.Path]] + + + + .. py:attribute:: include_geometry + :type: bool + + + + .. py:attribute:: remove_duplicates + :type: Optional[bool] + :value: True + + + + .. py:method:: include_geometry_must_group_by_primary_location_id(v, info: pydantic.ValidationInfo) + + + Include_geometry must groupby primary_location_id. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: filter_must_be_list(v) + + + Filter must be a list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: JoinedTimeseriesQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Joined timeseries query model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: primary_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: secondary_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: crosswalk_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: order_by + :type: List[JoinedFilterFieldEnum] + + + + .. py:attribute:: filters + :type: Optional[List[JoinedFilter]] + :value: [] + + + + .. py:attribute:: return_query + :type: bool + + + + .. py:attribute:: geometry_filepath + :type: Optional[Union[str, pathlib.Path]] + + + + .. py:attribute:: include_geometry + :type: bool + + + + .. py:attribute:: remove_duplicates + :type: Optional[bool] + :value: True + + + + .. py:method:: include_geometry_must_group_by_primary_location_id(v, info: pydantic.ValidationInfo) + + + Include_geometry must groupby primary_location_id. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: filter_must_be_list(v) + + + Filter must be a list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: TimeseriesQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Timeseries query model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: timeseries_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: order_by + :type: List[TimeseriesFilterFieldEnum] + + + + .. py:attribute:: filters + :type: Optional[List[TimeseriesFilter]] + :value: [] + + + + .. py:attribute:: return_query + :type: bool + + + + .. py:method:: filter_must_be_list(v) + + + Filter must be a list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: TimeseriesCharQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Timeseries char query model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: timeseries_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: order_by + :type: List[TimeseriesFilterFieldEnum] + + + + .. py:attribute:: group_by + :type: List[TimeseriesFilterFieldEnum] + + + + .. py:attribute:: filters + :type: Optional[List[TimeseriesFilter]] + :value: [] + + + + .. py:attribute:: return_query + :type: bool + + + + .. py:method:: filter_must_be_list(v) + + + Filter must be a list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + diff --git a/_sources/autoapi/teehr/models/queries_database/index.rst.txt b/_sources/autoapi/teehr/models/queries_database/index.rst.txt new file mode 100644 index 00000000..f8fe48e2 --- /dev/null +++ b/_sources/autoapi/teehr/models/queries_database/index.rst.txt @@ -0,0 +1,1134 @@ +:py:mod:`teehr.models.queries_database` +======================================= + +.. py:module:: teehr.models.queries_database + +.. autoapi-nested-parse:: + + Module for database query models. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + teehr.models.queries_database.BaseModel + teehr.models.queries_database.FieldTypeEnum + teehr.models.queries_database.JoinedFieldNameEnum + teehr.models.queries_database.TimeseriesNameEnum + teehr.models.queries_database.JoinedTimeseriesFieldName + teehr.models.queries_database.CalculateField + teehr.models.queries_database.Filter + teehr.models.queries_database.InsertJoinedTimeseriesQuery + teehr.models.queries_database.JoinedTimeseriesQuery + teehr.models.queries_database.TimeseriesQuery + teehr.models.queries_database.TimeseriesCharQuery + teehr.models.queries_database.MetricQuery + + + + +.. py:class:: BaseModel(/, **data: Any) + + + Bases: :py:obj:`pydantic.BaseModel` + + + Basemodel configuration. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:class:: ConfigDict + + + + ConfigDict. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: arbitrary_types_allowed + :value: True + + + + + +.. py:class:: FieldTypeEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Allowable duckdb data types. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: BIGINT + :value: 'BIGINT' + + + + .. py:attribute:: BIT + :value: 'BIT' + + + + .. py:attribute:: BOOLEAN + :value: 'BOOLEAN' + + + + .. py:attribute:: BLOB + :value: 'BLOB' + + + + .. py:attribute:: DATE + :value: 'DATE' + + + + .. py:attribute:: DOUBLE + :value: 'DOUBLE' + + + + .. py:attribute:: DECIMAL + :value: 'DECIMAL' + + + + .. py:attribute:: FLOAT + :value: 'FLOAT' + + + + .. py:attribute:: HUGEINT + :value: 'HUGEINT' + + + + .. py:attribute:: INTEGER + :value: 'INTEGER' + + + + .. py:attribute:: INTERVAL + :value: 'INTEGER' + + + + .. py:attribute:: REAL + :value: 'REAL' + + + + .. py:attribute:: SMALLINT + :value: 'SMALLINT' + + + + .. py:attribute:: TIME + :value: 'TIME' + + + + .. py:attribute:: TIMESTAMP + :value: 'TIMESTAMP' + + + + .. py:attribute:: TINYINT + :value: 'TINYINT' + + + + .. py:attribute:: UBIGINT + :value: 'UBIGINT' + + + + .. py:attribute:: UINTEGER + :value: 'UINTEGER' + + + + .. py:attribute:: USMALLINT + :value: 'USMALLINT' + + + + .. py:attribute:: UTINYINT + :value: 'UTINYINT' + + + + .. py:attribute:: UUID + :value: 'UUID' + + + + .. py:attribute:: VARCHAR + :value: 'VARCHAR' + + + + +.. py:class:: JoinedFieldNameEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Names of fields in base joined_timeseries table. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: reference_time + :value: 'reference_time' + + + + .. py:attribute:: value_time + :value: 'value_time' + + + + .. py:attribute:: secondary_location_id + :value: 'secondary_location_id' + + + + .. py:attribute:: secondary_value + :value: 'secondary_value' + + + + .. py:attribute:: configuration + :value: 'configuration' + + + + .. py:attribute:: measurement_unit + :value: 'measurement_unit' + + + + .. py:attribute:: variable_name + :value: 'variable_name' + + + + .. py:attribute:: primary_value + :value: 'primary_value' + + + + .. py:attribute:: primary_location_id + :value: 'primary_location_id' + + + + .. py:attribute:: lead_time + :value: 'lead_time' + + + + .. py:attribute:: absolute_difference + :value: 'absolute_difference' + + + + .. py:attribute:: geometry + :value: 'geometry' + + + + +.. py:class:: TimeseriesNameEnum + + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Timeseries Names. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: primary + :value: 'primary' + + + + .. py:attribute:: secondary + :value: 'secondary' + + + + +.. py:class:: JoinedTimeseriesFieldName(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Joined Timeseries Field Name model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: field_name + :type: str + + + + .. py:method:: field_name_must_exist_in_timeseries_table(v, info: pydantic.ValidationInfo) + + + Field name must exist in the database table. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: CalculateField(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Calculate field model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: parameter_names + :type: List[str] + + + + .. py:attribute:: new_field_name + :type: str + + + + .. py:attribute:: new_field_type + :type: FieldTypeEnum + + + + .. py:method:: field_name_must_be_valid(v) + + + Must not contain special characters. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: parameter_names_must_exist_as_fields(v, info: pydantic.ValidationInfo) + + + Parameter name must exist in the database table. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: Filter(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Filter model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: column + :type: str + + + + .. py:attribute:: operator + :type: teehr.models.queries.FilterOperatorEnum + + + + .. py:attribute:: value + :type: Union[str, int, float, datetime.datetime, List[Union[str, int, float, datetime.datetime]]] + + + + .. py:method:: is_iterable_not_str() + + + Check if obj is iterable and not str. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: in_operator_must_have_iterable(v: str, info: pydantic.ValidationInfo) -> str + + + Ensure the 'in' operator has an iterable. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: InsertJoinedTimeseriesQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + InsertJoinedTimeseriesQuery model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: primary_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: secondary_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: crosswalk_filepath + :type: Union[str, pathlib.Path] + + + + .. py:attribute:: order_by + :type: Optional[List[JoinedFieldNameEnum]] + :value: [] + + + + +.. py:class:: JoinedTimeseriesQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + JoinedTimeseriesQuery model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: order_by + :type: List[str] + + + + .. py:attribute:: filters + :type: Optional[List[Filter]] + :value: [] + + + + .. py:attribute:: return_query + :type: Optional[bool] + :value: False + + + + .. py:attribute:: include_geometry + :type: bool + + + + .. py:method:: filter_must_be_list(v) + + + Filter must be a list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: order_by_must_exist_as_fields(v, info: pydantic.ValidationInfo) + + + Order_by fields must currently exist in the database. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: filters_must_exist_as_fields(v, info: pydantic.ValidationInfo) + + + Filter fields must currently exist in the database. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: TimeseriesQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + TimeseriesQuery model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: order_by + :type: List[str] + + + + .. py:attribute:: filters + :type: Optional[List[Filter]] + :value: [] + + + + .. py:attribute:: return_query + :type: Optional[bool] + :value: False + + + + .. py:attribute:: timeseries_name + :type: TimeseriesNameEnum + + + + .. py:method:: filter_must_be_list(v) + + + Filter must be a list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: order_by_must_exist_as_fields(v, info: pydantic.ValidationInfo) + + + Order_by fields must be part one of the selected fields or + its alias. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: filters_must_exist_as_fields(v, info: pydantic.ValidationInfo) + + + Filter fields must currently exist in the database. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: TimeseriesCharQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Timeseries char query model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: order_by + :type: List[str] + + + + .. py:attribute:: group_by + :type: List[str] + + + + .. py:attribute:: filters + :type: Optional[List[Filter]] + :value: [] + + + + .. py:attribute:: return_query + :type: Optional[bool] + :value: False + + + + .. py:attribute:: timeseries_name + :type: TimeseriesNameEnum + + + + .. py:method:: filter_must_be_list(v) + + + Filter must be a list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: order_by_must_exist_as_fields_or_chars(v, info: pydantic.ValidationInfo) + + + Order_by fields must currently exist in the database or be one of + the calculated stats. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: group_by_must_exist_as_fields(v, info: pydantic.ValidationInfo) + + + Group_by fields must currently exist in the database. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: group_by_must_contain_primary_or_secondary_id(v) + + + Group_by must contain primary or secondary id. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: filters_must_exist_as_fields(v, info: pydantic.ValidationInfo) + + + Filter fields must currently exist in the database. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +.. py:class:: MetricQuery(/, **data: Any) + + + Bases: :py:obj:`BaseModel` + + + Metric query model. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + .. py:attribute:: include_geometry + :type: bool + + + + .. py:attribute:: group_by + :type: List[str] + + + + .. py:attribute:: order_by + :type: List[str] + + + + .. py:attribute:: include_metrics + :type: Union[List[teehr.models.queries.MetricEnum], teehr.models.queries.MetricEnum, str] + + + + .. py:attribute:: filters + :type: Optional[List[Filter]] + + + + .. py:attribute:: return_query + :type: Optional[bool] + :value: False + + + + .. py:method:: filter_must_be_list(v) + + + Filter must be a list. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + .. py:method:: validate_include_geometry_and_specified_fields(data, info: pydantic.ValidationInfo) + :classmethod: + + + Validate 'include_geometry' and order_by, group_by + and filters fields. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + diff --git a/_sources/autoapi/teehr/queries/duckdb/index.rst.txt b/_sources/autoapi/teehr/queries/duckdb/index.rst.txt new file mode 100644 index 00000000..0583d96e --- /dev/null +++ b/_sources/autoapi/teehr/queries/duckdb/index.rst.txt @@ -0,0 +1,425 @@ +:py:mod:`teehr.queries.duckdb` +============================== + +.. py:module:: teehr.queries.duckdb + +.. autoapi-nested-parse:: + + A module defining duckdb sql queries for parquet files. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.queries.duckdb.get_metrics + teehr.queries.duckdb.get_joined_timeseries + teehr.queries.duckdb.get_timeseries + teehr.queries.duckdb.get_timeseries_chars + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.queries.duckdb.SQL_DATETIME_STR_FORMAT + + +.. py:data:: SQL_DATETIME_STR_FORMAT + :value: '%Y-%m-%d %H:%M:%S' + + + +.. py:function:: get_metrics(primary_filepath: str, secondary_filepath: str, crosswalk_filepath: str, group_by: List[str], order_by: List[str], include_metrics: Union[List[teehr.models.queries.MetricEnum], all], filters: Union[List[dict], None] = None, return_query: bool = False, geometry_filepath: Union[str, None] = None, include_geometry: bool = False, remove_duplicates: bool = True) -> Union[str, pandas.DataFrame, geopandas.GeoDataFrame] + + + Calculate performance metrics using a parquet query. + + + :Parameters: + + **primary_filepath** : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + **secondary_filepath** : str + File path to the "forecast" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + **crosswalk_filepath** : str + File path to single crosswalk file. + + **group_by** : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **include_metrics** : List[str] + List of metrics (see below) for allowable list, or "all" to return all + Placeholder, currently ignored -> returns "all". + + **filters** : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + + **return_query** : bool = False + True returns the query string instead of the data. + + **include_geometry** : bool = True + True joins the geometry to the query results. + Only works if `primary_location_id` + is included as a group_by field. + + **remove_duplicates** : bool = True + True (default) removes joined timeseries rows with duplicate primary + values, where unique values are defined by the value_time, + secondary_reference_time, location_id, configuration, + variable_name and measurement_unit fields. + False does not check for or remove duplicate values. + This option can be used to improve performance if you are certain you + do not have duplicate primary_values. + + :Returns: + + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + + + + + + + + .. rubric:: Notes + + Filter, Order By and Group By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * [any user-added fields] + + Basic Metrics: + + * primary_count + * secondary_count + * primary_minimum + * secondary_minimum + * primary_maximum + * secondary_maximum + * primary_average + * secondary_average + * primary_sum + * secondary_sum + * primary_variance + * secondary_variance + * max_value_delta + + * max(secondary_value) - max(primary_value) + * bias + + * sum(primary_value - secondary_value)/count(*) + + HydroTools Metrics: + + * nash_sutcliffe_efficiency + * kling_gupta_efficiency + * coefficient_of_extrapolation + * coefficient_of_persistence + * mean_error + * mean_squared_error + * root_mean_squared_error + + Time-based Metrics: + + * primary_max_value_time + * secondary_max_value_time + * max_value_timedelta + + + .. rubric:: Examples + + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_joined_timeseries(primary_filepath: str, secondary_filepath: str, crosswalk_filepath: str, order_by: List[str], filters: Union[List[dict], None] = None, return_query: bool = False, geometry_filepath: Union[str, None] = None, include_geometry: bool = False, remove_duplicates: bool = True) -> Union[str, pandas.DataFrame, geopandas.GeoDataFrame] + + + Retrieve joined timeseries using a parquet query. + + + :Parameters: + + **primary_filepath** : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + **secondary_filepath** : str + File path to the "forecast" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + **crosswalk_filepath** : str + File path to single crosswalk file. + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **filters** : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + + **return_query** : bool = False + True returns the query string instead of the data. + + **include_geometry** : bool = True + True joins the geometry to the query results. + Only works if `primary_location_id`. + is included as a group_by field. + + **remove_duplicates** : bool = True + True (default) removes joined timeseries rows with duplicate primary + values, where unique values are defined by the value_time, + secondary_reference_time, location_id, configuration, + variable_name and measurement_unit fields. + False does not check for or remove duplicate values. + This option can be used to improve performance if you are certain you + do not have duplicate primary_values. + + :Returns: + + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + + + + + + + + .. rubric:: Notes + + Filter and Order By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + + + .. rubric:: Examples + + >>> order_by = ["lead_time", "primary_location_id"] + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "'123456'" + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "'2022-01-01 00:00'" + >>> }, + >>> { + >>> "column": "lead_time", + >>> "operator": "<=", + >>> "value": "'10 days'" + >>> } + >>> ] + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_timeseries(timeseries_filepath: str, order_by: List[str], filters: Union[List[dict], None] = None, return_query: bool = False) -> Union[str, pandas.DataFrame, geopandas.GeoDataFrame] + + + Retrieve timeseries using a parquet query. + + + :Parameters: + + **timeseries_filepath** : str + File path to the timeseries data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **filters** : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + + **return_query** : bool = False + True returns the query string instead of the data. + + :Returns: + + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + + + + + + + + .. rubric:: Notes + + Filter and Order By Fields: + + * value_time + * location_id + * value + * measurement_unit + * reference_time + * configuration + * variable_name + + + .. rubric:: Examples + + >>> order_by = ["lead_time", "primary_location_id"] + >>> filters = [ + >>> { + >>> "column": "location_id", + >>> "operator": "in", + >>> "value": [12345, 54321] + >>> }, + >>> ] + + + + .. + !! processed by numpydoc !! + +.. py:function:: get_timeseries_chars(timeseries_filepath: str, group_by: list[str], order_by: List[str], filters: Union[List[dict], None] = None, return_query: bool = False) -> Union[str, pandas.DataFrame, geopandas.GeoDataFrame] + + + Retrieve timeseries characteristics using a parquet query. + + + :Parameters: + + **timeseries_filepath** : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + **group_by** : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **filters** : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + + **return_query** : bool = False + True returns the query string instead of the data. + + :Returns: + + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + + + + + + + + .. rubric:: Notes + + Filter, Group By and Order By Fields + + * value_time + * location_id + * value + * measurement_unit + * reference_time + * configuration + * variable_name + + + .. rubric:: Examples + + >>> order_by = ["lead_time", "primary_location_id"] + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "'123456'" + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "'2022-01-01 00:00'" + >>> }, + >>> { + >>> "column": "lead_time", + >>> "operator": "<=", + >>> "value": "'10 days'" + >>> } + >>> ] + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/queries/duckdb_database/index.rst.txt b/_sources/autoapi/teehr/queries/duckdb_database/index.rst.txt new file mode 100644 index 00000000..51ef6631 --- /dev/null +++ b/_sources/autoapi/teehr/queries/duckdb_database/index.rst.txt @@ -0,0 +1,464 @@ +:py:mod:`teehr.queries.duckdb_database` +======================================= + +.. py:module:: teehr.queries.duckdb_database + +.. autoapi-nested-parse:: + + A module defining duckdb sql queries for a persistent database. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.queries.duckdb_database.create_get_metrics_query + teehr.queries.duckdb_database.create_join_and_save_timeseries_query + teehr.queries.duckdb_database.describe_timeseries + teehr.queries.duckdb_database.create_get_joined_timeseries_query + teehr.queries.duckdb_database.create_get_timeseries_query + teehr.queries.duckdb_database.create_get_timeseries_char_query + teehr.queries.duckdb_database.create_unique_field_values_query + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.queries.duckdb_database.SQL_DATETIME_STR_FORMAT + + +.. py:data:: SQL_DATETIME_STR_FORMAT + :value: '%Y-%m-%d %H:%M:%S' + + + +.. py:function:: create_get_metrics_query(mq: teehr.models.queries_database.MetricQuery) -> str + + + Build the query string to calculate performance metrics + using database queries. + + + :Parameters: + + **mq** : MetricQuery + Pydantic model containing query parameters. + + :Returns: + + str + The query string. + + + + + + + + + .. rubric:: Notes + + Filter, Order By and Group By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * [any user-added fields] + + Basic Metrics: + + * primary_count + * secondary_count + * primary_minimum + * secondary_minimum + * primary_maximum + * secondary_maximum + * primary_average + * secondary_average + * primary_sum + * secondary_sum + * primary_variance + * secondary_variance + * max_value_delta + + * max(secondary_value) - max(primary_value) + * bias + + * sum(primary_value - secondary_value)/count(*) + + HydroTools Metrics: + + * nash_sutcliffe_efficiency + * kling_gupta_efficiency + * coefficient_of_extrapolation + * coefficient_of_persistence + * mean_error + * mean_squared_error + * root_mean_squared_error + + Time-based Metrics: + + * primary_max_value_time + * secondary_max_value_time + * max_value_timedelta + + + .. rubric:: Examples + + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + + + + .. + !! processed by numpydoc !! + +.. py:function:: create_join_and_save_timeseries_query(jtq: teehr.models.queries_database.JoinedTimeseriesQuery) -> str + + + Load joined timeseries into a duckdb persistent database using a + database query. + + + :Parameters: + + **jtq** : JoinedTimeseriesQuery + Pydantic model containing query parameters. + + :Returns: + + str + The query string. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: describe_timeseries(timeseries_filepath: str) -> Dict + + + Retrieve descriptive stats for a time series. + + + :Parameters: + + **timeseries_filepath** : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + :Returns: + + Dict + A dictionary of summary statistics for a timeseries. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: create_get_joined_timeseries_query(jtq: teehr.models.queries_database.JoinedTimeseriesQuery) -> str + + + Retrieve joined timeseries using database query. + + + :Parameters: + + **jtq** : JoinedTimeseriesQuery + Pydantic model containing query parameters. + + :Returns: + + str + The query string. + + + + + + + + + .. rubric:: Notes + + Filter By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * absolute_difference + * [any user-added fields] + + Order By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + + + .. rubric:: Examples + + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + + + + .. + !! processed by numpydoc !! + +.. py:function:: create_get_timeseries_query(tq: teehr.models.queries_database.TimeseriesQuery) -> str + + + Retrieve joined timeseries using database query. + + + :Parameters: + + **tq** : TimeseriesQuery + Pydantic model containing query parameters. + + :Returns: + + str + The query string. + + + + + + + + + .. rubric:: Notes + + Filter By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * absolute_difference + * [any user-added fields] + + Order By Fields: + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + + + .. rubric:: Examples + + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + + + + .. + !! processed by numpydoc !! + +.. py:function:: create_get_timeseries_char_query(tcq: teehr.models.queries_database.TimeseriesCharQuery) -> str + + + Retrieve joined timeseries using database query. + + + :Parameters: + + **tcq** : TimeseriesCharQuery + Pydantic model containing query parameters. + + :Returns: + + str + The query string. + + + + + + + + + .. rubric:: Notes + + Filter, Order By and Group By Fields + + * reference_time + * primary_location_id + * secondary_location_id + * primary_value + * secondary_value + * value_time + * configuration + * measurement_unit + * variable_name + * lead_time + * [any user-added fields] + + + .. rubric:: Examples + + >>> order_by = ["lead_time", "primary_location_id"] + + >>> group_by = ["lead_time", "primary_location_id"] + + >>> filters = [ + >>> { + >>> "column": "primary_location_id", + >>> "operator": "=", + >>> "value": "gage-A", + >>> }, + >>> { + >>> "column": "reference_time", + >>> "operator": "=", + >>> "value": "2022-01-01 00:00:00", + >>> }, + >>> {"column": "lead_time", "operator": "<=", "value": "10 hours"}, + >>> ] + + + + .. + !! processed by numpydoc !! + +.. py:function:: create_unique_field_values_query(fn: teehr.models.queries_database.JoinedTimeseriesFieldName) -> str + + + Create a query for identifying unique values in a field. + + + :Parameters: + + **fn** : JoinedTimeseriesFieldName + Name of the field to query for unique values. + + :Returns: + + str + The query string. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/queries/index.rst.txt b/_sources/autoapi/teehr/queries/index.rst.txt new file mode 100644 index 00000000..a45eacab --- /dev/null +++ b/_sources/autoapi/teehr/queries/index.rst.txt @@ -0,0 +1,41 @@ +:py:mod:`teehr.queries` +======================= + +.. py:module:: teehr.queries + +.. autoapi-nested-parse:: + + + Queries init. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + duckdb/index.rst + duckdb_database/index.rst + pandas/index.rst + utils/index.rst + + diff --git a/_sources/autoapi/teehr/queries/pandas/index.rst.txt b/_sources/autoapi/teehr/queries/pandas/index.rst.txt new file mode 100644 index 00000000..8e4ea899 --- /dev/null +++ b/_sources/autoapi/teehr/queries/pandas/index.rst.txt @@ -0,0 +1,217 @@ +:py:mod:`teehr.queries.pandas` +============================== + +.. py:module:: teehr.queries.pandas + +.. autoapi-nested-parse:: + + A module for defining SQL queries against Pandas DataFrames. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.queries.pandas.get_metrics + teehr.queries.pandas.calculate_group_metrics + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.queries.pandas.SQL_DATETIME_STR_FORMAT + + +.. py:data:: SQL_DATETIME_STR_FORMAT + :value: '%Y-%m-%d %H:%M:%S' + + + +.. py:function:: get_metrics(primary_filepath: str, secondary_filepath: str, crosswalk_filepath: str, group_by: List[str], order_by: List[str], include_metrics: Union[List[teehr.models.queries.MetricEnum], all], filters: Union[List[dict], None] = None, return_query: bool = False, geometry_filepath: Union[str, None] = None, include_geometry: bool = False) -> Union[str, pandas.DataFrame, geopandas.GeoDataFrame] + + + Calculate performance metrics using a Pandas or Dask DataFrame. + + + :Parameters: + + **primary_filepath** : str + File path to the "observed" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + **secondary_filepath** : str + File path to the "forecast" data. String must include path to file(s) + and can include wildcards. For example, "/path/to/parquet/\*.parquet". + + **crosswalk_filepath** : str + File path to single crosswalk file. + + **group_by** : List[str] + List of column/field names to group timeseries data by. + Must provide at least one. + + **order_by** : List[str] + List of column/field names to order results by. + Must provide at least one. + + **include_metrics** : List[str] + List of metrics (see below) for allowable list, or "all" to return all. + + **filters** : Union[List[dict], None] = None + List of dictionaries describing the "where" clause to limit data that + is included in metrics. + + **return_query** : bool = False + True returns the query string instead of the data. + + **include_geometry** : bool = True + True joins the geometry to the query results. + Only works if `primary_location_id` + is included as a group_by field. + + :Returns: + + Union[str, pd.DataFrame, gpd.GeoDataFrame] + The query string or a DataFrame or GeoDataFrame of query results. + + + + + + + + + .. rubric:: Notes + + Basic Metrics: + + * primary_count + * secondary_count + * primary_minimum + * secondary_minimum + * primary_maximum + * secondary_maximum + * primary_average + * secondary_average + * primary_sum + * secondary_sum + * primary_variance + * secondary_variance + * max_value_delta + + * max(secondary_value) - max(primary_value) + * bias + + * sum(primary_value - secondary_value)/count(*) + + HydroTools Metrics: + + * nash_sutcliffe_efficiency + * kling_gupta_efficiency + * coefficient_of_extrapolation + * coefficient_of_persistence + * mean_error + * mean_squared_error + * root_mean_squared_error + + Time-based Metrics: + + * primary_max_value_time + * secondary_max_value_time + * max_value_timedelta + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: calculate_group_metrics(group: pandas.DataFrame, include_metrics: Union[List[str], str]) + + + Calculate metrics on a pd.DataFrame. + + + :Parameters: + + **group** : pd.DataFrame + Represents a population group to calculate the metrics on. + + **include_metrics** : List[str] + List of metrics (see below) for allowable list, or "all" to + return all. + + :Returns: + + pd.DataFrame + A DataFrame of calculated metrics. + + + + + + + + + .. rubric:: Notes + + This approach to calculating metrics is not as fast as + `teehr.queries.duckdb.get_metrics()` but is easier to update + and contains more metrics. It also serves as the reference + implementation for the duckdb queries. + + Basic Metrics: + + * primary_count + * secondary_count + * primary_minimum + * secondary_minimum + * primary_maximum + * secondary_maximum + * primary_average + * secondary_average + * primary_sum + * secondary_sum + * primary_variance + * secondary_variance + * max_value_delta + + * max(secondary_value) - max(primary_value) + * bias + + * sum(primary_value - secondary_value)/count(*) + + HydroTools Metrics: + + * nash_sutcliffe_efficiency + * kling_gupta_efficiency + * coefficient_of_extrapolation + * coefficient_of_persistence + * mean_error + * mean_squared_error + * root_mean_squared_error + + Time-based Metrics: + * primary_max_value_time + * secondary_max_value_time + * max_value_timedelta + + + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/queries/utils/index.rst.txt b/_sources/autoapi/teehr/queries/utils/index.rst.txt new file mode 100644 index 00000000..c8716457 --- /dev/null +++ b/_sources/autoapi/teehr/queries/utils/index.rst.txt @@ -0,0 +1,1014 @@ +:py:mod:`teehr.queries.utils` +============================= + +.. py:module:: teehr.queries.utils + +.. autoapi-nested-parse:: + + A module defining common utilities for queries. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.queries.utils._get_datetime_list_string + teehr.queries.utils._format_iterable_value + teehr.queries.utils._format_filter_item + teehr.queries.utils.filters_to_sql + teehr.queries.utils.geometry_join_clause + teehr.queries.utils.geometry_select_clause + teehr.queries.utils.geometry_joined_select_clause + teehr.queries.utils.metric_geometry_join_clause_db + teehr.queries.utils.metric_geometry_join_clause + teehr.queries.utils._remove_duplicates_jtq_cte + teehr.queries.utils._remove_duplicates_mq_cte + teehr.queries.utils._join_time_on + teehr.queries.utils._join_on + teehr.queries.utils._nse_cte + teehr.queries.utils._join_nse_cte + teehr.queries.utils._select_max_value_timedelta + teehr.queries.utils._select_secondary_max_value_time + teehr.queries.utils._select_primary_max_value_time + teehr.queries.utils._select_root_mean_squared_error + teehr.queries.utils._select_mean_squared_error + teehr.queries.utils._select_mean_error + teehr.queries.utils._select_kling_gupta_efficiency + teehr.queries.utils._select_nash_sutcliffe_efficiency + teehr.queries.utils._select_bias + teehr.queries.utils._select_max_value_delta + teehr.queries.utils._select_primary_count + teehr.queries.utils._select_secondary_count + teehr.queries.utils._select_primary_minimum + teehr.queries.utils._select_secondary_minimum + teehr.queries.utils._select_primary_maximum + teehr.queries.utils._select_secondary_maximum + teehr.queries.utils._select_primary_average + teehr.queries.utils._select_secondary_average + teehr.queries.utils._select_primary_sum + teehr.queries.utils._select_secondary_sum + teehr.queries.utils._select_primary_variance + teehr.queries.utils._select_secondary_variance + teehr.queries.utils.df_to_gdf + teehr.queries.utils.remove_empty_lines + + + +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + + teehr.queries.utils.SQL_DATETIME_STR_FORMAT + + +.. py:data:: SQL_DATETIME_STR_FORMAT + :value: '%Y-%m-%d %H:%M:%S' + + + +.. py:function:: _get_datetime_list_string(values) + + + Get a datetime list as a list of strings. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _format_iterable_value(values: collections.abc.Iterable[Union[str, int, float, datetime.datetime]]) -> str + + + Return an SQL formatted string from list of values. + + + :Parameters: + + **values** : Iterable + Contains values to be formatted as a string for SQL. Only one type of + value (str, int, float, datetime) should be used. First value in list + is used to determine value type. Values are not checked for type + consistency. + + :Returns: + + str + An SQL formatted string from list of values. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _format_filter_item(filter: Union[teehr.models.queries.JoinedFilter, teehr.models.queries.TimeseriesFilter, teehr.models.queries_database.Filter]) -> str + + + Return an SQL formatted string for single filter object. + + + :Parameters: + + **filter** : models.\*Filter + A single \*Filter object. + + :Returns: + + str + An SQL formatted string for single filter object. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: filters_to_sql(filters: Union[List[teehr.models.queries.JoinedFilter], List[teehr.models.queries_database.Filter]]) -> List[str] + + + Generate SQL where clause string from filters. + + + :Parameters: + + **filters** : Union[List[tmq.JoinedFilter], List[tmqd.Filter]] + A list of Filter objects describing the filters. + + :Returns: + + str + A where clause formatted string. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: geometry_join_clause(q: Union[teehr.models.queries.MetricQuery, teehr.models.queries.JoinedTimeseriesQuery]) -> str + + + Generate the join clause for geometry. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: geometry_select_clause(q: Union[teehr.models.queries.MetricQuery, teehr.models.queries.JoinedTimeseriesQuery, teehr.models.queries_database.MetricQuery, teehr.models.queries_database.JoinedTimeseriesQuery]) -> str + + + "Generate the geometry select clause. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: geometry_joined_select_clause(q: Union[teehr.models.queries.MetricQuery, teehr.models.queries.JoinedTimeseriesQuery]) -> str + + + Generate the geometry select clause for a database. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: metric_geometry_join_clause_db(q: Union[teehr.models.queries_database.MetricQuery, teehr.models.queries_database.JoinedTimeseriesQuery]) -> str + + + Generate the metric geometry join clause for a database. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: metric_geometry_join_clause(q: Union[teehr.models.queries.MetricQuery, teehr.models.queries.JoinedTimeseriesQuery]) -> str + + + Generate the metric geometry join clause. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _remove_duplicates_jtq_cte(q: teehr.models.queries.JoinedTimeseriesQuery) -> str + + + Generate the remove duplicates CTE for the JoinedTimeseriesQuery. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _remove_duplicates_mq_cte(q: teehr.models.queries.MetricQuery) -> str + + + Generate the remove duplicates CTE for the MetricQuery. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _join_time_on(join: str, join_to: str, join_on: List[str]) + + + Generate the join time on query. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _join_on(join: str, join_to: str, join_on: List[str]) -> str + + + Generate the join on query. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _nse_cte(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the nash-sutcliffe-efficiency CTE. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _join_nse_cte(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the join nash-sutcliffe-efficiency CTE. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_max_value_timedelta(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select max value timedelta query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_secondary_max_value_time(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select secondary max value time query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_primary_max_value_time(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select primary max value time query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_root_mean_squared_error(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select root mean squared error query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_mean_squared_error(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select mean squared error query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_mean_error(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select mean error query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_kling_gupta_efficiency(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select kling gupta efficiency query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_nash_sutcliffe_efficiency(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select nash sutcliffe efficiency query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_bias(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select bias query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_max_value_delta(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select max value delta query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_primary_count(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select primary count query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_secondary_count(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select secondary count query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_primary_minimum(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select primary minimum query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_secondary_minimum(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select secondary minimum query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_primary_maximum(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select primary maximum query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_secondary_maximum(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select secondary maximum query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_primary_average(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select primary average query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_secondary_average(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select secondary average query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_primary_sum(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select primary sum query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_secondary_sum(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select secondary sum query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_primary_variance(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select primary variance query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: _select_secondary_variance(mq: Union[teehr.models.queries.MetricQuery, teehr.models.queries_database.MetricQuery]) -> str + + + Generate the select secondary variance query segment. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: df_to_gdf(df: pandas.DataFrame) -> geopandas.GeoDataFrame + + + Convert pd.DataFrame to gpd.GeoDataFrame. + + When the `geometry` column is read from a parquet file using DuckBD + it is a bytearray in the resulting pd.DataFrame. The `geometry` needs + to be convert to bytes before GeoPandas can work with it. This function + does that. + + :Parameters: + + **df** : pd.DataFrame + DataFrame with a `geometry` column that has geometry stored as + a bytearray. + + :Returns: + + gpd.GeoDataFrame + GeoDataFrame with a valid `geometry` column. + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: remove_empty_lines(text: str) -> str + + + Remove empty lines from string. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/utilities/generate_weights/index.rst.txt b/_sources/autoapi/teehr/utilities/generate_weights/index.rst.txt new file mode 100644 index 00000000..f8dbeb94 --- /dev/null +++ b/_sources/autoapi/teehr/utilities/generate_weights/index.rst.txt @@ -0,0 +1,216 @@ +:py:mod:`teehr.utilities.generate_weights` +========================================== + +.. py:module:: teehr.utilities.generate_weights + +.. autoapi-nested-parse:: + + Module for generating area-based weights for grid layer pixels. + + .. + !! processed by numpydoc !! + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + teehr.utilities.generate_weights.vectorize + teehr.utilities.generate_weights.overlay_zones + teehr.utilities.generate_weights.vectorize_grid + teehr.utilities.generate_weights.calculate_weights + teehr.utilities.generate_weights.generate_weights_file + + + +.. py:function:: vectorize(data_array: xarray.DataArray) -> geopandas.GeoDataFrame + + + Convert 2D xarray.DataArray into a geopandas.GeoDataFrame. + + + + + + + + + + + + .. rubric:: Notes + + Heavily borrowed from GeoCube, see: + https://github.com/corteva/geocube/blob/master/geocube/vector.py#L12 + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: overlay_zones(grid: geopandas.GeoDataFrame, zones: geopandas.GeoDataFrame) -> geopandas.GeoDataFrame + + + Overlay zone polygons on vectorized grid cells. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: vectorize_grid(src_da: xarray.DataArray, nodata_val: float, crs_wkt: str, vectorize_chunk: float = 40) -> geopandas.GeoDataFrame + + + Vectorize pixels in the template array in chunks using dask. + + + + + + + + + + + + .. rubric:: Notes + + Parameter vectorize_chunk determines how many pixels will + be vectorized at one time + (thousands of pixels) + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: calculate_weights(grid_gdf: geopandas.GeoDataFrame, zone_gdf: geopandas.GeoDataFrame, overlay_chunk: float = 250) -> geopandas.GeoDataFrame + + + Overlay vectorized pixels and zone polygons, and calculate + areal weights, returning a geodataframe. + + + + + + + + + + + + .. rubric:: Notes + + Parameter overlay_chunk determines the size of the rectangular + window that spatially subsets datasets for the operation + (thousands of pixels). + + + + + + .. + !! processed by numpydoc !! + +.. py:function:: generate_weights_file(zone_polygon_filepath: Union[pathlib.Path, str], template_dataset: Union[str, pathlib.Path], variable_name: str, output_weights_filepath: Union[str, pathlib.Path], crs_wkt: str, unique_zone_id: str = None, **read_args: Dict) -> None + + + Generate a file of row/col indices and weights for pixels intersecting + given zone polyons. + + + :Parameters: + + **zone_polygon_filepath** : str + Path to the polygons geoparquet file. + + **template_dataset** : str + Path to the grid dataset to use as a template. + + **variable_name** : str + Name of the variable within the dataset. + + **output_weights_filepath** : str + Path to the resultant weights file. + + **crs_wkt** : str + Coordinate system for given domain as WKT string. + + **unique_zone_id** : str + Name of the field in the zone polygon file containing unique IDs. + + **\*\*read_args** : dict, optional + Keyword arguments to be passed to GeoPandas read_file(). + read_parquet(), and read_feather() methods. + + + + + + + + + + + + .. rubric:: Examples + + Here we generate weights for grid pixels intersecting a given + polygon(s). The algorithm accounts for the fraction of the pixel + area that is within the polygon. We'll use the Nextgen divides/ + catchments as the polygons and a NWM v2.2 forcing file as the + template grid. + + Import the necessary modules. + + >>> from teehr.utilities.generate_weights import generate_weights_file + >>> from teehr.loading.nwm.const import CONUS_NWM_WKT + + Define the input variables. + + >>> TEST_DIR = Path("tests", "data", "nwm22") + >>> TEMP_DIR = Path("tests", "data", "temp") + >>> TEMPLATE_FILEPATH = Path(TEST_DIR, "test_template_grid.nc") + >>> ZONES_FILEPATH = Path(TEST_DIR, "test_ngen_divides.parquet") + >>> WEIGHTS_FILEPATH = Path(TEST_DIR, "test_weights_results.parquet") + + Perform the calculation, writing to the output directory, or optionally + returning the dataframe if no output path is specified. + + >>> df = generate_weights_file( + >>> zone_polygon_filepath=ZONES_FILEPATH, + >>> template_dataset=TEMPLATE_FILEPATH, + >>> variable_name="RAINRATE", + >>> crs_wkt=CONUS_NWM_WKT, + >>> output_weights_filepath=None, + >>> unique_zone_id="id", + >>> ) + + + + .. + !! processed by numpydoc !! + diff --git a/_sources/autoapi/teehr/utilities/index.rst.txt b/_sources/autoapi/teehr/utilities/index.rst.txt new file mode 100644 index 00000000..6f6ff516 --- /dev/null +++ b/_sources/autoapi/teehr/utilities/index.rst.txt @@ -0,0 +1,38 @@ +:py:mod:`teehr.utilities` +========================= + +.. py:module:: teehr.utilities + +.. autoapi-nested-parse:: + + + Utilities init. + + + + + + + + + + + + + + + + + .. + !! processed by numpydoc !! + + +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + generate_weights/index.rst + + diff --git a/_sources/changelog/index.rst.txt b/_sources/changelog/index.rst.txt new file mode 100644 index 00000000..62a0a39c --- /dev/null +++ b/_sources/changelog/index.rst.txt @@ -0,0 +1,288 @@ +Release Notes +============= + + +0.3.9 - 2024-02-15 +-------------------- + +Added +^^^^^ +* Adds sphinx documentation framework and initial docs. +* The `documentation-publish.yml` workflow is set to build the docs and push to github pages + on every tag. +* The `pre-commit-config.yml` github hook runs on each commit and checks docstring formatting, + trailing whitespaces, and the presence of large files. +* Added documenation-related python dependencies to `[tool.poetry.group.dev.dependencies]` + +Changed +^^^^^^^ +* Example notebooks have been moved to `docs/sphinx/user_guide/notebooks`. +* The CHANGELOG.md is now the `index.rst` file in `docs/sphinx/changelog`. +* The CONTRIBUTE.md and release_process.md files now part of the `index.rst` + file in `docs/sphinx/development`. +* The data_models.md and queries.md are now the `data_models.rst` and `queries.rst` + files in `docs/sphinx/getting_started`. + + +0.3.8 - 2024-02-14 +-------------------- + +Added +^^^^^ +* Adds logging with a `NullHandler()` that can be implemented by the parent app using teehr. + + +0.3.7 - 2024-02-09 +-------------------- + +Changed +^^^^^^^ +* Upgraded pandas to ^2.2.0 +* Changed unit="H" in pandas.time_delta to unit="h" +* Updated assert statements in `test_weight_generation.py` + +0.3.6 - 2024-02-07 +-------------------- + +Added +^^^^^ +* Adds an exception to catch an error when a corrupted file is encountered while building + the Kerchunk reference file using `SingleHdf5ToZarr`. +* The behavior determining whether to raise an exception is controlled by the + `ignore_missing_file` flag. + + +0.3.5 - 2023-12-18 +-------------------- + +Added +^^^^^ +* Adds additional chunking methods for USGS and NWM retrospective loading to allow + week, month and year chunking. +* Adds mean areal summaries for NWM retrospective gridded forcing variables +* Adds NWM v3.0 to retrospective loading + +Changed +^^^^^^^ +* Fixes USGS loading to include last date of range +* Removes extra fields from v2.1 retro output + +0.3.4 - 2023-12-18 +-------------------- + +Added +^^^^^ +* Adds the `read_only` argument to the `query` method in the TEEHRDatasetDB class with default values + specified in the query methods. + +Changed +^^^^^^^ +* Establishes a read-only database connection as a class variable to the TEEHRDatasetAPI class so it can + be re-used for each class instance. + +0.3.3 - 2023-12-13 +-------------------- + +Added +^^^^^ +* Adds `get_joined_timeseries` method to TEEHR Dataset classes. + +Changed +^^^^^^^ +* Updated validation fields in the `TimeSeriesQuery` pydantic model to accept only selected fields + rather than existing database fields. +* Updated function argument typing in `queries/utils.py` to be more explicit + +0.3.2 - 2023-12-12 +-------------------- + +Added +^^^^^ +* None + +Changed +^^^^^^^ +* Fixed the `bias` metric so that it is `sum(secondary_value - primary_value)/count(*)` instead of + `sum(primary_value - secondary_value)/count(*)` which resulted in the wrong sign. +* Changed `primary_max_value_time`, `secondary_max_value_time` and `max_value_timedelta` + queries to use built-in functions instead of CTEs. This improves speed significantly. +* Fixed bug in queries when filtering by `configuration`, `measurement_unit` and `variable.` +* Refactored `join_attributes` in `TEEHRDatasetDB` to better handle attributes with no units. +* Refactored `create_join_and_save_timeseries_query queries` so that the de-duplication + CTE is after the intial join CTE for improved performance. +* Changes default list of `order_by` variables in `insert_joined_timeseries` to improve + query performance + +0.3.1 - 2023-12-08 +-------------------- + +Added +^^^^^ +* Adds a boolean flag to parquet-based metric query control whether or not to de-duplicate. +* Adds a test primary timeseries file including duplicate values for testing. + +Changed +^^^^^^^ +* Refactored parquet-based `get_metrics` and `get_joined_timeseries` queries to that so that the de-duplication + CTE is after the intial join CTE for improved performance. + + +0.3.0 - 2023-12-08 +-------------------- + +Added +^^^^^ +* Adds a dataclass and database that allows preprocessing of joined timeseries and attributes as well as the addition of user defined functions. +* Adds an initial web service API that serves out `timeseries` and `metrics` along with some other supporting data. +* Adds an initial interactive web application using the web service API. + +Changed +^^^^^^^ +* Switches to poetry to manage Python venv +* Upgrades to Pydantic 2+ +* Upgrades to Pangeo image `pangeo/pangeo-notebook:2023.09.11` + + +0.2.9 - 2023-12-08 +-------------------- + +Added +^^^^^ +* Three options related to kerchunk jsons + * `local` - (default) previous behavior, manually creates the jsons based on GCS netcdf files using Kerchunk's `SingleHdf5ToZarr`. Any locally existing files will be used before creating new jsons from the remote store. + * `remote` - use pre-created jsons, skipping any that do not exist within the specified time frame. Jsons are read directly from s3 using fsspec + * `auto` - use pre-created jsons, creating any that do not exist within the specified time frame +* Adds `nwm_version` (nwm22 or nwm30) and `data_source` (GCS, NOMADS, DSTOR - currently on GCS implemented) as loading arguments + +Changed +^^^^^^^ +* Combines loading modules into one directory `loading/nwm` +* Updates to loading example notebooks +* Updates to loading tests + +0.2.8 - 2023-11-14 +-------------------- + +Added +^^^^^ +- NWM v3.0 data loading and configuration models +- Added check for duplicate rows in `get_metrics` and `get_joined_timeseries` queries (#69) +- Added control for overwrite file behavior in loading (#77) +- Significant refactor of the loading libraries +- Added ability to select which retrospective version to download (v2.0 or v2.1) (#80) + +Changed +^^^^^^^ + +- Fixed NWM pydantic configurations models for v2.2 +- Refactored `models/loading` directory + +0.2.7 - 2023-09-14 +-------------------- + +Added +^^^^^ +- More testing to NWM point and grid loading functions + +0.2.6 - 2023-09-14 +-------------------- + +Changed +^^^^^^^ + +- Fixed some sloppy bugs in `nwm_grid_data.py` + +Added +^^^^^ +- `ValueError` handling when encountering a corrupt zarr json file + +0.2.5 - 2023-09-11 +-------------------- + +Changed +^^^^^^^ + +- None + +Added +^^^^^ +- Added ability to use holoviz export to TEEHR-HUB: + - Installed firefox (and a bunch of dependencies) to the Docker container (using apt) + - Installed selenium and the geckodriver using conda + +0.2.4 - 2023-08-30 +-------------------- + +Changed +^^^^^^^ + +- Behavior of loading when encountering missing files +- Renamed field `zone` to `location_id` in `nwm_grid_data.py` and `generate_weights.py` + +Added +^^^^^ +- The boolean flag `ignore_missing_files` to point and grid loading to determine whether to fail or continue on missing NWM files +- Added a check to skip locally existing zarr json files when loading NWM data + +0.2.3 - 2023-08-23 +-------------------- + +Changed +^^^^^^^ + +- Removed pyarrow from time calculations in `nwm_point_data.py` loading due to windows bug +- Updated output file name in `nwm_point_data.py` to include forecast hour if `process_by_z_hour=False` + +0.2.2 - 2023-08-23 +-------------------- + +Added +^^^^^ + +- nodejs to the jupyterhub build so the extensions will load (not 100% sure this was needed) + +Changed +^^^^^^^ + +- Updated TEEHR to v0.2.2, including TEEHR-HUB +- Updated the TEEHR-HUB baseimage to `pangeo/pangeo-notebook:2023.07.05` + +0.2.1 - 2023-08-21 +-------------------- + +Added +^^^^^ + +- Nothing + +Changed +^^^^^^^ + +- Updated TEEHR version in TEEHR-HUB to v0.2.1 +- Converts nwm feature id's to numpy array in loading + +0.2.0 - 2023-08-17 +-------------------- + +Added +^^^^^ + +- This changelog + +Changed +^^^^^^^ + +- Loading directory refactor changed import paths to loading modules +- Changed directory of `generate_weights.py` utility +- Replaced NWM config parameter dictionary with pydantic models +- NWM reference time used by TEEHR is now taken directly from the file name rather than the "reference time" embedded in the file +- Use of the term `run` updated to `configuration` for NWM + + +0.1.3 - 2023-06-17 +-------------------- + +Added +^^^^^ + +- Initial release diff --git a/_sources/development/index.rst.txt b/_sources/development/index.rst.txt new file mode 100644 index 00000000..b9f7f229 --- /dev/null +++ b/_sources/development/index.rst.txt @@ -0,0 +1,109 @@ +.. _development: + +=============== +Developer Guide +=============== + + +Contributing Guidelines +----------------------- + +These contributing guidelines will be updated as we progress. They are pretty +slim to start. + +TEEHR has multiple parts, one is a library of reusable code that can be imported +as a dependency to another project, another is examples and dashboards which are +more use case specific (e.g., a dashboard to conduct post event analysis). The +guidelines for contributing may be a bit different. + +Library Code +^^^^^^^^^^^^ +- Use `PEP 8 `_ +- Use LFS for large files +- Write tests - you are going to test your code, why not write an actual test + `pytest `_. +- Use the Numpy doc string format + `numpydoc `_ + +Git LFS +^^^^^^^ +Use git lfs for large files. Even better keep large files out of the repo. + +Notebooks +^^^^^^^^^ +- Do not commit notebook output to the repo. Use can install and use `nbstripout` + to strip output. After cloning, you must run `nbstripout --install`. + +`nbstripoutput` is configured to strip output from notebooks to keep the size down +and make diffing files easier. See https://github.com/kynan/nbstripout. +The configuration is stored in the `.gitattributes` file, but the tool must be +installed per repo. You may need to install the Python package first with +`conda install nbstripout` or similar depending on your environment. + + +Local Development +^^^^^^^^^^^^^^^^^ +The most common way to use TEEHR is by installing it in a Python virtual +environment. The document covers using a conda virtual environment, but +there is no hard requirement to do so. In this case the packages are not +installed, so you need to make sure you add ``src/`` to your Python path. +There are two way to do this below, but depending on your development +environment, your milage may vary. + +``TODO: Poetry docs`` + +Release Process +^^^^^^^^^^^^^^^ +This document describes the release process which has some manual steps to complete. + +Create branch with the following updated to the new version (find and replace version number): + +- ``version.txt`` +- ``README.md`` +- ``pyproject.toml`` + +Update the changelog at ``docs/sphinx/changelog/index.rst`` to reflect the changes included in the release. + +If also pushing changes to TEEHR-HUB, also update tags in ``teehr-hub/helm-chart/config.yaml``. + +Make a PR to main. After PR has been reviewed and merged, checkout ``main`` pull changes and tag the commit. + +.. code-block:: bash + + git checkout main + git pull + git tag -a v0.x.x -m "version 0.x.x" + git push origin v0.x.x + +Tagging will trigger a docker container build and push to the AWS registry for deployment to TEEHR-HUB. +Deployment to TEEHR-HUB is a manual process that requires the correct credentials. + + +Contributing to the Documentation +--------------------------------- +* description +* docstring approach (numpy) +* pre-commit validation +* building and pushing docs + +The documentation files are in the ``docs/sphinx`` directory. + +To build the documentation html files, navigate to ``docs/sphinx`` and run: + +.. code-block:: bash + + make clean html + +Check your files locally in a browser such as Firefox: + +.. code-block:: bash + + firefox _build/html/index.html & + +Some pre-commit hooks are configured automatically run when you commit some code. +These check for things like large files, docstring formatting, added whitespace, etc. +To run these manually and print the results to a text file `pre-commit-output.txt`, run: + +.. code-block:: bash + + pre-commit run --all-files > pre-commit-output.txt diff --git a/_sources/getting_started/data_model.rst.txt b/_sources/getting_started/data_model.rst.txt new file mode 100644 index 00000000..cfb4e525 --- /dev/null +++ b/_sources/getting_started/data_model.rst.txt @@ -0,0 +1,50 @@ +.. _data_model: + +Evaluation Data Models +====================== +This document describes the initial data models used in the yet-to-be-named evaluation system. These are considered the minimum fields required for the system to work. Additional fields can be added but may not show up by default in the prepared queries. The `timeseries`, `crosswalk`, `location` and `threshold` data models are schemas but not table names. In practice the data will be stored in one or more files (parquet or csv) and can have any name. In some cases it could also be implemented as an actual database table. + +This is intended to be a living document that provides a common data model to work from that will be updated as we progress, learn what works, learn what doesn't, etc. + +Timeseries +---------- +The `timeseries` data model (mostly taken from [HydroTools](https://github.com/NOAA-OWP/hydrotools)) describes the schema used to store, you guessed it, timeseries data from both observed and simulated sources. In the context of this system, this data model will be utilized as the format to store data as parquet files. As such, a standard file directory structure is also important. See [Cache Directory Structure] below. + +- `reference_time`: [datetime] This is a reference time for a particular model simulation. +- `value_time`: [datetime] This is the time a value is valid at. +- `value`: [float] This is the value being reported. +- `variable_name`: [string] This describes the type of value, for example `streamflow`. This are not verified by the system but should be consistent. +- `measurement_unit`: [string] This describes the units of the - `value`, for example `m^3/s`. This are not verified by the system but should be consistent. +- `configuration`: [string] This is a reference string describing the model simulation. In the context of the v2.2 NWM, this would refer to the a model run such as `medium_range` or `analysis_assim`. In the NextGen system or a test-bed simulation, this would be a relevant identifier. +- `location_id`: [string] This is the `location_id` associated assigned to the timeseries by the creating entity. For example, if this timeseries was USGS gage data, then the - `location_id` would be the USGS site code, if it was output from V2.2 of the National Water Model, then the NWM feature ID would be used. For clarity, a "source string" can be prepend to the ID to make the source of the ID obvious. For example `usgs-0123456789` or `huc10-1834526098`. "source string" should be lowercase and separated from the ID with a hyphen ("-"). + +Crosswalk +--------- +The `crosswalk` data model is used to define relationships between timeseries IDs that have different IDs but refer to the same or comparable locations. + +- `primary_location_id`: [string] This is the primary ID used to join timeseries from different sources for the purpose of comparison. This ID will typically be the ID used for the "observed" or baseline timeseries that a forecast or simulation timeseries are being compared against, but could itself be a simulation. For example, if comparing one simulation to another. As the primary ID, this is the ID used to join geographic and location specific data (i.e. thresholds) to the timeseries data. +- `secondary_location_id`: [string] This is the secondary location ID used for the "simulated" timeseries. + +Location +-------- +The `location` data model is used to store geographic entities. This could include almost anything but in the context of hydrologic data we will mostly be referring to forecast points or gage locations (points) and catchments (polygons), and possibly stream reaches (polylines). + +- `id`: [string] This is the ID of the location data and should match the `timeseries.location_id` and the `crosswalk.primary_location_id` as well as the `threshold.location_id` (e.g., usgs-0123456789). This is the ID associated with the "observed" timeseries. +- `name`: [string] This is the common name for location (e.g., Some River at Some City, Some State). This will be used for plots +- `geometry`: [string/bytes] Geometry in WKT/WKB format. For example "Point(100,200)". Must be "EPSG:4326" + +Attributes +---------- +The `attributes` data model is used to store other data about the location. This could possibly replaced by a generic `key:value` table. + +- `location_id`: [string] Primary location ID of the location. +- `attribute_name`: [string] Name of attribute (i.e., 2-year flow, eco-region) +- `attribute_value`: [string | float] Attribute value +- `attribute_unit`: [string] Units of attribute value, if any. + +Data Model Diagram +------------------ +The following is a visual representation of the data model structure. + +.. image:: ../../images/data_model.png + :width: 1000 diff --git a/_sources/getting_started/index.rst.txt b/_sources/getting_started/index.rst.txt new file mode 100644 index 00000000..75b9dc2a --- /dev/null +++ b/_sources/getting_started/index.rst.txt @@ -0,0 +1,174 @@ +.. _getting_started: + +=============== +Getting started +=============== + +Installation +------------ +There are several methods for installing TEEHR. + +You can install from PyPI using pip [TODO]: + +.. code-block:: python + + [TODO] pip install teehr + +You can install from github: + +.. code-block:: python + + # Using pip + pip install 'teehr @ git+https://github.com/RTIInternational/teehr@[BRANCH_TAG]' + + # Using poetry + poetry add git+https://github.com/RTIInternational/teehr.git#[BRANCH TAG] + +You can use Docker: + +.. code-block:: bash + + docker build -t teehr:v0.3.2 . + docker run -it --rm --volume $HOME:$HOME -p 8888:8888 teehr:v0.3.2 jupyter lab --ip 0.0.0.0 $HOME + + +API Overview +------------ +TEEHR is comprised of several submodules having specific functionality related to loading, storing, +processing, and visualizing hydrologic data. + +* **Loading**: For fetching and formatting data (ie, NWM forecasts or USGS streamflow records). +* **Queries**: For querying data from cached parquet files or databases and for generating metrics. +* **Database**: For building and querying data using a persistent database. +* **API**: For enabling web-based analysis of a TEEHR database. +* **Utilities**: Helper scripts for common analysis tasks. + +Each submodule can be imported independently: + +.. ipython:: python + + # To fetch and format NWM point data. + import teehr.loading.nwm.nwm_points as tlp + + # For querying cached parquet files. + import teehr.queries.duckdb as tqd + + +.. note:: + + Add note about using Dask (many functions, are designed to take advantage of Dask, + especially loading, and you should see performance improvements by starting a local Dask cluster) + +For example: + +.. code-block:: python + + import os + from dask.distributed import Client + + n_workers = max(os.cpu_count() - 1, 1) + client = Client(n_workers=n_workers) + + +Examples +-------- + +Fetching NWM Data +^^^^^^^^^^^^^^^^^ + +An example of using TEEHR to fetch retrospective NWM v2.0 data and format into the TEEHR data model +is shown here. + +.. code-block:: python + + # Import the packages. + import teehr.loading.nwm.retrospective as nwm_retro + from pathlib import Path + from datetime import datetime + + # Define the import variables. + NWM_VERSION = "nwm20" + VARIABLE_NAME = "streamflow" + START_DATE = datetime(2000, 1, 1) + END_DATE = datetime(2000, 1, 2, 23) + LOCATION_IDS = [7086109, 7040481] + + OUTPUT_ROOT = Path(Path().home(), "temp") + OUTPUT_DIR = Path(OUTPUT_ROOT, "nwm20_retrospective") + + # Fetch and format + nwm_retro.nwm_retro_to_parquet( + nwm_version=NWM_VERSION, + variable_name=VARIABLE_NAME, + start_date=START_DATE, + end_date=END_DATE, + location_ids=LOCATION_IDS, + output_parquet_dir=OUTPUT_DIR + ) + +TEEHR Database +^^^^^^^^^^^^^^ + +Once the data adheres to the TEEHR data model, we can use the `TEEHRDatasetDB` class +to create a persisent database, allowing for efficient exploration and metric queries. + +.. ipython:: python + + from pathlib import Path + + from teehr.database.teehr_dataset import TEEHRDatasetDB + + # Define file paths the test data + PRIMARY_FILEPATH = "getting_started/test_data/*short_obs.parquet" + SECONDARY_FILEPATH = "getting_started/test_data/*_fcast.parquet" + CROSSWALK_FILEPATH = "getting_started/test_data/crosswalk.parquet" + DATABASE_FILEPATH = Path("getting_started/test_data/temp_test.db") + + # Delete the test database if it already exists. + if DATABASE_FILEPATH.is_file(): + DATABASE_FILEPATH.unlink() + + # Initialize a database. + tds = TEEHRDatasetDB(DATABASE_FILEPATH) + + # Join the primary and secondary timeseries using the crosswalk table + # and insert the data into the `joined_timeseries` database table. + tds.insert_joined_timeseries( + primary_filepath=PRIMARY_FILEPATH, + secondary_filepath=SECONDARY_FILEPATH, + crosswalk_filepath=CROSSWALK_FILEPATH, + drop_added_fields=True, + ) + + # Let's look at the table schema. + schema_df = tds.get_joined_timeseries_schema() + schema_df + + # Now we can perform queries and calculate metrics. + df = tds.query("SELECT * FROM joined_timeseries", format="df") + df + + +Example notebooks +^^^^^^^^^^^^^^^^^ + +See the :doc:`Loading <../user_guide/notebooks/loading_examples_index>` and +:doc:`Query <../user_guide/notebooks/queries_examples_index>` notebooks for more in-depth examples. + +Data Model +---------- + +Link to the data model documentation: :ref:`data_model` + + +Queries +------- + +Link to the queries documentation: :ref:`queries` + +.. toctree:: + :maxdepth: 2 + :hidden: + + data_model + queries diff --git a/_sources/getting_started/queries.rst.txt b/_sources/getting_started/queries.rst.txt new file mode 100644 index 00000000..e849a1dd --- /dev/null +++ b/_sources/getting_started/queries.rst.txt @@ -0,0 +1,36 @@ +.. _queries: + +Queries +======= + +The TEEHR data library provides tools for querying data from the cached parquet files and for generating metrics. This includes: + +* Get Timeseries +* Get Timeseries Characteristics +* Get Joined Timeseries +* Get Metrics + +Get Timeseries +-------------- +This feature simply applies filters to the timeseries tables and returns the requested timeseries. + + +Get Timeseries Characteristics +------------------------------ +This feature returns simple summary statistics on the requested timeseries. + + +Get Joined Timeseries +--------------------- +This feature joined two different tables of timeseries together based on location and time, applies filters and returns the paired timeseries + +.. image:: ../../images/joined_timeseries.png + :width: 1000 + + +Metrics +------- +This feature starts by joining the timeseries as described above, then the timeseries are grouped to create populations, then the requested metrics are calculated. + +.. image:: ../../images/metrics.png + :width: 1000 diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..be1ec9ea --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,102 @@ +.. "TEEHR: Tools for Exploratory Evaluation in Hydrologic Research" documentation master file, created by + sphinx-quickstart on Mon Jan 29 10:49:57 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. image: ../images/teehr.png + +******************** +TEEHR documentation +******************** + +TEEHR (pronounced "tier") is a python tool set for loading, storing, processing and visualizing hydrologic data, particularly National Water Model data, for the purpose of exploring and evaluating the datasets to assess their skill and performance. + +.. grid:: 1 2 2 2 + :gutter: 4 + :padding: 2 2 0 0 + :class-container: sd-text-center + + .. grid-item-card:: Getting started + :img-top: _static/index_getting_started.svg + :class-card: intro-card + :shadow: md + + New to *TEEHR*? Check out the getting started guides. They contain an + introduction to *TEEHR'* main concepts and links to additional tutorials. + + +++ + + .. button-ref:: getting_started + :ref-type: ref + :click-parent: + :color: secondary + :expand: + + To the getting started guides + + .. grid-item-card:: User guide + :img-top: _static/index_user_guide.svg + :class-card: intro-card + :shadow: md + + The user guide provides in-depth information on the + key concepts of pandas with useful background information and explanation. + + +++ + + .. button-ref:: user_guide + :ref-type: ref + :click-parent: + :color: secondary + :expand: + + To the user guide + + .. grid-item-card:: API reference + :img-top: _static/index_api.svg + :class-card: intro-card + :shadow: md + + The reference guide contains a detailed description of + the TEEHR API. The reference describes how the methods work and which parameters can + be used. It assumes that you have an understanding of the key concepts. + + +++ + + .. button-ref:: autoapi + :ref-type: ref + :click-parent: + :color: secondary + :expand: + + To the reference guide + + .. grid-item-card:: Developer guide + :img-top: _static/index_contribute.svg + :class-card: intro-card + :shadow: md + + Saw a typo in the documentation? Want to improve + existing functionalities? The contributing guidelines will guide + you through the process of improving TEEHR. + + +++ + + .. button-ref:: development + :ref-type: ref + :click-parent: + :color: secondary + :expand: + + To the development guide + + +.. toctree:: + :hidden: + :titlesonly: + :maxdepth: 3 + + getting_started/index + user_guide/index + development/index + changelog/index diff --git a/_sources/user_guide/index.rst.txt b/_sources/user_guide/index.rst.txt new file mode 100644 index 00000000..9f6d86e1 --- /dev/null +++ b/_sources/user_guide/index.rst.txt @@ -0,0 +1,64 @@ +.. _user_guide: + +========== +User Guide +========== + +Run through several "use-cases" here, start to finish + +NWM retro +--------- + +* Fetch NWM retro and USGS data +* Load into a database +* Run some queries +* Visualize some output + + +NWM forecasts +------------- +* Fetch NWM operational forecasts and USGS data +* Load into a database +* Run some queries +* Visualize some output + + +Gridded/MAP analysis +-------------------- +* ... + + +Web interface +------------- +* ... + + +TEEHR-Hub +--------- +* ... ? + + +Some Tests +---------- + +ipython example: + +.. ipython:: python + + import pandas as pd + + s = pd.Series([1, 3, 5, np.nan, 6, 8]) + s + +:download:`Download this example notebook (right-click, Save-As) ` + +.. toctree:: + :maxdepth: 2 + :hidden: + + notebooks/loading_examples_index + notebooks/queries_examples_index + + +.. include: grid_loading_example.ipynb +.. :parser: myst_nb.docutils_ diff --git a/_sources/user_guide/notebooks/loading/grid_loading_example.ipynb.txt b/_sources/user_guide/notebooks/loading/grid_loading_example.ipynb.txt new file mode 100644 index 00000000..8a691f5e --- /dev/null +++ b/_sources/user_guide/notebooks/loading/grid_loading_example.ipynb.txt @@ -0,0 +1,241 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f87ea1e0", + "metadata": {}, + "source": [ + "# NWM Gridded Data Loading" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "38f2f9f4-5294-4f9b-971a-931b91da8f85", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Import the required packages.\n", + "\n", + "# Run this if TEEHR is not installed\n", + "# import sys\n", + "# sys.path.insert(0, \"../../src\")\n", + "\n", + "import os\n", + "\n", + "from dask.distributed import Client\n", + "from pathlib import Path\n", + "\n", + "import teehr.loading.nwm.nwm_grids as tlg\n", + "\n", + "import teehr.utilities.generate_weights as gw\n", + "from teehr.loading.nwm.const import CONUS_NWM_WKT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Specify input variables" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76e363e7-a3e0-427f-811f-a6298b93536f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Set some notebook variables to point to the relevant study files.\n", + "TEMP_GEO_DIR = Path(Path.home(), \"temp/geo\")\n", + "TEMP_GEO_DIR.mkdir(exist_ok=True, parents=True)\n", + "\n", + "# Generate weights\n", + "# fetch \"https://storage.googleapis.com/national-water-model/nwm.20220101/forcing_short_range/nwm.t00z.short_range.forcing.f001.conus.nc\"\n", + "GRID_TEMPLATE_FILE = Path(TEMP_GEO_DIR, \"nwm.t00z.short_range.forcing.f001.conus.nc\")\n", + "\n", + "# fetch \"https://nextgen-hydrofabric.s3.amazonaws.com/v1.2/nextgen_03S.gpkg\"\n", + "ZONE_GEO_FILE = Path(TEMP_GEO_DIR, \"nextgen_03S.gpkg\")\n", + "ZONAL_WEIGHTS_FILEPATH = Path(TEMP_GEO_DIR, \"nextgen_03S_weights.parquet\")\n", + "UNIQUE_ZONE_ID = \"id\"\n", + "\n", + "# NWM\n", + "CONFIGURATION = \"forcing_short_range\" # forcing_short_range, forcing_analysis_assim, forcing_medium_range\n", + "OUTPUT_TYPE = \"forcing\"\n", + "VARIABLE_NAME = \"RAINRATE\"\n", + "\n", + "START_DATE = \"2020-12-18\"\n", + "INGEST_DAYS = 1\n", + "\n", + "JSON_DIR = Path(Path.home(), \"temp/parquet/jsons/\")\n", + "OUTPUT_DIR = Path(Path.home(), \"temp/parquet\")\n", + "\n", + "NWM_VERSION = \"nwm22\" # Currently accepts \"nwm22\" or \"nwm30\"\n", + " # Use \"nwm22\" for dates prior to 09-19-2023\n", + "\n", + "DATA_SOURCE = \"GCS\" # Specifies the remote location from which to fetch the data\n", + " # (\"GCS\", \"NOMADS\", \"DSTOR\")\n", + "\n", + "KERCHUNK_METHOD = \"auto\" # When data_source = \"GCS\", specifies the preference in creating Kerchunk reference json files.\n", + " # \"local\" - always create new json files from netcdf files in GCS and save locally, if they do not already exist\n", + " # \"remote\" - read the CIROH pre-generated jsons from s3, ignoring any that are unavailable\n", + " # \"auto\" - read the CIROH pre-generated jsons from s3, and create any that are unavailable, storing locally\n", + "\n", + "CONCAT_DIMS = [\"time\"] # \"reference_time\"\n", + "T_MINUS = [0, 1, 2] # Only used if an assimilation run is selected\n", + "IGNORE_MISSING_FILE = True # If True, the missing file(s) will be skipped and the process will resume\n", + " # If False, TEEHR will fail if a missing NWM file is encountered\n", + "OVERWRITE_OUTPUT = True # If True, existing output files will be overwritten\n", + " # If False (default), existing files are retained" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fetch a template forcing netCDF file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53724249-5fbe-4fa4-8fd9-96a3238d16b3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "!wget -O /home/jovyan/temp/geo/nwm.t00z.short_range.forcing.f001.conus.nc \\\n", + "https://storage.googleapis.com/national-water-model/nwm.20220101/forcing_short_range/nwm.t00z.short_range.forcing.f001.conus.nc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fetch some example polygons (nextgen divides)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a717b43", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "!wget -O /home/jovyan/temp/geo/nextgen_03S.gpkg https://lynker-spatial.s3.amazonaws.com/v20/gpkg/nextgen_03S.gpkg" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Start a local dask cluster" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30f8283c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "n_workers = max(os.cpu_count() - 1, 1)\n", + "client = Client(n_workers=n_workers)\n", + "client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate the weights file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ac3c383-9aaf-4fd7-8c33-461ff5e87bb7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%time\n", + "gw.generate_weights_file(\n", + " zone_polygon_filepath=ZONE_GEO_FILE,\n", + " template_dataset=GRID_TEMPLATE_FILE,\n", + " variable_name=VARIABLE_NAME,\n", + " output_weights_filepath=ZONAL_WEIGHTS_FILEPATH,\n", + " crs_wkt=CONUS_NWM_WKT,\n", + " unique_zone_id=UNIQUE_ZONE_ID,\n", + " layer=\"divides\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fetch the gridded data summarized to the polygons" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b77bd47", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%time\n", + "tlg.nwm_grids_to_parquet(\n", + " configuration=CONFIGURATION,\n", + " output_type=OUTPUT_TYPE,\n", + " variable_name=VARIABLE_NAME,\n", + " start_date=START_DATE,\n", + " ingest_days=INGEST_DAYS,\n", + " zonal_weights_filepath=ZONAL_WEIGHTS_FILEPATH,\n", + " json_dir=JSON_DIR,\n", + " output_parquet_dir=OUTPUT_DIR,\n", + " nwm_version=NWM_VERSION,\n", + " data_source=DATA_SOURCE,\n", + " kerchunk_method=KERCHUNK_METHOD,\n", + " t_minus_hours=T_MINUS,\n", + " ignore_missing_file=IGNORE_MISSING_FILE,\n", + " overwrite_output=OVERWRITE_OUTPUT\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/_sources/user_guide/notebooks/loading/load_retrospective.ipynb.txt b/_sources/user_guide/notebooks/loading/load_retrospective.ipynb.txt new file mode 100644 index 00000000..f8e0187b --- /dev/null +++ b/_sources/user_guide/notebooks/loading/load_retrospective.ipynb.txt @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Download NWM v2.1 Retrospective Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Import the required packages.\n", + "\n", + "# Need to install TEEHR to avoid this\n", + "import sys\n", + "import os\n", + "sys.path.insert(0, \"../../src\")\n", + "\n", + "import teehr.loading.nwm.retrospective_points as nwm_retro\n", + "from pathlib import Path\n", + "from datetime import datetime\n", + "\n", + "from dask.distributed import Client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set Variables \n", + "Set variables to specify what to download and where to save the files.\n", + "When setting up a study, you will need to specify these variables as needed for your study.\n", + "This will likely involve generating a larger list of `location_ids` and specifying your study directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "NWM_VERSION = \"nwm20\"\n", + "VARIABLE_NAME = \"streamflow\"\n", + "START_DATE = datetime(2000, 1, 1)\n", + "END_DATE = datetime(2000, 1, 2, 23)\n", + "LOCATION_IDS = [7086109, 7040481]\n", + "\n", + "OUTPUT_ROOT = Path(Path().home(), \"temp\")\n", + "OUTPUT_DIR = Path(OUTPUT_ROOT, \"nwm20_retrospective\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Start a dask cluster if you want to use it\n", + "n_workers = max(os.cpu_count() - 1, 1)\n", + "client = Client(n_workers=n_workers)\n", + "client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%time\n", + "nwm_retro.nwm_retro_to_parquet(\n", + " nwm_version=NWM_VERSION,\n", + " variable_name=VARIABLE_NAME,\n", + " start_date=START_DATE,\n", + " end_date=END_DATE,\n", + " location_ids=LOCATION_IDS,\n", + " output_parquet_dir=OUTPUT_DIR\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/_sources/user_guide/notebooks/loading/ngen_to_parquet.ipynb.txt b/_sources/user_guide/notebooks/loading/ngen_to_parquet.ipynb.txt new file mode 100644 index 00000000..8edc98dc --- /dev/null +++ b/_sources/user_guide/notebooks/loading/ngen_to_parquet.ipynb.txt @@ -0,0 +1,386 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example NGEN to Parquet\n", + "This is an example notebook to convert ngen catchments, nexus, forcings, and outputs to parquet files for use in TEEHR. It also loads USGS gage locations and grabs USGS gage data.\n", + "\n", + "This code is not at all DRY at the moment. There is probably room for tools to handle this process in a standardized yet configurable way. The NextGen output can vary a lot depending on the model, configuration, etc., so careful planning and understanding of the output would be required. Ideally, code could just be pointed at the `realization`, `catchment` and `nexus` files and everything would be determined programatically from there. This would require a deeper understanding of the NextGen and BMI, etc. to implement, than we currently have.\n", + "\n", + "Before using any of the code below, you should examine the `*.csv` files carefully as the formats could be different." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "!pip install hydrotools" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Need to install TEEHR to avoid this\n", + "import sys\n", + "sys.path.insert(0, \"../../src\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import geopandas as gpd\n", + "import geoviews as gv\n", + "from pathlib import Path\n", + "from datetime import datetime\n", + "import hvplot.pandas\n", + "import cartopy.crs as ccrs\n", + "from holoviews import opts\n", + "\n", + "import teehr.loading.usgs as tlu" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set some configurations\n", + "NGEN_DIR = \"/home/jovyan/shared-readwrite/rti-eval/awi_16_680661_001/ngen/\"\n", + "NGEN_CONFIG_DIR = Path(NGEN_DIR, \"config\")\n", + "NGEN_FORCINGS_DIR = Path(NGEN_DIR, \"forcings\")\n", + "NGEN_OUTPUT_DIR = Path(NGEN_DIR, \"output\")\n", + "\n", + "STUDY_DIR = \"/home/jovyan/shared-readwrite/rti-eval/awi_16_680661_001/\"\n", + "STUDY_TS_DIR = Path(STUDY_DIR, \"timeseries\")\n", + "STUDY_GEO_DIR = Path(STUDY_DIR, \"geo\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Catchment GeoJSON" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "catchment_file_gdf = gpd.read_file(Path(NGEN_CONFIG_DIR, \"catchments.geojson\"))\n", + "catchment_file_gdf[\"name\"] = catchment_file_gdf[\"id\"]\n", + "catchment_file_gdf = catchment_file_gdf[[\"id\", \"name\", \"geometry\"]]\n", + "catchment_file_gdf.to_parquet(Path(STUDY_GEO_DIR, \"cat_geometry.parquet\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "catchment_crosswalk = pd.DataFrame({\"primary_location_id\": catchment_file_gdf[\"id\"], \"secondary_location_id\": catchment_file_gdf[\"id\"]})\n", + "catchment_crosswalk.to_parquet(Path(STUDY_GEO_DIR, \"cat_cat_crosswalk.parquet\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Nexus GeoJSON" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nexus_file_gdf = gpd.read_file(Path(NGEN_CONFIG_DIR, \"nexus.geojson\"))\n", + "nexus_file_gdf[\"name\"] = nexus_file_gdf[\"id\"]\n", + "nexus_file_gdf = nexus_file_gdf[[\"id\", \"name\", \"geometry\"]]\n", + "nexus_file_gdf.to_parquet(Path(STUDY_GEO_DIR, \"nex_geometry.parquet\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nexus_crosswalk = pd.DataFrame({\"primary_location_id\": nexus_file_gdf[\"id\"], \"secondary_location_id\": nexus_file_gdf[\"id\"]})\n", + "nexus_crosswalk.to_parquet(Path(STUDY_GEO_DIR, \"nex_nex_crosswalk.parquet\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Catchment Forcings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cat_forcing_files = NGEN_FORCINGS_DIR.glob(pattern=\"cat16_cat-*.csv\")\n", + "\n", + "cat_dfs = []\n", + "for file in cat_forcing_files:\n", + " cat_df = pd.read_csv(file, parse_dates=[\"time\"])\n", + " cat_df[\"configuration\"] = \"awi_16_680661_001\"\n", + " cat_df[\"variable_name\"] = \"precipitation_rate\"\n", + " cat_df[\"reference_time\"] = \"\"\n", + " cat_df[\"measurement_unit\"] = \"mm/hr\"\n", + " cat_df[\"location_id\"] = file.stem.split(\"_\")[-1]\n", + " cat_df.rename(columns={\"time\":\"value_time\", \"precip_rate\":\"value\"}, inplace=True, errors=\"raise\")\n", + " cat_df[\"value\"] = cat_df[\"value\"] * 3600\n", + " cat_df = cat_df[[\"reference_time\", \"location_id\", \"value_time\", \"value\", \"variable_name\", \"measurement_unit\", \"configuration\"]]\n", + " # Depending on size may want to write inididual parquet files.\n", + " # cat_df.to_parquet(Path(STUDY_FORCINGS_DIR, f\"{file.stem.split('_')[-1]}.parquet\"))\n", + " cat_dfs.append(cat_df)\n", + " \n", + "pd.concat(cat_dfs).to_parquet(Path(STUDY_TS_DIR, \"catchment_forcings.parquet\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Catchment Output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cat_output_files = NGEN_OUTPUT_DIR.glob(pattern=\"cat-*.csv\")\n", + "\n", + "cat_out_dfs = []\n", + "for file in cat_output_files:\n", + " cat_out_df = pd.read_csv(file)\n", + " cat_out_df.rename(columns={\"Time Step\":\"lead_time\",\"Time\":\"value_time\", \"Q_OUT\": \"value\"}, inplace=True, errors=\"raise\")\n", + " cat_out_df[\"configuration\"] = \"awi_16_680661_00\"\n", + " cat_out_df[\"location_id\"] = file.stem.split(\"_\")[-1]\n", + " cat_out_df[\"variable_name\"] = \"runoff\"\n", + " cat_out_df[\"reference_time\"] = cat_out_df[\"value_time\"].iloc[0]\n", + " cat_out_df[\"measurement_unit\"] = \"m3/s\"\n", + " cat_out_df = cat_out_df[[\"reference_time\", \"location_id\", \"value_time\", \"value\", \"variable_name\", \"measurement_unit\", \"configuration\"]]\n", + " # Depending on size may want to write inididual parquet files.\n", + " # cat_out_df.to_parquet(Path(STUDY_OUTPUT_DIR, f\"{file.stem.split('_')[-1]}.parquet\"))\n", + " cat_out_dfs.append(cat_out_df)\n", + " \n", + "pd.concat(cat_out_dfs).to_parquet(Path(STUDY_TS_DIR, \"catchment_simulation.parquet\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Nexus Output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nexus_output_files = NGEN_OUTPUT_DIR.glob(pattern=\"nex-*.csv\")\n", + "\n", + "nex_out_dfs = []\n", + "for file in nexus_output_files:\n", + " nex_out_df = pd.read_csv(file, header=1, names=[\"lead_time\",\"value_time\", \"value\"], parse_dates=[\"value_time\"])\n", + " nex_out_df[\"configuration\"] = \"awi_16_680661_00\"\n", + " nex_out_df[\"location_id\"] = file.stem.split(\"_\")[0]\n", + " nex_out_df[\"variable_name\"] = \"streamflow\"\n", + " nex_out_df[\"reference_time\"] = nex_out_df[\"value_time\"].iloc[0]\n", + " nex_out_df[\"measurement_unit\"] = \"m3/s\"\n", + " nex_out_df = nex_out_df[[\"reference_time\", \"location_id\", \"value_time\", \"value\", \"variable_name\", \"measurement_unit\", \"configuration\"]]\n", + " # Depending on size may want to write inididual parquet files.\n", + " # nex_out_df.to_parquet(Path(STUDY_OUTPUT_DIR, f\"{file.stem.split('_')[-1]}.parquet\"))\n", + " nex_out_dfs.append(nex_out_df)\n", + " \n", + "pd.concat(nex_out_dfs).to_parquet(Path(STUDY_TS_DIR, \"nexus_simulation.parquet\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## USGS Gages and Crosswalk\n", + "The gage data GeoJSON file `usgs_awi_16_680661_001_gages.geojson` was created manually using QGIS and the nexus and catchment GeoJSON files from the study for this example project, but could be automated in the future.\n", + "\n", + "First the `usgs_awi_16_680661_001_gages.geojson` is converted to parquet and then a crosswalk table is created and also saved as a parquet file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Convert USGS Gages to Parquet\n", + "usgs_gdf = gpd.read_file(Path(STUDY_GEO_DIR, \"usgs_awi_16_680661_001_gages.geojson\"))\n", + "usgs_gdf.rename(columns={\"STAID\":\"id\",\"STANAME\":\"name\"}, inplace=True, errors=\"raise\")\n", + "usgs_gdf[\"id\"] = \"usgs-\" + usgs_gdf[\"id\"].astype(str)\n", + "usgs_gdf = usgs_gdf[[\"id\", \"name\", \"geometry\"]]\n", + "usgs_gdf.to_parquet(Path(STUDY_GEO_DIR, \"usgs_awi_16_680661_001_geometry.parquet\"))\n", + "# usgs_gdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This crosswalk list was generated by hand because the hydrofabric for this example \n", + "# does not match the published hydrofabric. In the future, this could be automated \n", + "# based on the hydrofabric data.\n", + "nex_location_ids = [\n", + " \"nex-680635\",\n", + " \"nex-680639\",\n", + " \"nex-680646\",\n", + " \"nex-680649\",\n", + " \"nex-680892\",\n", + " \"nex-680741\",\n", + " \"nex-680662\"\n", + "]\n", + "usgs_gage_ids = [\n", + " \"usgs-10154200\",\n", + " \"usgs-10155000\",\n", + " \"usgs-10155200\",\n", + " \"usgs-10155500\",\n", + " \"usgs-10156000\",\n", + " \"usgs-10157500\",\n", + " \"usgs-10163000\"\n", + "]\n", + "usgs_nex_crosswalk = pd.DataFrame(\n", + " {\n", + " \"primary_location_id\": usgs_gage_ids,\n", + " \"secondary_location_id\": nex_location_ids\n", + " }\n", + ")\n", + "usgs_nex_crosswalk.to_parquet(Path(STUDY_GEO_DIR, \"usgs_nex_crosswalk.parquet\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load USGS Gage Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Use the TEEHR library to load USGS gage data\n", + "usgs_site_codes = [v.replace(\"usgs-\", \"\") for v in usgs_nex_crosswalk[\"primary_location_id\"].to_list()]\n", + "tlu.usgs_to_parquet(\n", + " sites=usgs_site_codes,\n", + " start_date=datetime(1980, 1, 1),\n", + " end_date=datetime(1980, 2, 1),\n", + " output_parquet_dir=Path(STUDY_TS_DIR),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Lets look at the data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tiles = gv.tile_sources.OSM" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cat_gdf = gpd.read_parquet(Path(STUDY_GEO_DIR, \"cat_geometry.parquet\")).to_crs(\"EPSG:3857\")\n", + "catchments = cat_gdf.hvplot(crs=ccrs.GOOGLE_MERCATOR)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nex_gdf = gpd.read_parquet(Path(STUDY_GEO_DIR, \"nex_geometry.parquet\")).to_crs(\"EPSG:3857\")\n", + "nexus = nex_gdf.hvplot(color=[\"red\"], crs=ccrs.GOOGLE_MERCATOR)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "usgs_gdf = gpd.read_parquet(Path(STUDY_GEO_DIR, \"usgs_awi_16_680661_001_geometry.parquet\")).to_crs(\"EPSG:3857\")\n", + "usgs = usgs_gdf.hvplot(color=[\"green\"], crs=ccrs.GOOGLE_MERCATOR)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(tiles * catchments * nexus * usgs).opts(width=800, height=600)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/_sources/user_guide/notebooks/loading/point_loading_example.ipynb.txt b/_sources/user_guide/notebooks/loading/point_loading_example.ipynb.txt new file mode 100644 index 00000000..bf2ec90b --- /dev/null +++ b/_sources/user_guide/notebooks/loading/point_loading_example.ipynb.txt @@ -0,0 +1,165 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NWM Point Data Loading" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Import the required packages.\n", + "\n", + "# Run this if TEEHR is not installed\n", + "# import sys\n", + "# sys.path.insert(0, \"../../src\")\n", + "\n", + "import os\n", + "\n", + "import teehr.loading.nwm.nwm_points as tlp\n", + "\n", + "from pathlib import Path\n", + "from dask.distributed import Client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Specify input variables" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "CONFIGURATION = \"short_range\" # analysis_assim, short_range, analysis_assim_hawaii, medium_range_mem1\n", + "OUTPUT_TYPE = \"channel_rt\"\n", + "VARIABLE_NAME = \"streamflow\"\n", + "T_MINUS = [0, 1, 2] # Only used if an assimilation run is selected\n", + "\n", + "NWM_VERSION = \"nwm22\" # Currently accepts \"nwm22\" or \"nwm30\"\n", + " # Use \"nwm22\" for dates prior to 09-19-2023\n", + "\n", + "DATA_SOURCE = \"GCS\" # Specifies the remote location from which to fetch the data\n", + " # (\"GCS\", \"NOMADS\", \"DSTOR\")\n", + "\n", + "KERCHUNK_METHOD = \"auto\" # When data_source = \"GCS\", specifies the preference in creating Kerchunk reference json files.\n", + " # \"local\" - always create new json files from netcdf files in GCS and save locally, if they do not already exist\n", + " # \"remote\" - read the CIROH pre-generated jsons from s3, ignoring any that are unavailable\n", + " # \"auto\" - read the CIROH pre-generated jsons from s3, and create any that are unavailable, storing locally\n", + "\n", + "PROCESS_BY_Z_HOUR = True # If True, NWM files will be processed by z-hour per day. If False, files will be\n", + " # processed in chunks (defined by STEPSIZE). This can help if you want to read many reaches\n", + " # at once (all ~2.7 million for medium range for example).\n", + "\n", + "STEPSIZE = 100 # Only used if PROCESS_BY_Z_HOUR = False. Controls how many files are processed in memory at once\n", + " # Higher values can increase performance at the expense on memory (default value: 100)\n", + "\n", + "IGNORE_MISSING_FILE = True # If True, the missing file(s) will be skipped and the process will resume\n", + " # If False, TEEHR will fail if a missing NWM file is encountered\n", + "\n", + "OVERWRITE_OUTPUT = True # If True, existing output files will be overwritten\n", + " # If False (default), existing files are retained\n", + "\n", + "START_DATE = \"2023-03-18\"\n", + "INGEST_DAYS = 1\n", + "\n", + "OUTPUT_ROOT = Path(Path().home(), \"temp\")\n", + "JSON_DIR = Path(OUTPUT_ROOT, \"zarr\", CONFIGURATION)\n", + "OUTPUT_DIR = Path(OUTPUT_ROOT, \"timeseries\", CONFIGURATION)\n", + "\n", + "# For this simple example, we'll get data for 10 NWM reaches that coincide with USGS gauges\n", + "LOCATION_IDS = [7086109, 7040481, 7053819, 7111205, 7110249, 14299781, 14251875, 14267476, 7152082, 14828145]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Start a local dask cluster" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "n_workers = max(os.cpu_count() - 1, 1)\n", + "client = Client(n_workers=n_workers)\n", + "client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fetch the NWM data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%time\n", + "tlp.nwm_to_parquet(\n", + " configuration=CONFIGURATION,\n", + " output_type=OUTPUT_TYPE,\n", + " variable_name=VARIABLE_NAME,\n", + " start_date=START_DATE,\n", + " ingest_days=INGEST_DAYS,\n", + " location_ids=LOCATION_IDS,\n", + " json_dir=JSON_DIR,\n", + " output_parquet_dir=OUTPUT_DIR,\n", + " nwm_version=NWM_VERSION,\n", + " data_source=DATA_SOURCE,\n", + " kerchunk_method=KERCHUNK_METHOD,\n", + " t_minus_hours=T_MINUS,\n", + " process_by_z_hour=PROCESS_BY_Z_HOUR,\n", + " stepsize=STEPSIZE,\n", + " ignore_missing_file=IGNORE_MISSING_FILE,\n", + " overwrite_output=OVERWRITE_OUTPUT,\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/_sources/user_guide/notebooks/loading_examples_index.rst.txt b/_sources/user_guide/notebooks/loading_examples_index.rst.txt new file mode 100644 index 00000000..85739cec --- /dev/null +++ b/_sources/user_guide/notebooks/loading_examples_index.rst.txt @@ -0,0 +1,12 @@ +================ +Loading Examples +================ + + +.. toctree:: + :maxdepth: 2 + + loading/grid_loading_example + loading/load_retrospective + loading/ngen_to_parquet + loading/point_loading_example diff --git a/_sources/user_guide/notebooks/queries/ngen_simulation_data_queries.ipynb.txt b/_sources/user_guide/notebooks/queries/ngen_simulation_data_queries.ipynb.txt new file mode 100644 index 00000000..33e948a3 --- /dev/null +++ b/_sources/user_guide/notebooks/queries/ngen_simulation_data_queries.ipynb.txt @@ -0,0 +1,359 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NGEN Simulation Data Query Examples\n", + "This notebook provides examples of querying and visualizing some NGEN data that has been transformed and stored in the TEEHR format.\n", + "\n", + "There are only a few USGS gages in the study area, so you may wish to explore the forcing inputs and catchment ouptuts too." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "!pip install duckdb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Need to install TEEHR to avoid this\n", + "import sys\n", + "sys.path.insert(0, \"../../src\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import the required packages.\n", + "import holoviews as hv\n", + "import geoviews as gv\n", + "from pathlib import Path\n", + "import hvplot.pandas\n", + "import cartopy.crs as ccrs\n", + "from holoviews import opts\n", + "import duckdb\n", + "import geopandas as gpd\n", + "\n", + "import teehr.queries.duckdb as tqd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set Paths to Files\n", + "Set some notebook variables to point to the relevant study files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "STUDY_DIR = \"/home/jovyan/shared-readwrite/rti-eval/awi_16_680661_001/\"\n", + "STUDY_TS_DIR = Path(STUDY_DIR, \"timeseries\")\n", + "STUDY_GEO_DIR = Path(STUDY_DIR, \"geo\")\n", + "\n", + "USGS_TS_FILEPATH = Path(STUDY_TS_DIR, \"usgs.parquet\")\n", + "NEXUS_TS_FILEPATH = Path(STUDY_TS_DIR, \"nexus_simulation.parquet\")\n", + "CATCHMENT_FORCINGS_TS_FILEPATH = Path(STUDY_TS_DIR, \"catchment_forcings.parquet\")\n", + "CATCHMENT_SIM_TS_FILEPATH = Path(STUDY_TS_DIR, \"catchment_simulation.parquet\")\n", + "\n", + "CROSSWALK_FILEPATH = Path(STUDY_GEO_DIR, \"usgs_nex_crosswalk.parquet\")\n", + "GEOMETRY_FILEPATH = Path(STUDY_GEO_DIR, \"usgs_awi_16_680661_001_geometry.parquet\")\n", + "\n", + "CATCHMENT_FILEPATH = Path(STUDY_GEO_DIR, \"cat_geometry.parquet\")\n", + "NEXUS_FILEPATH = Path(STUDY_GEO_DIR, \"nex_geometry.parquet\")\n", + "USGS_GAGES_FILEPATH = Path(STUDY_GEO_DIR, \"usgs_awi_16_680661_001_geometry.parquet\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualize the Spatial Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tiles = gv.tile_sources.OSM" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "catchments_gdf = catchments = gpd.read_parquet(CATCHMENT_FILEPATH).to_crs(\"EPSG:3857\")\n", + "catchments = catchments_gdf.hvplot(color=[\"lightblue\"])\n", + "# catchments_gdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nexus_gdf = gpd.read_parquet(NEXUS_FILEPATH).to_crs(\"EPSG:3857\")\n", + "nexus = nexus_gdf.hvplot(color=[\"green\"], size=40)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "usgs_gdf = gpd.read_parquet(USGS_GAGES_FILEPATH).to_crs(\"EPSG:3857\")\n", + "usgs = usgs_gdf.hvplot(color=[\"red\"], crs=ccrs.GOOGLE_MERCATOR, size=40)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(tiles * catchments * nexus * usgs).opts(width=600, height=600, show_legend=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Explore the Parquet Files\n", + "Run som simple queries to see the schema and one row from the files. This is informative." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(duckdb.query(f\"SELECT * from parquet_schema('{USGS_TS_FILEPATH}');\"))\n", + "print(duckdb.query(f\"SELECT * from parquet_schema('{NEXUS_TS_FILEPATH}');\"))\n", + "print(duckdb.query(f\"SELECT * from parquet_schema('{CROSSWALK_FILEPATH}');\"))\n", + "print(duckdb.query(f\"SELECT * from parquet_schema('{GEOMETRY_FILEPATH}');\"))\n", + "print(duckdb.query(f\"SELECT * from parquet_schema('{CATCHMENT_FORCINGS_TS_FILEPATH}');\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(duckdb.query(f\"SELECT * from read_parquet('{USGS_TS_FILEPATH}') LIMIT 1;\"))\n", + "print(duckdb.query(f\"SELECT * from read_parquet('{NEXUS_TS_FILEPATH}') LIMIT 1;\"))\n", + "print(duckdb.query(f\"SELECT * from read_parquet('{CROSSWALK_FILEPATH}') LIMIT 1;\"))\n", + "print(duckdb.query(f\"SELECT * from read_parquet('{GEOMETRY_FILEPATH}') LIMIT 1;\"))\n", + "print(duckdb.query(f\"SELECT * from read_parquet('{CATCHMENT_FORCINGS_TS_FILEPATH}') LIMIT 1;\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run Some Timeseries Queries\n", + "Run queries against the timeseries data to generate metrics and explore the timeseries data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nexus_df = tqd.get_timeseries(\n", + " timeseries_filepath=NEXUS_TS_FILEPATH,\n", + " order_by=[\"location_id\"],\n", + " return_query=False,\n", + " filters=[\n", + " {\n", + " \"column\": \"location_id\",\n", + " \"operator\": \"=\",\n", + " \"value\":\"nex-680625\"\n", + " }\n", + " ]\n", + ")\n", + "\n", + "cat_df = tqd.get_timeseries(\n", + " timeseries_filepath=CATCHMENT_FORCINGS_TS_FILEPATH,\n", + " order_by=[\"location_id\", \"value_time\"],\n", + " return_query=False,\n", + " filters=[\n", + " {\n", + " \"column\": \"location_id\",\n", + " \"operator\": \"=\",\n", + " \"value\":\"cat-680625\"\n", + " }\n", + " ]\n", + ")\n", + "# nexus_df\n", + "cat_df\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# sim = nexus_df.hvplot(\"value_time\", \"value\", color=\"red\")\n", + "precip = cat_df.hvplot(\"value_time\", \"value\", color=\"blue\")\n", + "precip" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "# Query the specified study files with a simple group_by `primary_location_id`.\n", + "# Include geometry in the response.\n", + "\n", + "# No USGS gages data for metrics\n", + "query_gdf = tqd.get_metrics(\n", + " primary_filepath=PRIMARY_FILEPATH,\n", + " secondary_filepath=SECONDARY_FILEPATH,\n", + " crosswalk_filepath=CROSSWALK_FILEPATH,\n", + " geometry_filepath=GEOMETRY_FILEPATH,\n", + " group_by=[\"primary_location_id\"],\n", + " order_by=[\"primary_location_id\"],\n", + " return_query=False,\n", + " include_geometry=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_gdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "query_gdf_prj = query_gdf.to_crs(\"EPSG:3857\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "query_hvplot = query_gdf_prj.hvplot(\n", + " # color=\"red\",\n", + " crs=ccrs.GOOGLE_MERCATOR,\n", + " hover_cols=[\"primary_location_id\", \"bias\"],\n", + " c=\"bias\",\n", + " size=100\n", + ")\n", + "(tiles * catchments * nexus * query_hvplot).opts(width=600, height=600, show_legend=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "query_df = tqd.get_joined_timeseries(\n", + " primary_filepath=PRIMARY_FILEPATH,\n", + " secondary_filepath=SECONDARY_FILEPATH,\n", + " crosswalk_filepath=CROSSWALK_FILEPATH,\n", + " geometry_filepath=GEOMETRY_FILEPATH,\n", + " order_by=[\"primary_location_id\"],\n", + " filters=[{\n", + " \"column\": \"primary_location_id\",\n", + " \"operator\": \"=\",\n", + " \"value\": \"usgs-02450180\"\n", + "\n", + " }],\n", + " return_query=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "obs = query_df.hvplot(\"value_time\", \"primary_value\", color=\"blue\")\n", + "sim = query_df.hvplot(\"value_time\", \"secondary_value\", color=\"red\")\n", + "obs * sim" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/_sources/user_guide/notebooks/queries/post_event_data_queries.ipynb.txt b/_sources/user_guide/notebooks/queries/post_event_data_queries.ipynb.txt new file mode 100644 index 00000000..bcc8570f --- /dev/null +++ b/_sources/user_guide/notebooks/queries/post_event_data_queries.ipynb.txt @@ -0,0 +1,337 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run Queries Against Post-Event Dataset\n", + "In this notebook we will run the queries against the post event datasets which are CONUS HUC10 mean areal precipitation that spans 2022-12-18 to 2023-01-18." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import the required packages.\n", + "\n", + "# Need to install TEEHR to avoid this\n", + "import sys\n", + "sys.path.insert(0, \"../../src\")\n", + "\n", + "import holoviews as hv\n", + "import geoviews as gv\n", + "import teehr.queries.duckdb as tqd\n", + "import hvplot.pandas\n", + "import cartopy.crs as ccrs\n", + "from holoviews import opts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## First - MAPs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set some notebook variables to point to the relevant study files.\n", + "FORCING_ANALYSIS_ASSIM = \"/home/jovyan/shared/rti-eval/post-event-example/timeseries/forcing_analysis_assim/*.parquet\"\n", + "FORCING_MEDIUM_RANGE = \"/home/jovyan/shared/rti-eval/post-event-example/timeseries/forcing_medium_range/*.parquet\"\n", + "CATCHMENT_XWALK = \"/home/jovyan/shared/rti-eval/post-event-example/geo/huc10_huc10_crosswalk.parquet\"\n", + "CATCHMENT_GEOMETRY = \"/home/jovyan/shared/rti-eval/post-event-example/geo/huc10_geometry.parquet\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "# Query the specified study files with a simple group_by `primary_location_id`.\n", + "# Include geometry in the response.\n", + "\n", + "query_gdf = tqd.get_metrics(\n", + " primary_filepath=PRIMARY_FILEPATH,\n", + " secondary_filepath=SECONDARY_FILEPATH,\n", + " crosswalk_filepath=CROSSWALK_FILEPATH,\n", + " # geometry_filepath=GEOMETRY_FILEPATH,\n", + " group_by=[\"primary_location_id\", \"reference_time\"],\n", + " order_by=[\"primary_location_id\", \"reference_time\"],\n", + " filters=[\n", + " {\n", + " \"column\": \"primary_location_id\",\n", + " \"operator\": \"like\",\n", + " \"value\": \"huc10-1810020416\"\n", + " },\n", + " {\n", + " \"column\": \"reference_time\",\n", + " \"operator\": \"=\",\n", + " \"value\": \"2022-12-18 12:00:00\"\n", + " },\n", + " ],\n", + " return_query=False,\n", + " include_geometry=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_gdf " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "# Query the specified study files with a simple group_by `primary_location_id`.\n", + "# Include geometry in the response.\n", + "\n", + "query_gdf = tqd.get_metrics(\n", + " primary_filepath=PRIMARY_FILEPATH,\n", + " secondary_filepath=SECONDARY_FILEPATH,\n", + " crosswalk_filepath=CROSSWALK_FILEPATH,\n", + " geometry_filepath=GEOMETRY_FILEPATH,\n", + " group_by=[\"primary_location_id\"],\n", + " order_by=[\"primary_location_id\"],\n", + " filters=[{\n", + " \"column\": \"primary_location_id\",\n", + " \"operator\": \"like\",\n", + " \"value\": \"huc10-18%\"\n", + " }],\n", + " return_query=False,\n", + " include_geometry=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_gdf " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "# Query the specified study files with a simple group_by `primary_location_id`.\n", + "# Include geometry in the response.\n", + "\n", + "query_pd_gdf = tqp.get_metrics(\n", + " primary_filepath=FORCING_ANALYSIS_ASSIM,\n", + " secondary_filepath=FORCING_MEDIUM_RANGE,\n", + " crosswalk_filepath=CATCHMENT_XWALK,\n", + " geometry_filepath=CATCHMENT_GEOMETRY,\n", + " group_by=[\"primary_location_id\"],\n", + " order_by=[\"primary_location_id\"],\n", + " include_metrics=[\"nash_sutcliffe_efficiency\"],\n", + " filters=[{\n", + " \"column\": \"primary_location_id\",\n", + " \"operator\": \"like\",\n", + " \"value\": \"huc10-18%\"\n", + " }],\n", + " return_query=False,\n", + " include_geometry=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_pd_gdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "query_gdf_prj = query_gdf.to_crs(\"EPSG:3857\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "tiles = gv.tile_sources.OSM\n", + "query_hvplot = query_gdf_prj.hvplot(\n", + " color=\"red\",\n", + " crs=ccrs.GOOGLE_MERCATOR,\n", + " hover_cols=[\"nash_sutcliffe_efficiency\"]\n", + ")\n", + "(tiles * query_hvplot).opts(width=600, height=600, show_legend=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "query_df = tqd.get_joined_timeseries(\n", + " primary_filepath=FORCING_ANALYSIS_ASSIM,\n", + " secondary_filepath=FORCING_MEDIUM_RANGE,\n", + " crosswalk_filepath=CATCHMENT_XWALK,\n", + " geometry_filepath=CATCHMENT_GEOMETRY,\n", + " order_by=[\"primary_location_id\"],\n", + " filters=[{\n", + " \"column\": \"primary_location_id\",\n", + " \"operator\": \"=\",\n", + " \"value\": \"huc10-1801010101\"\n", + "\n", + " }],\n", + " return_query=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_df.hvplot(\"value_time\", \"primary_value\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "## Second - Point Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set some notebook variables to point to the relevant study files.\n", + "USGS = \"/home/jovyan/shared/rti-eval/post-event-example/timeseries/usgs/*.parquet\"\n", + "MEDIUM_RANGE_MEM1 = \"/home/jovyan/shared/rti-eval/post-event-example/timeseries/medium_range_mem1/*.parquet\"\n", + "GAGE_XWALK = \"/home/jovyan/shared/rti-eval/post-event-example/geo/usgs_nwm22_crosswalk.parquet\"\n", + "USGS_GEOMETRY = \"/home/jovyan/shared/rti-eval/post-event-example/geo/usgs_geometry.parquet\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "# Query the specified study files with a simple group_by `primary_location_id`.\n", + "# Include geometry in the response.\n", + "\n", + "query_gdf = tqd.get_metrics(\n", + " primary_filepath=USGS,\n", + " secondary_filepath=MEDIUM_RANGE_MEM1,\n", + " crosswalk_filepath=GAGE_XWALK,\n", + " geometry_filepath=USGS_GEOMETRY,\n", + " group_by=[\"primary_location_id\"],\n", + " order_by=[\"primary_location_id\"],\n", + " include_metrics=[\"bias\"],\n", + " filters=[{\n", + " \"column\": \"primary_value\",\n", + " \"operator\": \">\",\n", + " \"value\": \"-999\"\n", + " }],\n", + " return_query=False,\n", + " include_geometry=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_gdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "tiles = gv.tile_sources.OSM\n", + "query_hvplot = query_gdf.to_crs(\"EPSG:3857\").hvplot(\n", + " crs=ccrs.GOOGLE_MERCATOR,\n", + " c=\"bias\"\n", + ")\n", + "(tiles * query_hvplot).opts(width=1200, height=600, show_legend=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/_sources/user_guide/notebooks/queries/test_queries.ipynb.txt b/_sources/user_guide/notebooks/queries/test_queries.ipynb.txt new file mode 100644 index 00000000..c9104f54 --- /dev/null +++ b/_sources/user_guide/notebooks/queries/test_queries.ipynb.txt @@ -0,0 +1,160 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test Queries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import the required packages.\n", + "\n", + "import holoviews as hv\n", + "import geoviews as gv\n", + "import teehr.queries.duckdb as tqd\n", + "import hvplot.pandas\n", + "import cartopy.crs as ccrs\n", + "from holoviews import opts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set some notebook variables to point to the relevant study files.\n", + "\n", + "PRIMARY_FILEPATH = \"/home/matt/repos/teehr/tests/data/test_study/timeseries/*_obs.parquet\"\n", + "SECONDARY_FILEPATH = \"/home/matt/repos/teehr/tests/data/test_study/timeseries/*_fcast.parquet\"\n", + "CROSSWALK_FILEPATH = \"/home/matt/repos/teehr/tests/data/test_study/geo/crosswalk.parquet\"\n", + "GEOMETRY_FILEPATH = \"/home/matt/repos/teehr/tests/data/test_study/geo/gages.parquet\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "# Query the specified study files with a simple group_by `primary_location_id`.\n", + "# Include geometry in the response.\n", + "\n", + "query_gdf = tqd.get_metrics(\n", + " primary_filepath=PRIMARY_FILEPATH,\n", + " secondary_filepath=SECONDARY_FILEPATH,\n", + " crosswalk_filepath=CROSSWALK_FILEPATH,\n", + " geometry_filepath=GEOMETRY_FILEPATH,\n", + " group_by=[\"primary_location_id\"],\n", + " order_by=[\"primary_location_id\"],\n", + " return_query=False,\n", + " include_geometry=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_gdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "query_gdf_prj = query_gdf.to_crs(\"EPSG:3857\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "query_gdf_prj = query_gdf.to_crs(\"EPSG:3857\")\n", + "tiles = gv.tile_sources.OSM\n", + "query_hvplot = query_gdf_prj.hvplot(\n", + " color=\"red\",\n", + " crs=ccrs.GOOGLE_MERCATOR,\n", + " hover_cols=[\"bias\"]\n", + ")\n", + "(tiles * query_hvplot).opts(width=600, height=600)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_df = tqd.get_joined_timeseries(\n", + " primary_filepath=PRIMARY_FILEPATH,\n", + " secondary_filepath=SECONDARY_FILEPATH,\n", + " crosswalk_filepath=CROSSWALK_FILEPATH,\n", + " geometry_filepath=GEOMETRY_FILEPATH,\n", + " order_by=[\"primary_location_id\"],\n", + " filters=[{\n", + " \"column\": \"primary_location_id\",\n", + " \"operator\": \"=\",\n", + " \"value\": \"gage-A\"\n", + "\n", + " }],\n", + " return_query=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_df.hvplot()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "teehr", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/_sources/user_guide/notebooks/queries_examples_index.rst.txt b/_sources/user_guide/notebooks/queries_examples_index.rst.txt new file mode 100644 index 00000000..98c8c49a --- /dev/null +++ b/_sources/user_guide/notebooks/queries_examples_index.rst.txt @@ -0,0 +1,11 @@ +============== +Query Examples +============== + + +.. toctree:: + :maxdepth: 2 + + queries/ngen_simulation_data_queries + queries/post_event_data_queries + queries/test_queries diff --git a/_sphinx_design_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css b/_sphinx_design_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css new file mode 100644 index 00000000..eb19f698 --- /dev/null +++ b/_sphinx_design_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css @@ -0,0 +1 @@ +.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative}details.sd-dropdown .sd-summary-title{font-weight:700;padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary{list-style:none;padding:1em}details.sd-dropdown summary .sd-octicon.no-title{vertical-align:middle}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown summary::-webkit-details-marker{display:none}details.sd-dropdown summary:focus{outline:none}details.sd-dropdown .sd-summary-icon{margin-right:.5em}details.sd-dropdown .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary:hover .sd-summary-up svg,details.sd-dropdown summary:hover .sd-summary-down svg{opacity:1;transform:scale(1.1)}details.sd-dropdown .sd-summary-up svg,details.sd-dropdown .sd-summary-down svg{display:block;opacity:.6}details.sd-dropdown .sd-summary-up,details.sd-dropdown .sd-summary-down{pointer-events:none;position:absolute;right:1em;top:1em}details.sd-dropdown[open]>.sd-summary-title .sd-summary-down{visibility:hidden}details.sd-dropdown:not([open])>.sd-summary-title .sd-summary-up{visibility:hidden}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem} diff --git a/_sphinx_design_static/design-tabs.js b/_sphinx_design_static/design-tabs.js new file mode 100644 index 00000000..36b38cf0 --- /dev/null +++ b/_sphinx_design_static/design-tabs.js @@ -0,0 +1,27 @@ +var sd_labels_by_text = {}; + +function ready() { + const li = document.getElementsByClassName("sd-tab-label"); + for (const label of li) { + syncId = label.getAttribute("data-sync-id"); + if (syncId) { + label.onclick = onLabelClick; + if (!sd_labels_by_text[syncId]) { + sd_labels_by_text[syncId] = []; + } + sd_labels_by_text[syncId].push(label); + } + } +} + +function onLabelClick() { + // Activate other inputs with the same sync id. + syncId = this.getAttribute("data-sync-id"); + for (label of sd_labels_by_text[syncId]) { + if (label === this) continue; + label.previousElementSibling.checked = true; + } + window.localStorage.setItem("sphinx-design-last-tab", syncId); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..e760386b --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 270px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/css/teehr.css b/_static/css/teehr.css new file mode 100644 index 00000000..9723e6c3 --- /dev/null +++ b/_static/css/teehr.css @@ -0,0 +1,70 @@ +/* Override some aspects of the pydata-sphinx-theme */ + +:root { + /* Use softer blue from bootstrap's default info color */ + --pst-color-info: 23, 162, 184; + } + + table { + width: auto; /* Override fit-content which breaks Styler user guide ipynb */ + } + + /* Main index page overview cards */ + + .intro-card { + padding: 30px 10px 20px 10px; + } + + .intro-card .sd-card-img-top { + margin: 10px; + height: 52px; + background: none !important; + } + + .intro-card .sd-card-title { + color: var(--pst-color-primary); + font-size: var(--pst-font-size-h5); + padding: 1rem 0rem 0.5rem 0rem; + } + + .intro-card .sd-card-footer { + border: none !important; + } + + .intro-card .sd-card-footer p.sd-card-text { + max-width: 220px; + margin-left: auto; + margin-right: auto; + } + + .intro-card .sd-btn-secondary { + background-color: #6c757d !important; + border-color: #6c757d !important; + } + + .intro-card .sd-btn-secondary:hover { + background-color: #5a6268 !important; + border-color: #545b62 !important; + } + + .card, .card img { + background-color: var(--pst-color-background); + } + + .ciroh-card { + margin: 10px; + height: 140px; + background: none !important; + } + + html[data-theme="dark"] { + h1 { + color: rgb(138, 189, 216); + } + h2 { + color: rgb(138, 189, 216); + } + h3 { + color: rgb(138, 189, 216); + } +} \ No newline at end of file diff --git a/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css b/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css new file mode 100644 index 00000000..eb19f698 --- /dev/null +++ b/_static/design-style.1e8bd061cd6da7fc9cf755528e8ffc24.min.css @@ -0,0 +1 @@ +.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative}details.sd-dropdown .sd-summary-title{font-weight:700;padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary{list-style:none;padding:1em}details.sd-dropdown summary .sd-octicon.no-title{vertical-align:middle}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown summary::-webkit-details-marker{display:none}details.sd-dropdown summary:focus{outline:none}details.sd-dropdown .sd-summary-icon{margin-right:.5em}details.sd-dropdown .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary:hover .sd-summary-up svg,details.sd-dropdown summary:hover .sd-summary-down svg{opacity:1;transform:scale(1.1)}details.sd-dropdown .sd-summary-up svg,details.sd-dropdown .sd-summary-down svg{display:block;opacity:.6}details.sd-dropdown .sd-summary-up,details.sd-dropdown .sd-summary-down{pointer-events:none;position:absolute;right:1em;top:1em}details.sd-dropdown[open]>.sd-summary-title .sd-summary-down{visibility:hidden}details.sd-dropdown:not([open])>.sd-summary-title .sd-summary-up{visibility:hidden}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem} diff --git a/_static/design-tabs.js b/_static/design-tabs.js new file mode 100644 index 00000000..36b38cf0 --- /dev/null +++ b/_static/design-tabs.js @@ -0,0 +1,27 @@ +var sd_labels_by_text = {}; + +function ready() { + const li = document.getElementsByClassName("sd-tab-label"); + for (const label of li) { + syncId = label.getAttribute("data-sync-id"); + if (syncId) { + label.onclick = onLabelClick; + if (!sd_labels_by_text[syncId]) { + sd_labels_by_text[syncId] = []; + } + sd_labels_by_text[syncId].push(label); + } + } +} + +function onLabelClick() { + // Activate other inputs with the same sync id. + syncId = this.getAttribute("data-sync-id"); + for (label of sd_labels_by_text[syncId]) { + if (label === this) continue; + label.previousElementSibling.checked = true; + } + window.localStorage.setItem("sphinx-design-last-tab", syncId); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 00000000..d06a71d7 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 00000000..a18beba7 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '0.3.2', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/favicon.png b/_static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a29efba6a4df0aa032e07f8a840fca15ff190046 GIT binary patch literal 507 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!h6$c7jv*25$r7x}at2PBtC)5l+ImgC{{Qy2 zqK!;(W@>H9wT`hSg$=n!r?vIh&wRBoW=+HN*2ae(v(<%#r`RVXTwz?yx1g8#-@l*7 zKm7h4zvsvK`TG@?x}+cOcyx5l_4j)c+KURCZs~kqFDx#b^!LkCYv~XU?FUbvC0K0I z`ObZjujAFN`BhJkPR|U`EBo-@-nqD8TFLoq*^=`A4X!!40^Jbi)}}0b@4%yuhyy21 zoH%giOp~~=A!E0=LITq?{Tojv8iu^h(32=V&|uy0B1hu#F7Icj7MI@wn#ORZZEd00 z@7-bZC;tBNc)d}G2+-byBnunE9QDO@2am-D7|wC%oo+e3x83{asn6$G0~FspxNqO~ zOknSZd1u3G?Ej^8w00%2E+~3XFnzuC;<%UF{)4rA-Y+Swzy0CV@cU^_ts9spF#j<* zQoZf|38~x(0^Dx01|Oa_)ZN>q8erzP;Mnn~fzxylNGvJd(O1%L|YD%~$s^SuwD&F}z9aigSK$a1R(^44$rjF6*2UngE;_;Y0uc literal 0 HcmV?d00001 diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHDfiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/_static/graphviz.css b/_static/graphviz.css new file mode 100644 index 00000000..8d81c02e --- /dev/null +++ b/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/_static/index_api.svg b/_static/index_api.svg new file mode 100644 index 00000000..69f7ba1d --- /dev/null +++ b/_static/index_api.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/_static/index_contribute.svg b/_static/index_contribute.svg new file mode 100644 index 00000000..de3d9023 --- /dev/null +++ b/_static/index_contribute.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/_static/index_getting_started.svg b/_static/index_getting_started.svg new file mode 100644 index 00000000..2d36622c --- /dev/null +++ b/_static/index_getting_started.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/_static/index_user_guide.svg b/_static/index_user_guide.svg new file mode 100644 index 00000000..bd170535 --- /dev/null +++ b/_static/index_user_guide.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 00000000..250f5665 --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css b/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css new file mode 100644 index 00000000..33566310 --- /dev/null +++ b/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css @@ -0,0 +1,2342 @@ +/* Variables */ +:root { + --mystnb-source-bg-color: #f7f7f7; + --mystnb-stdout-bg-color: #fcfcfc; + --mystnb-stderr-bg-color: #fdd; + --mystnb-traceback-bg-color: #fcfcfc; + --mystnb-source-border-color: #ccc; + --mystnb-source-margin-color: green; + --mystnb-stdout-border-color: #f7f7f7; + --mystnb-stderr-border-color: #f7f7f7; + --mystnb-traceback-border-color: #ffd6d6; + --mystnb-hide-prompt-opacity: 70%; + --mystnb-source-border-radius: .4em; + --mystnb-source-border-width: 1px; +} + +/* Whole cell */ +div.container.cell { + padding-left: 0; + margin-bottom: 1em; +} + +/* Removing all background formatting so we can control at the div level */ +.cell_input div.highlight, +.cell_output pre, +.cell_input pre, +.cell_output .output { + border: none; + box-shadow: none; +} + +.cell_output .output pre, +.cell_input pre { + margin: 0px; +} + +/* Input cells */ +div.cell div.cell_input, +div.cell details.above-input>summary { + padding-left: 0em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + background-color: var(--mystnb-source-bg-color); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; + border-radius: var(--mystnb-source-border-radius); +} + +div.cell_input>div, +div.cell_output div.output>div.highlight { + margin: 0em !important; + border: none !important; +} + +/* All cell outputs */ +.cell_output { + padding-left: 1em; + padding-right: 0em; + margin-top: 1em; +} + +/* Text outputs from cells */ +.cell_output .output.text_plain, +.cell_output .output.traceback, +.cell_output .output.stream, +.cell_output .output.stderr { + margin-top: 1em; + margin-bottom: 0em; + box-shadow: none; +} + +.cell_output .output.text_plain, +.cell_output .output.stream { + background: var(--mystnb-stdout-bg-color); + border: 1px solid var(--mystnb-stdout-border-color); +} + +.cell_output .output.stderr { + background: var(--mystnb-stderr-bg-color); + border: 1px solid var(--mystnb-stderr-border-color); +} + +.cell_output .output.traceback { + background: var(--mystnb-traceback-bg-color); + border: 1px solid var(--mystnb-traceback-border-color); +} + +/* Collapsible cell content */ +div.cell details.above-input div.cell_input { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed; +} + +div.cell div.cell_input.above-output-prompt { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +div.cell details.above-input>summary { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed; + padding-left: 1em; + margin-bottom: 0; +} + +div.cell details.above-output>summary { + background-color: var(--mystnb-source-bg-color); + padding-left: 1em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + border-radius: var(--mystnb-source-border-radius); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; +} + +div.cell details.below-input>summary { + background-color: var(--mystnb-source-bg-color); + padding-left: 1em; + padding-right: 0em; + border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid; + border-top: none; + border-bottom-left-radius: var(--mystnb-source-border-radius); + border-bottom-right-radius: var(--mystnb-source-border-radius); + border-left-color: var(--mystnb-source-margin-color); + border-left-width: medium; +} + +div.cell details.hide>summary>span { + opacity: var(--mystnb-hide-prompt-opacity); +} + +div.cell details.hide[open]>summary>span.collapsed { + display: none; +} + +div.cell details.hide:not([open])>summary>span.expanded { + display: none; +} + +@keyframes collapsed-fade-in { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} +div.cell details.hide[open]>summary~* { + -moz-animation: collapsed-fade-in 0.3s ease-in-out; + -webkit-animation: collapsed-fade-in 0.3s ease-in-out; + animation: collapsed-fade-in 0.3s ease-in-out; +} + +/* Math align to the left */ +.cell_output .MathJax_Display { + text-align: left !important; +} + +/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */ +div.cell_output table { + border: none; + border-collapse: collapse; + border-spacing: 0; + color: black; + font-size: 1em; + table-layout: fixed; +} + +div.cell_output thead { + border-bottom: 1px solid black; + vertical-align: bottom; +} + +div.cell_output tr, +div.cell_output th, +div.cell_output td { + text-align: right; + vertical-align: middle; + padding: 0.5em 0.5em; + line-height: normal; + white-space: normal; + max-width: none; + border: none; +} + +div.cell_output th { + font-weight: bold; +} + +div.cell_output tbody tr:nth-child(odd) { + background: #f5f5f5; +} + +div.cell_output tbody tr:hover { + background: rgba(66, 165, 245, 0.2); +} + +/** source code line numbers **/ +span.linenos { + opacity: 0.5; +} + +/* Inline text from `paste` operation */ + +span.pasted-text { + font-weight: bold; +} + +span.pasted-inline img { + max-height: 2em; +} + +tbody span.pasted-inline img { + max-height: none; +} + +/* Font colors for translated ANSI escape sequences +Color values are copied from Jupyter Notebook +https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21 +Background colors from +https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors +*/ +div.highlight .-Color-Bold { + font-weight: bold; +} + +div.highlight .-Color[class*=-Black] { + color: #3E424D +} + +div.highlight .-Color[class*=-Red] { + color: #E75C58 +} + +div.highlight .-Color[class*=-Green] { + color: #00A250 +} + +div.highlight .-Color[class*=-Yellow] { + color: #DDB62B +} + +div.highlight .-Color[class*=-Blue] { + color: #208FFB +} + +div.highlight .-Color[class*=-Magenta] { + color: #D160C4 +} + +div.highlight .-Color[class*=-Cyan] { + color: #60C6C8 +} + +div.highlight .-Color[class*=-White] { + color: #C5C1B4 +} + +div.highlight .-Color[class*=-BGBlack] { + background-color: #3E424D +} + +div.highlight .-Color[class*=-BGRed] { + background-color: #E75C58 +} + +div.highlight .-Color[class*=-BGGreen] { + background-color: #00A250 +} + +div.highlight .-Color[class*=-BGYellow] { + background-color: #DDB62B +} + +div.highlight .-Color[class*=-BGBlue] { + background-color: #208FFB +} + +div.highlight .-Color[class*=-BGMagenta] { + background-color: #D160C4 +} + +div.highlight .-Color[class*=-BGCyan] { + background-color: #60C6C8 +} + +div.highlight .-Color[class*=-BGWhite] { + background-color: #C5C1B4 +} + +/* Font colors for 8-bit ANSI */ + +div.highlight .-Color[class*=-C0] { + color: #000000 +} + +div.highlight .-Color[class*=-BGC0] { + background-color: #000000 +} + +div.highlight .-Color[class*=-C1] { + color: #800000 +} + +div.highlight .-Color[class*=-BGC1] { + background-color: #800000 +} + +div.highlight .-Color[class*=-C2] { + color: #008000 +} + +div.highlight .-Color[class*=-BGC2] { + background-color: #008000 +} + +div.highlight .-Color[class*=-C3] { + color: #808000 +} + +div.highlight .-Color[class*=-BGC3] { + background-color: #808000 +} + +div.highlight .-Color[class*=-C4] { + color: #000080 +} + +div.highlight .-Color[class*=-BGC4] { + background-color: #000080 +} + +div.highlight .-Color[class*=-C5] { + color: #800080 +} + +div.highlight .-Color[class*=-BGC5] { + background-color: #800080 +} + +div.highlight .-Color[class*=-C6] { + color: #008080 +} + +div.highlight .-Color[class*=-BGC6] { + background-color: #008080 +} + +div.highlight .-Color[class*=-C7] { + color: #C0C0C0 +} + +div.highlight .-Color[class*=-BGC7] { + background-color: #C0C0C0 +} + +div.highlight .-Color[class*=-C8] { + color: #808080 +} + +div.highlight .-Color[class*=-BGC8] { + background-color: #808080 +} + +div.highlight .-Color[class*=-C9] { + color: #FF0000 +} + +div.highlight .-Color[class*=-BGC9] { + background-color: #FF0000 +} + +div.highlight .-Color[class*=-C10] { + color: #00FF00 +} + +div.highlight .-Color[class*=-BGC10] { + background-color: #00FF00 +} + +div.highlight .-Color[class*=-C11] { + color: #FFFF00 +} + +div.highlight .-Color[class*=-BGC11] { + background-color: #FFFF00 +} + +div.highlight .-Color[class*=-C12] { + color: #0000FF +} + +div.highlight .-Color[class*=-BGC12] { + background-color: #0000FF +} + +div.highlight .-Color[class*=-C13] { + color: #FF00FF +} + +div.highlight .-Color[class*=-BGC13] { + background-color: #FF00FF +} + +div.highlight .-Color[class*=-C14] { + color: #00FFFF +} + +div.highlight .-Color[class*=-BGC14] { + background-color: #00FFFF +} + +div.highlight .-Color[class*=-C15] { + color: #FFFFFF +} + +div.highlight .-Color[class*=-BGC15] { + background-color: #FFFFFF +} + +div.highlight .-Color[class*=-C16] { + color: #000000 +} + +div.highlight .-Color[class*=-BGC16] { + background-color: #000000 +} + +div.highlight .-Color[class*=-C17] { + color: #00005F +} + +div.highlight .-Color[class*=-BGC17] { + background-color: #00005F +} + +div.highlight .-Color[class*=-C18] { + color: #000087 +} + +div.highlight .-Color[class*=-BGC18] { + background-color: #000087 +} + +div.highlight .-Color[class*=-C19] { + color: #0000AF +} + +div.highlight .-Color[class*=-BGC19] { + background-color: #0000AF +} + +div.highlight .-Color[class*=-C20] { + color: #0000D7 +} + +div.highlight .-Color[class*=-BGC20] { + background-color: #0000D7 +} + +div.highlight .-Color[class*=-C21] { + color: #0000FF +} + +div.highlight .-Color[class*=-BGC21] { + background-color: #0000FF +} + +div.highlight .-Color[class*=-C22] { + color: #005F00 +} + +div.highlight .-Color[class*=-BGC22] { + background-color: #005F00 +} + +div.highlight .-Color[class*=-C23] { + color: #005F5F +} + +div.highlight .-Color[class*=-BGC23] { + background-color: #005F5F +} + +div.highlight .-Color[class*=-C24] { + color: #005F87 +} + +div.highlight .-Color[class*=-BGC24] { + background-color: #005F87 +} + +div.highlight .-Color[class*=-C25] { + color: #005FAF +} + +div.highlight .-Color[class*=-BGC25] { + background-color: #005FAF +} + +div.highlight .-Color[class*=-C26] { + color: #005FD7 +} + +div.highlight .-Color[class*=-BGC26] { + background-color: #005FD7 +} + +div.highlight .-Color[class*=-C27] { + color: #005FFF +} + +div.highlight .-Color[class*=-BGC27] { + background-color: #005FFF +} + +div.highlight .-Color[class*=-C28] { + color: #008700 +} + +div.highlight .-Color[class*=-BGC28] { + background-color: #008700 +} + +div.highlight .-Color[class*=-C29] { + color: #00875F +} + +div.highlight .-Color[class*=-BGC29] { + background-color: #00875F +} + +div.highlight .-Color[class*=-C30] { + color: #008787 +} + +div.highlight .-Color[class*=-BGC30] { + background-color: #008787 +} + +div.highlight .-Color[class*=-C31] { + color: #0087AF +} + +div.highlight .-Color[class*=-BGC31] { + background-color: #0087AF +} + +div.highlight .-Color[class*=-C32] { + color: #0087D7 +} + +div.highlight .-Color[class*=-BGC32] { + background-color: #0087D7 +} + +div.highlight .-Color[class*=-C33] { + color: #0087FF +} + +div.highlight .-Color[class*=-BGC33] { + background-color: #0087FF +} + +div.highlight .-Color[class*=-C34] { + color: #00AF00 +} + +div.highlight .-Color[class*=-BGC34] { + background-color: #00AF00 +} + +div.highlight .-Color[class*=-C35] { + color: #00AF5F +} + +div.highlight .-Color[class*=-BGC35] { + background-color: #00AF5F +} + +div.highlight .-Color[class*=-C36] { + color: #00AF87 +} + +div.highlight .-Color[class*=-BGC36] { + background-color: #00AF87 +} + +div.highlight .-Color[class*=-C37] { + color: #00AFAF +} + +div.highlight .-Color[class*=-BGC37] { + background-color: #00AFAF +} + +div.highlight .-Color[class*=-C38] { + color: #00AFD7 +} + +div.highlight .-Color[class*=-BGC38] { + background-color: #00AFD7 +} + +div.highlight .-Color[class*=-C39] { + color: #00AFFF +} + +div.highlight .-Color[class*=-BGC39] { + background-color: #00AFFF +} + +div.highlight .-Color[class*=-C40] { + color: #00D700 +} + +div.highlight .-Color[class*=-BGC40] { + background-color: #00D700 +} + +div.highlight .-Color[class*=-C41] { + color: #00D75F +} + +div.highlight .-Color[class*=-BGC41] { + background-color: #00D75F +} + +div.highlight .-Color[class*=-C42] { + color: #00D787 +} + +div.highlight .-Color[class*=-BGC42] { + background-color: #00D787 +} + +div.highlight .-Color[class*=-C43] { + color: #00D7AF +} + +div.highlight .-Color[class*=-BGC43] { + background-color: #00D7AF +} + +div.highlight .-Color[class*=-C44] { + color: #00D7D7 +} + +div.highlight .-Color[class*=-BGC44] { + background-color: #00D7D7 +} + +div.highlight .-Color[class*=-C45] { + color: #00D7FF +} + +div.highlight .-Color[class*=-BGC45] { + background-color: #00D7FF +} + +div.highlight .-Color[class*=-C46] { + color: #00FF00 +} + +div.highlight .-Color[class*=-BGC46] { + background-color: #00FF00 +} + +div.highlight .-Color[class*=-C47] { + color: #00FF5F +} + +div.highlight .-Color[class*=-BGC47] { + background-color: #00FF5F +} + +div.highlight .-Color[class*=-C48] { + color: #00FF87 +} + +div.highlight .-Color[class*=-BGC48] { + background-color: #00FF87 +} + +div.highlight .-Color[class*=-C49] { + color: #00FFAF +} + +div.highlight .-Color[class*=-BGC49] { + background-color: #00FFAF +} + +div.highlight .-Color[class*=-C50] { + color: #00FFD7 +} + +div.highlight .-Color[class*=-BGC50] { + background-color: #00FFD7 +} + +div.highlight .-Color[class*=-C51] { + color: #00FFFF +} + +div.highlight .-Color[class*=-BGC51] { + background-color: #00FFFF +} + +div.highlight .-Color[class*=-C52] { + color: #5F0000 +} + +div.highlight .-Color[class*=-BGC52] { + background-color: #5F0000 +} + +div.highlight .-Color[class*=-C53] { + color: #5F005F +} + +div.highlight .-Color[class*=-BGC53] { + background-color: #5F005F +} + +div.highlight .-Color[class*=-C54] { + color: #5F0087 +} + +div.highlight .-Color[class*=-BGC54] { + background-color: #5F0087 +} + +div.highlight .-Color[class*=-C55] { + color: #5F00AF +} + +div.highlight .-Color[class*=-BGC55] { + background-color: #5F00AF +} + +div.highlight .-Color[class*=-C56] { + color: #5F00D7 +} + +div.highlight .-Color[class*=-BGC56] { + background-color: #5F00D7 +} + +div.highlight .-Color[class*=-C57] { + color: #5F00FF +} + +div.highlight .-Color[class*=-BGC57] { + background-color: #5F00FF +} + +div.highlight .-Color[class*=-C58] { + color: #5F5F00 +} + +div.highlight .-Color[class*=-BGC58] { + background-color: #5F5F00 +} + +div.highlight .-Color[class*=-C59] { + color: #5F5F5F +} + +div.highlight .-Color[class*=-BGC59] { + background-color: #5F5F5F +} + +div.highlight .-Color[class*=-C60] { + color: #5F5F87 +} + +div.highlight .-Color[class*=-BGC60] { + background-color: #5F5F87 +} + +div.highlight .-Color[class*=-C61] { + color: #5F5FAF +} + +div.highlight .-Color[class*=-BGC61] { + background-color: #5F5FAF +} + +div.highlight .-Color[class*=-C62] { + color: #5F5FD7 +} + +div.highlight .-Color[class*=-BGC62] { + background-color: #5F5FD7 +} + +div.highlight .-Color[class*=-C63] { + color: #5F5FFF +} + +div.highlight .-Color[class*=-BGC63] { + background-color: #5F5FFF +} + +div.highlight .-Color[class*=-C64] { + color: #5F8700 +} + +div.highlight .-Color[class*=-BGC64] { + background-color: #5F8700 +} + +div.highlight .-Color[class*=-C65] { + color: #5F875F +} + +div.highlight .-Color[class*=-BGC65] { + background-color: #5F875F +} + +div.highlight .-Color[class*=-C66] { + color: #5F8787 +} + +div.highlight .-Color[class*=-BGC66] { + background-color: #5F8787 +} + +div.highlight .-Color[class*=-C67] { + color: #5F87AF +} + +div.highlight .-Color[class*=-BGC67] { + background-color: #5F87AF +} + +div.highlight .-Color[class*=-C68] { + color: #5F87D7 +} + +div.highlight .-Color[class*=-BGC68] { + background-color: #5F87D7 +} + +div.highlight .-Color[class*=-C69] { + color: #5F87FF +} + +div.highlight .-Color[class*=-BGC69] { + background-color: #5F87FF +} + +div.highlight .-Color[class*=-C70] { + color: #5FAF00 +} + +div.highlight .-Color[class*=-BGC70] { + background-color: #5FAF00 +} + +div.highlight .-Color[class*=-C71] { + color: #5FAF5F +} + +div.highlight .-Color[class*=-BGC71] { + background-color: #5FAF5F +} + +div.highlight .-Color[class*=-C72] { + color: #5FAF87 +} + +div.highlight .-Color[class*=-BGC72] { + background-color: #5FAF87 +} + +div.highlight .-Color[class*=-C73] { + color: #5FAFAF +} + +div.highlight .-Color[class*=-BGC73] { + background-color: #5FAFAF +} + +div.highlight .-Color[class*=-C74] { + color: #5FAFD7 +} + +div.highlight .-Color[class*=-BGC74] { + background-color: #5FAFD7 +} + +div.highlight .-Color[class*=-C75] { + color: #5FAFFF +} + +div.highlight .-Color[class*=-BGC75] { + background-color: #5FAFFF +} + +div.highlight .-Color[class*=-C76] { + color: #5FD700 +} + +div.highlight .-Color[class*=-BGC76] { + background-color: #5FD700 +} + +div.highlight .-Color[class*=-C77] { + color: #5FD75F +} + +div.highlight .-Color[class*=-BGC77] { + background-color: #5FD75F +} + +div.highlight .-Color[class*=-C78] { + color: #5FD787 +} + +div.highlight .-Color[class*=-BGC78] { + background-color: #5FD787 +} + +div.highlight .-Color[class*=-C79] { + color: #5FD7AF +} + +div.highlight .-Color[class*=-BGC79] { + background-color: #5FD7AF +} + +div.highlight .-Color[class*=-C80] { + color: #5FD7D7 +} + +div.highlight .-Color[class*=-BGC80] { + background-color: #5FD7D7 +} + +div.highlight .-Color[class*=-C81] { + color: #5FD7FF +} + +div.highlight .-Color[class*=-BGC81] { + background-color: #5FD7FF +} + +div.highlight .-Color[class*=-C82] { + color: #5FFF00 +} + +div.highlight .-Color[class*=-BGC82] { + background-color: #5FFF00 +} + +div.highlight .-Color[class*=-C83] { + color: #5FFF5F +} + +div.highlight .-Color[class*=-BGC83] { + background-color: #5FFF5F +} + +div.highlight .-Color[class*=-C84] { + color: #5FFF87 +} + +div.highlight .-Color[class*=-BGC84] { + background-color: #5FFF87 +} + +div.highlight .-Color[class*=-C85] { + color: #5FFFAF +} + +div.highlight .-Color[class*=-BGC85] { + background-color: #5FFFAF +} + +div.highlight .-Color[class*=-C86] { + color: #5FFFD7 +} + +div.highlight .-Color[class*=-BGC86] { + background-color: #5FFFD7 +} + +div.highlight .-Color[class*=-C87] { + color: #5FFFFF +} + +div.highlight .-Color[class*=-BGC87] { + background-color: #5FFFFF +} + +div.highlight .-Color[class*=-C88] { + color: #870000 +} + +div.highlight .-Color[class*=-BGC88] { + background-color: #870000 +} + +div.highlight .-Color[class*=-C89] { + color: #87005F +} + +div.highlight .-Color[class*=-BGC89] { + background-color: #87005F +} + +div.highlight .-Color[class*=-C90] { + color: #870087 +} + +div.highlight .-Color[class*=-BGC90] { + background-color: #870087 +} + +div.highlight .-Color[class*=-C91] { + color: #8700AF +} + +div.highlight .-Color[class*=-BGC91] { + background-color: #8700AF +} + +div.highlight .-Color[class*=-C92] { + color: #8700D7 +} + +div.highlight .-Color[class*=-BGC92] { + background-color: #8700D7 +} + +div.highlight .-Color[class*=-C93] { + color: #8700FF +} + +div.highlight .-Color[class*=-BGC93] { + background-color: #8700FF +} + +div.highlight .-Color[class*=-C94] { + color: #875F00 +} + +div.highlight .-Color[class*=-BGC94] { + background-color: #875F00 +} + +div.highlight .-Color[class*=-C95] { + color: #875F5F +} + +div.highlight .-Color[class*=-BGC95] { + background-color: #875F5F +} + +div.highlight .-Color[class*=-C96] { + color: #875F87 +} + +div.highlight .-Color[class*=-BGC96] { + background-color: #875F87 +} + +div.highlight .-Color[class*=-C97] { + color: #875FAF +} + +div.highlight .-Color[class*=-BGC97] { + background-color: #875FAF +} + +div.highlight .-Color[class*=-C98] { + color: #875FD7 +} + +div.highlight .-Color[class*=-BGC98] { + background-color: #875FD7 +} + +div.highlight .-Color[class*=-C99] { + color: #875FFF +} + +div.highlight .-Color[class*=-BGC99] { + background-color: #875FFF +} + +div.highlight .-Color[class*=-C100] { + color: #878700 +} + +div.highlight .-Color[class*=-BGC100] { + background-color: #878700 +} + +div.highlight .-Color[class*=-C101] { + color: #87875F +} + +div.highlight .-Color[class*=-BGC101] { + background-color: #87875F +} + +div.highlight .-Color[class*=-C102] { + color: #878787 +} + +div.highlight .-Color[class*=-BGC102] { + background-color: #878787 +} + +div.highlight .-Color[class*=-C103] { + color: #8787AF +} + +div.highlight .-Color[class*=-BGC103] { + background-color: #8787AF +} + +div.highlight .-Color[class*=-C104] { + color: #8787D7 +} + +div.highlight .-Color[class*=-BGC104] { + background-color: #8787D7 +} + +div.highlight .-Color[class*=-C105] { + color: #8787FF +} + +div.highlight .-Color[class*=-BGC105] { + background-color: #8787FF +} + +div.highlight .-Color[class*=-C106] { + color: #87AF00 +} + +div.highlight .-Color[class*=-BGC106] { + background-color: #87AF00 +} + +div.highlight .-Color[class*=-C107] { + color: #87AF5F +} + +div.highlight .-Color[class*=-BGC107] { + background-color: #87AF5F +} + +div.highlight .-Color[class*=-C108] { + color: #87AF87 +} + +div.highlight .-Color[class*=-BGC108] { + background-color: #87AF87 +} + +div.highlight .-Color[class*=-C109] { + color: #87AFAF +} + +div.highlight .-Color[class*=-BGC109] { + background-color: #87AFAF +} + +div.highlight .-Color[class*=-C110] { + color: #87AFD7 +} + +div.highlight .-Color[class*=-BGC110] { + background-color: #87AFD7 +} + +div.highlight .-Color[class*=-C111] { + color: #87AFFF +} + +div.highlight .-Color[class*=-BGC111] { + background-color: #87AFFF +} + +div.highlight .-Color[class*=-C112] { + color: #87D700 +} + +div.highlight .-Color[class*=-BGC112] { + background-color: #87D700 +} + +div.highlight .-Color[class*=-C113] { + color: #87D75F +} + +div.highlight .-Color[class*=-BGC113] { + background-color: #87D75F +} + +div.highlight .-Color[class*=-C114] { + color: #87D787 +} + +div.highlight .-Color[class*=-BGC114] { + background-color: #87D787 +} + +div.highlight .-Color[class*=-C115] { + color: #87D7AF +} + +div.highlight .-Color[class*=-BGC115] { + background-color: #87D7AF +} + +div.highlight .-Color[class*=-C116] { + color: #87D7D7 +} + +div.highlight .-Color[class*=-BGC116] { + background-color: #87D7D7 +} + +div.highlight .-Color[class*=-C117] { + color: #87D7FF +} + +div.highlight .-Color[class*=-BGC117] { + background-color: #87D7FF +} + +div.highlight .-Color[class*=-C118] { + color: #87FF00 +} + +div.highlight .-Color[class*=-BGC118] { + background-color: #87FF00 +} + +div.highlight .-Color[class*=-C119] { + color: #87FF5F +} + +div.highlight .-Color[class*=-BGC119] { + background-color: #87FF5F +} + +div.highlight .-Color[class*=-C120] { + color: #87FF87 +} + +div.highlight .-Color[class*=-BGC120] { + background-color: #87FF87 +} + +div.highlight .-Color[class*=-C121] { + color: #87FFAF +} + +div.highlight .-Color[class*=-BGC121] { + background-color: #87FFAF +} + +div.highlight .-Color[class*=-C122] { + color: #87FFD7 +} + +div.highlight .-Color[class*=-BGC122] { + background-color: #87FFD7 +} + +div.highlight .-Color[class*=-C123] { + color: #87FFFF +} + +div.highlight .-Color[class*=-BGC123] { + background-color: #87FFFF +} + +div.highlight .-Color[class*=-C124] { + color: #AF0000 +} + +div.highlight .-Color[class*=-BGC124] { + background-color: #AF0000 +} + +div.highlight .-Color[class*=-C125] { + color: #AF005F +} + +div.highlight .-Color[class*=-BGC125] { + background-color: #AF005F +} + +div.highlight .-Color[class*=-C126] { + color: #AF0087 +} + +div.highlight .-Color[class*=-BGC126] { + background-color: #AF0087 +} + +div.highlight .-Color[class*=-C127] { + color: #AF00AF +} + +div.highlight .-Color[class*=-BGC127] { + background-color: #AF00AF +} + +div.highlight .-Color[class*=-C128] { + color: #AF00D7 +} + +div.highlight .-Color[class*=-BGC128] { + background-color: #AF00D7 +} + +div.highlight .-Color[class*=-C129] { + color: #AF00FF +} + +div.highlight .-Color[class*=-BGC129] { + background-color: #AF00FF +} + +div.highlight .-Color[class*=-C130] { + color: #AF5F00 +} + +div.highlight .-Color[class*=-BGC130] { + background-color: #AF5F00 +} + +div.highlight .-Color[class*=-C131] { + color: #AF5F5F +} + +div.highlight .-Color[class*=-BGC131] { + background-color: #AF5F5F +} + +div.highlight .-Color[class*=-C132] { + color: #AF5F87 +} + +div.highlight .-Color[class*=-BGC132] { + background-color: #AF5F87 +} + +div.highlight .-Color[class*=-C133] { + color: #AF5FAF +} + +div.highlight .-Color[class*=-BGC133] { + background-color: #AF5FAF +} + +div.highlight .-Color[class*=-C134] { + color: #AF5FD7 +} + +div.highlight .-Color[class*=-BGC134] { + background-color: #AF5FD7 +} + +div.highlight .-Color[class*=-C135] { + color: #AF5FFF +} + +div.highlight .-Color[class*=-BGC135] { + background-color: #AF5FFF +} + +div.highlight .-Color[class*=-C136] { + color: #AF8700 +} + +div.highlight .-Color[class*=-BGC136] { + background-color: #AF8700 +} + +div.highlight .-Color[class*=-C137] { + color: #AF875F +} + +div.highlight .-Color[class*=-BGC137] { + background-color: #AF875F +} + +div.highlight .-Color[class*=-C138] { + color: #AF8787 +} + +div.highlight .-Color[class*=-BGC138] { + background-color: #AF8787 +} + +div.highlight .-Color[class*=-C139] { + color: #AF87AF +} + +div.highlight .-Color[class*=-BGC139] { + background-color: #AF87AF +} + +div.highlight .-Color[class*=-C140] { + color: #AF87D7 +} + +div.highlight .-Color[class*=-BGC140] { + background-color: #AF87D7 +} + +div.highlight .-Color[class*=-C141] { + color: #AF87FF +} + +div.highlight .-Color[class*=-BGC141] { + background-color: #AF87FF +} + +div.highlight .-Color[class*=-C142] { + color: #AFAF00 +} + +div.highlight .-Color[class*=-BGC142] { + background-color: #AFAF00 +} + +div.highlight .-Color[class*=-C143] { + color: #AFAF5F +} + +div.highlight .-Color[class*=-BGC143] { + background-color: #AFAF5F +} + +div.highlight .-Color[class*=-C144] { + color: #AFAF87 +} + +div.highlight .-Color[class*=-BGC144] { + background-color: #AFAF87 +} + +div.highlight .-Color[class*=-C145] { + color: #AFAFAF +} + +div.highlight .-Color[class*=-BGC145] { + background-color: #AFAFAF +} + +div.highlight .-Color[class*=-C146] { + color: #AFAFD7 +} + +div.highlight .-Color[class*=-BGC146] { + background-color: #AFAFD7 +} + +div.highlight .-Color[class*=-C147] { + color: #AFAFFF +} + +div.highlight .-Color[class*=-BGC147] { + background-color: #AFAFFF +} + +div.highlight .-Color[class*=-C148] { + color: #AFD700 +} + +div.highlight .-Color[class*=-BGC148] { + background-color: #AFD700 +} + +div.highlight .-Color[class*=-C149] { + color: #AFD75F +} + +div.highlight .-Color[class*=-BGC149] { + background-color: #AFD75F +} + +div.highlight .-Color[class*=-C150] { + color: #AFD787 +} + +div.highlight .-Color[class*=-BGC150] { + background-color: #AFD787 +} + +div.highlight .-Color[class*=-C151] { + color: #AFD7AF +} + +div.highlight .-Color[class*=-BGC151] { + background-color: #AFD7AF +} + +div.highlight .-Color[class*=-C152] { + color: #AFD7D7 +} + +div.highlight .-Color[class*=-BGC152] { + background-color: #AFD7D7 +} + +div.highlight .-Color[class*=-C153] { + color: #AFD7FF +} + +div.highlight .-Color[class*=-BGC153] { + background-color: #AFD7FF +} + +div.highlight .-Color[class*=-C154] { + color: #AFFF00 +} + +div.highlight .-Color[class*=-BGC154] { + background-color: #AFFF00 +} + +div.highlight .-Color[class*=-C155] { + color: #AFFF5F +} + +div.highlight .-Color[class*=-BGC155] { + background-color: #AFFF5F +} + +div.highlight .-Color[class*=-C156] { + color: #AFFF87 +} + +div.highlight .-Color[class*=-BGC156] { + background-color: #AFFF87 +} + +div.highlight .-Color[class*=-C157] { + color: #AFFFAF +} + +div.highlight .-Color[class*=-BGC157] { + background-color: #AFFFAF +} + +div.highlight .-Color[class*=-C158] { + color: #AFFFD7 +} + +div.highlight .-Color[class*=-BGC158] { + background-color: #AFFFD7 +} + +div.highlight .-Color[class*=-C159] { + color: #AFFFFF +} + +div.highlight .-Color[class*=-BGC159] { + background-color: #AFFFFF +} + +div.highlight .-Color[class*=-C160] { + color: #D70000 +} + +div.highlight .-Color[class*=-BGC160] { + background-color: #D70000 +} + +div.highlight .-Color[class*=-C161] { + color: #D7005F +} + +div.highlight .-Color[class*=-BGC161] { + background-color: #D7005F +} + +div.highlight .-Color[class*=-C162] { + color: #D70087 +} + +div.highlight .-Color[class*=-BGC162] { + background-color: #D70087 +} + +div.highlight .-Color[class*=-C163] { + color: #D700AF +} + +div.highlight .-Color[class*=-BGC163] { + background-color: #D700AF +} + +div.highlight .-Color[class*=-C164] { + color: #D700D7 +} + +div.highlight .-Color[class*=-BGC164] { + background-color: #D700D7 +} + +div.highlight .-Color[class*=-C165] { + color: #D700FF +} + +div.highlight .-Color[class*=-BGC165] { + background-color: #D700FF +} + +div.highlight .-Color[class*=-C166] { + color: #D75F00 +} + +div.highlight .-Color[class*=-BGC166] { + background-color: #D75F00 +} + +div.highlight .-Color[class*=-C167] { + color: #D75F5F +} + +div.highlight .-Color[class*=-BGC167] { + background-color: #D75F5F +} + +div.highlight .-Color[class*=-C168] { + color: #D75F87 +} + +div.highlight .-Color[class*=-BGC168] { + background-color: #D75F87 +} + +div.highlight .-Color[class*=-C169] { + color: #D75FAF +} + +div.highlight .-Color[class*=-BGC169] { + background-color: #D75FAF +} + +div.highlight .-Color[class*=-C170] { + color: #D75FD7 +} + +div.highlight .-Color[class*=-BGC170] { + background-color: #D75FD7 +} + +div.highlight .-Color[class*=-C171] { + color: #D75FFF +} + +div.highlight .-Color[class*=-BGC171] { + background-color: #D75FFF +} + +div.highlight .-Color[class*=-C172] { + color: #D78700 +} + +div.highlight .-Color[class*=-BGC172] { + background-color: #D78700 +} + +div.highlight .-Color[class*=-C173] { + color: #D7875F +} + +div.highlight .-Color[class*=-BGC173] { + background-color: #D7875F +} + +div.highlight .-Color[class*=-C174] { + color: #D78787 +} + +div.highlight .-Color[class*=-BGC174] { + background-color: #D78787 +} + +div.highlight .-Color[class*=-C175] { + color: #D787AF +} + +div.highlight .-Color[class*=-BGC175] { + background-color: #D787AF +} + +div.highlight .-Color[class*=-C176] { + color: #D787D7 +} + +div.highlight .-Color[class*=-BGC176] { + background-color: #D787D7 +} + +div.highlight .-Color[class*=-C177] { + color: #D787FF +} + +div.highlight .-Color[class*=-BGC177] { + background-color: #D787FF +} + +div.highlight .-Color[class*=-C178] { + color: #D7AF00 +} + +div.highlight .-Color[class*=-BGC178] { + background-color: #D7AF00 +} + +div.highlight .-Color[class*=-C179] { + color: #D7AF5F +} + +div.highlight .-Color[class*=-BGC179] { + background-color: #D7AF5F +} + +div.highlight .-Color[class*=-C180] { + color: #D7AF87 +} + +div.highlight .-Color[class*=-BGC180] { + background-color: #D7AF87 +} + +div.highlight .-Color[class*=-C181] { + color: #D7AFAF +} + +div.highlight .-Color[class*=-BGC181] { + background-color: #D7AFAF +} + +div.highlight .-Color[class*=-C182] { + color: #D7AFD7 +} + +div.highlight .-Color[class*=-BGC182] { + background-color: #D7AFD7 +} + +div.highlight .-Color[class*=-C183] { + color: #D7AFFF +} + +div.highlight .-Color[class*=-BGC183] { + background-color: #D7AFFF +} + +div.highlight .-Color[class*=-C184] { + color: #D7D700 +} + +div.highlight .-Color[class*=-BGC184] { + background-color: #D7D700 +} + +div.highlight .-Color[class*=-C185] { + color: #D7D75F +} + +div.highlight .-Color[class*=-BGC185] { + background-color: #D7D75F +} + +div.highlight .-Color[class*=-C186] { + color: #D7D787 +} + +div.highlight .-Color[class*=-BGC186] { + background-color: #D7D787 +} + +div.highlight .-Color[class*=-C187] { + color: #D7D7AF +} + +div.highlight .-Color[class*=-BGC187] { + background-color: #D7D7AF +} + +div.highlight .-Color[class*=-C188] { + color: #D7D7D7 +} + +div.highlight .-Color[class*=-BGC188] { + background-color: #D7D7D7 +} + +div.highlight .-Color[class*=-C189] { + color: #D7D7FF +} + +div.highlight .-Color[class*=-BGC189] { + background-color: #D7D7FF +} + +div.highlight .-Color[class*=-C190] { + color: #D7FF00 +} + +div.highlight .-Color[class*=-BGC190] { + background-color: #D7FF00 +} + +div.highlight .-Color[class*=-C191] { + color: #D7FF5F +} + +div.highlight .-Color[class*=-BGC191] { + background-color: #D7FF5F +} + +div.highlight .-Color[class*=-C192] { + color: #D7FF87 +} + +div.highlight .-Color[class*=-BGC192] { + background-color: #D7FF87 +} + +div.highlight .-Color[class*=-C193] { + color: #D7FFAF +} + +div.highlight .-Color[class*=-BGC193] { + background-color: #D7FFAF +} + +div.highlight .-Color[class*=-C194] { + color: #D7FFD7 +} + +div.highlight .-Color[class*=-BGC194] { + background-color: #D7FFD7 +} + +div.highlight .-Color[class*=-C195] { + color: #D7FFFF +} + +div.highlight .-Color[class*=-BGC195] { + background-color: #D7FFFF +} + +div.highlight .-Color[class*=-C196] { + color: #FF0000 +} + +div.highlight .-Color[class*=-BGC196] { + background-color: #FF0000 +} + +div.highlight .-Color[class*=-C197] { + color: #FF005F +} + +div.highlight .-Color[class*=-BGC197] { + background-color: #FF005F +} + +div.highlight .-Color[class*=-C198] { + color: #FF0087 +} + +div.highlight .-Color[class*=-BGC198] { + background-color: #FF0087 +} + +div.highlight .-Color[class*=-C199] { + color: #FF00AF +} + +div.highlight .-Color[class*=-BGC199] { + background-color: #FF00AF +} + +div.highlight .-Color[class*=-C200] { + color: #FF00D7 +} + +div.highlight .-Color[class*=-BGC200] { + background-color: #FF00D7 +} + +div.highlight .-Color[class*=-C201] { + color: #FF00FF +} + +div.highlight .-Color[class*=-BGC201] { + background-color: #FF00FF +} + +div.highlight .-Color[class*=-C202] { + color: #FF5F00 +} + +div.highlight .-Color[class*=-BGC202] { + background-color: #FF5F00 +} + +div.highlight .-Color[class*=-C203] { + color: #FF5F5F +} + +div.highlight .-Color[class*=-BGC203] { + background-color: #FF5F5F +} + +div.highlight .-Color[class*=-C204] { + color: #FF5F87 +} + +div.highlight .-Color[class*=-BGC204] { + background-color: #FF5F87 +} + +div.highlight .-Color[class*=-C205] { + color: #FF5FAF +} + +div.highlight .-Color[class*=-BGC205] { + background-color: #FF5FAF +} + +div.highlight .-Color[class*=-C206] { + color: #FF5FD7 +} + +div.highlight .-Color[class*=-BGC206] { + background-color: #FF5FD7 +} + +div.highlight .-Color[class*=-C207] { + color: #FF5FFF +} + +div.highlight .-Color[class*=-BGC207] { + background-color: #FF5FFF +} + +div.highlight .-Color[class*=-C208] { + color: #FF8700 +} + +div.highlight .-Color[class*=-BGC208] { + background-color: #FF8700 +} + +div.highlight .-Color[class*=-C209] { + color: #FF875F +} + +div.highlight .-Color[class*=-BGC209] { + background-color: #FF875F +} + +div.highlight .-Color[class*=-C210] { + color: #FF8787 +} + +div.highlight .-Color[class*=-BGC210] { + background-color: #FF8787 +} + +div.highlight .-Color[class*=-C211] { + color: #FF87AF +} + +div.highlight .-Color[class*=-BGC211] { + background-color: #FF87AF +} + +div.highlight .-Color[class*=-C212] { + color: #FF87D7 +} + +div.highlight .-Color[class*=-BGC212] { + background-color: #FF87D7 +} + +div.highlight .-Color[class*=-C213] { + color: #FF87FF +} + +div.highlight .-Color[class*=-BGC213] { + background-color: #FF87FF +} + +div.highlight .-Color[class*=-C214] { + color: #FFAF00 +} + +div.highlight .-Color[class*=-BGC214] { + background-color: #FFAF00 +} + +div.highlight .-Color[class*=-C215] { + color: #FFAF5F +} + +div.highlight .-Color[class*=-BGC215] { + background-color: #FFAF5F +} + +div.highlight .-Color[class*=-C216] { + color: #FFAF87 +} + +div.highlight .-Color[class*=-BGC216] { + background-color: #FFAF87 +} + +div.highlight .-Color[class*=-C217] { + color: #FFAFAF +} + +div.highlight .-Color[class*=-BGC217] { + background-color: #FFAFAF +} + +div.highlight .-Color[class*=-C218] { + color: #FFAFD7 +} + +div.highlight .-Color[class*=-BGC218] { + background-color: #FFAFD7 +} + +div.highlight .-Color[class*=-C219] { + color: #FFAFFF +} + +div.highlight .-Color[class*=-BGC219] { + background-color: #FFAFFF +} + +div.highlight .-Color[class*=-C220] { + color: #FFD700 +} + +div.highlight .-Color[class*=-BGC220] { + background-color: #FFD700 +} + +div.highlight .-Color[class*=-C221] { + color: #FFD75F +} + +div.highlight .-Color[class*=-BGC221] { + background-color: #FFD75F +} + +div.highlight .-Color[class*=-C222] { + color: #FFD787 +} + +div.highlight .-Color[class*=-BGC222] { + background-color: #FFD787 +} + +div.highlight .-Color[class*=-C223] { + color: #FFD7AF +} + +div.highlight .-Color[class*=-BGC223] { + background-color: #FFD7AF +} + +div.highlight .-Color[class*=-C224] { + color: #FFD7D7 +} + +div.highlight .-Color[class*=-BGC224] { + background-color: #FFD7D7 +} + +div.highlight .-Color[class*=-C225] { + color: #FFD7FF +} + +div.highlight .-Color[class*=-BGC225] { + background-color: #FFD7FF +} + +div.highlight .-Color[class*=-C226] { + color: #FFFF00 +} + +div.highlight .-Color[class*=-BGC226] { + background-color: #FFFF00 +} + +div.highlight .-Color[class*=-C227] { + color: #FFFF5F +} + +div.highlight .-Color[class*=-BGC227] { + background-color: #FFFF5F +} + +div.highlight .-Color[class*=-C228] { + color: #FFFF87 +} + +div.highlight .-Color[class*=-BGC228] { + background-color: #FFFF87 +} + +div.highlight .-Color[class*=-C229] { + color: #FFFFAF +} + +div.highlight .-Color[class*=-BGC229] { + background-color: #FFFFAF +} + +div.highlight .-Color[class*=-C230] { + color: #FFFFD7 +} + +div.highlight .-Color[class*=-BGC230] { + background-color: #FFFFD7 +} + +div.highlight .-Color[class*=-C231] { + color: #FFFFFF +} + +div.highlight .-Color[class*=-BGC231] { + background-color: #FFFFFF +} + +div.highlight .-Color[class*=-C232] { + color: #080808 +} + +div.highlight .-Color[class*=-BGC232] { + background-color: #080808 +} + +div.highlight .-Color[class*=-C233] { + color: #121212 +} + +div.highlight .-Color[class*=-BGC233] { + background-color: #121212 +} + +div.highlight .-Color[class*=-C234] { + color: #1C1C1C +} + +div.highlight .-Color[class*=-BGC234] { + background-color: #1C1C1C +} + +div.highlight .-Color[class*=-C235] { + color: #262626 +} + +div.highlight .-Color[class*=-BGC235] { + background-color: #262626 +} + +div.highlight .-Color[class*=-C236] { + color: #303030 +} + +div.highlight .-Color[class*=-BGC236] { + background-color: #303030 +} + +div.highlight .-Color[class*=-C237] { + color: #3A3A3A +} + +div.highlight .-Color[class*=-BGC237] { + background-color: #3A3A3A +} + +div.highlight .-Color[class*=-C238] { + color: #444444 +} + +div.highlight .-Color[class*=-BGC238] { + background-color: #444444 +} + +div.highlight .-Color[class*=-C239] { + color: #4E4E4E +} + +div.highlight .-Color[class*=-BGC239] { + background-color: #4E4E4E +} + +div.highlight .-Color[class*=-C240] { + color: #585858 +} + +div.highlight .-Color[class*=-BGC240] { + background-color: #585858 +} + +div.highlight .-Color[class*=-C241] { + color: #626262 +} + +div.highlight .-Color[class*=-BGC241] { + background-color: #626262 +} + +div.highlight .-Color[class*=-C242] { + color: #6C6C6C +} + +div.highlight .-Color[class*=-BGC242] { + background-color: #6C6C6C +} + +div.highlight .-Color[class*=-C243] { + color: #767676 +} + +div.highlight .-Color[class*=-BGC243] { + background-color: #767676 +} + +div.highlight .-Color[class*=-C244] { + color: #808080 +} + +div.highlight .-Color[class*=-BGC244] { + background-color: #808080 +} + +div.highlight .-Color[class*=-C245] { + color: #8A8A8A +} + +div.highlight .-Color[class*=-BGC245] { + background-color: #8A8A8A +} + +div.highlight .-Color[class*=-C246] { + color: #949494 +} + +div.highlight .-Color[class*=-BGC246] { + background-color: #949494 +} + +div.highlight .-Color[class*=-C247] { + color: #9E9E9E +} + +div.highlight .-Color[class*=-BGC247] { + background-color: #9E9E9E +} + +div.highlight .-Color[class*=-C248] { + color: #A8A8A8 +} + +div.highlight .-Color[class*=-BGC248] { + background-color: #A8A8A8 +} + +div.highlight .-Color[class*=-C249] { + color: #B2B2B2 +} + +div.highlight .-Color[class*=-BGC249] { + background-color: #B2B2B2 +} + +div.highlight .-Color[class*=-C250] { + color: #BCBCBC +} + +div.highlight .-Color[class*=-BGC250] { + background-color: #BCBCBC +} + +div.highlight .-Color[class*=-C251] { + color: #C6C6C6 +} + +div.highlight .-Color[class*=-BGC251] { + background-color: #C6C6C6 +} + +div.highlight .-Color[class*=-C252] { + color: #D0D0D0 +} + +div.highlight .-Color[class*=-BGC252] { + background-color: #D0D0D0 +} + +div.highlight .-Color[class*=-C253] { + color: #DADADA +} + +div.highlight .-Color[class*=-BGC253] { + background-color: #DADADA +} + +div.highlight .-Color[class*=-C254] { + color: #E4E4E4 +} + +div.highlight .-Color[class*=-BGC254] { + background-color: #E4E4E4 +} + +div.highlight .-Color[class*=-C255] { + color: #EEEEEE +} + +div.highlight .-Color[class*=-BGC255] { + background-color: #EEEEEE +} diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 00000000..997797f2 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,152 @@ +html[data-theme="light"] .highlight pre { line-height: 125%; } +html[data-theme="light"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight .hll { background-color: #7971292e } +html[data-theme="light"] .highlight { background: #fefefe; color: #545454 } +html[data-theme="light"] .highlight .c { color: #797129 } /* Comment */ +html[data-theme="light"] .highlight .err { color: #d91e18 } /* Error */ +html[data-theme="light"] .highlight .k { color: #7928a1 } /* Keyword */ +html[data-theme="light"] .highlight .l { color: #797129 } /* Literal */ +html[data-theme="light"] .highlight .n { color: #545454 } /* Name */ +html[data-theme="light"] .highlight .o { color: #008000 } /* Operator */ +html[data-theme="light"] .highlight .p { color: #545454 } /* Punctuation */ +html[data-theme="light"] .highlight .ch { color: #797129 } /* Comment.Hashbang */ +html[data-theme="light"] .highlight .cm { color: #797129 } /* Comment.Multiline */ +html[data-theme="light"] .highlight .cp { color: #797129 } /* Comment.Preproc */ +html[data-theme="light"] .highlight .cpf { color: #797129 } /* Comment.PreprocFile */ +html[data-theme="light"] .highlight .c1 { color: #797129 } /* Comment.Single */ +html[data-theme="light"] .highlight .cs { color: #797129 } /* Comment.Special */ +html[data-theme="light"] .highlight .gd { color: #007faa } /* Generic.Deleted */ +html[data-theme="light"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="light"] .highlight .gh { color: #007faa } /* Generic.Heading */ +html[data-theme="light"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="light"] .highlight .gu { color: #007faa } /* Generic.Subheading */ +html[data-theme="light"] .highlight .kc { color: #7928a1 } /* Keyword.Constant */ +html[data-theme="light"] .highlight .kd { color: #7928a1 } /* Keyword.Declaration */ +html[data-theme="light"] .highlight .kn { color: #7928a1 } /* Keyword.Namespace */ +html[data-theme="light"] .highlight .kp { color: #7928a1 } /* Keyword.Pseudo */ +html[data-theme="light"] .highlight .kr { color: #7928a1 } /* Keyword.Reserved */ +html[data-theme="light"] .highlight .kt { color: #797129 } /* Keyword.Type */ +html[data-theme="light"] .highlight .ld { color: #797129 } /* Literal.Date */ +html[data-theme="light"] .highlight .m { color: #797129 } /* Literal.Number */ +html[data-theme="light"] .highlight .s { color: #008000 } /* Literal.String */ +html[data-theme="light"] .highlight .na { color: #797129 } /* Name.Attribute */ +html[data-theme="light"] .highlight .nb { color: #797129 } /* Name.Builtin */ +html[data-theme="light"] .highlight .nc { color: #007faa } /* Name.Class */ +html[data-theme="light"] .highlight .no { color: #007faa } /* Name.Constant */ +html[data-theme="light"] .highlight .nd { color: #797129 } /* Name.Decorator */ +html[data-theme="light"] .highlight .ni { color: #008000 } /* Name.Entity */ +html[data-theme="light"] .highlight .ne { color: #7928a1 } /* Name.Exception */ +html[data-theme="light"] .highlight .nf { color: #007faa } /* Name.Function */ +html[data-theme="light"] .highlight .nl { color: #797129 } /* Name.Label */ +html[data-theme="light"] .highlight .nn { color: #545454 } /* Name.Namespace */ +html[data-theme="light"] .highlight .nx { color: #545454 } /* Name.Other */ +html[data-theme="light"] .highlight .py { color: #007faa } /* Name.Property */ +html[data-theme="light"] .highlight .nt { color: #007faa } /* Name.Tag */ +html[data-theme="light"] .highlight .nv { color: #d91e18 } /* Name.Variable */ +html[data-theme="light"] .highlight .ow { color: #7928a1 } /* Operator.Word */ +html[data-theme="light"] .highlight .pm { color: #545454 } /* Punctuation.Marker */ +html[data-theme="light"] .highlight .w { color: #545454 } /* Text.Whitespace */ +html[data-theme="light"] .highlight .mb { color: #797129 } /* Literal.Number.Bin */ +html[data-theme="light"] .highlight .mf { color: #797129 } /* Literal.Number.Float */ +html[data-theme="light"] .highlight .mh { color: #797129 } /* Literal.Number.Hex */ +html[data-theme="light"] .highlight .mi { color: #797129 } /* Literal.Number.Integer */ +html[data-theme="light"] .highlight .mo { color: #797129 } /* Literal.Number.Oct */ +html[data-theme="light"] .highlight .sa { color: #008000 } /* Literal.String.Affix */ +html[data-theme="light"] .highlight .sb { color: #008000 } /* Literal.String.Backtick */ +html[data-theme="light"] .highlight .sc { color: #008000 } /* Literal.String.Char */ +html[data-theme="light"] .highlight .dl { color: #008000 } /* Literal.String.Delimiter */ +html[data-theme="light"] .highlight .sd { color: #008000 } /* Literal.String.Doc */ +html[data-theme="light"] .highlight .s2 { color: #008000 } /* Literal.String.Double */ +html[data-theme="light"] .highlight .se { color: #008000 } /* Literal.String.Escape */ +html[data-theme="light"] .highlight .sh { color: #008000 } /* Literal.String.Heredoc */ +html[data-theme="light"] .highlight .si { color: #008000 } /* Literal.String.Interpol */ +html[data-theme="light"] .highlight .sx { color: #008000 } /* Literal.String.Other */ +html[data-theme="light"] .highlight .sr { color: #d91e18 } /* Literal.String.Regex */ +html[data-theme="light"] .highlight .s1 { color: #008000 } /* Literal.String.Single */ +html[data-theme="light"] .highlight .ss { color: #007faa } /* Literal.String.Symbol */ +html[data-theme="light"] .highlight .bp { color: #797129 } /* Name.Builtin.Pseudo */ +html[data-theme="light"] .highlight .fm { color: #007faa } /* Name.Function.Magic */ +html[data-theme="light"] .highlight .vc { color: #d91e18 } /* Name.Variable.Class */ +html[data-theme="light"] .highlight .vg { color: #d91e18 } /* Name.Variable.Global */ +html[data-theme="light"] .highlight .vi { color: #d91e18 } /* Name.Variable.Instance */ +html[data-theme="light"] .highlight .vm { color: #797129 } /* Name.Variable.Magic */ +html[data-theme="light"] .highlight .il { color: #797129 } /* Literal.Number.Integer.Long */ +html[data-theme="dark"] .highlight pre { line-height: 125%; } +html[data-theme="dark"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight .hll { background-color: #ffd9002e } +html[data-theme="dark"] .highlight { background: #2b2b2b; color: #f8f8f2 } +html[data-theme="dark"] .highlight .c { color: #ffd900 } /* Comment */ +html[data-theme="dark"] .highlight .err { color: #ffa07a } /* Error */ +html[data-theme="dark"] .highlight .k { color: #dcc6e0 } /* Keyword */ +html[data-theme="dark"] .highlight .l { color: #ffd900 } /* Literal */ +html[data-theme="dark"] .highlight .n { color: #f8f8f2 } /* Name */ +html[data-theme="dark"] .highlight .o { color: #abe338 } /* Operator */ +html[data-theme="dark"] .highlight .p { color: #f8f8f2 } /* Punctuation */ +html[data-theme="dark"] .highlight .ch { color: #ffd900 } /* Comment.Hashbang */ +html[data-theme="dark"] .highlight .cm { color: #ffd900 } /* Comment.Multiline */ +html[data-theme="dark"] .highlight .cp { color: #ffd900 } /* Comment.Preproc */ +html[data-theme="dark"] .highlight .cpf { color: #ffd900 } /* Comment.PreprocFile */ +html[data-theme="dark"] .highlight .c1 { color: #ffd900 } /* Comment.Single */ +html[data-theme="dark"] .highlight .cs { color: #ffd900 } /* Comment.Special */ +html[data-theme="dark"] .highlight .gd { color: #00e0e0 } /* Generic.Deleted */ +html[data-theme="dark"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="dark"] .highlight .gh { color: #00e0e0 } /* Generic.Heading */ +html[data-theme="dark"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="dark"] .highlight .gu { color: #00e0e0 } /* Generic.Subheading */ +html[data-theme="dark"] .highlight .kc { color: #dcc6e0 } /* Keyword.Constant */ +html[data-theme="dark"] .highlight .kd { color: #dcc6e0 } /* Keyword.Declaration */ +html[data-theme="dark"] .highlight .kn { color: #dcc6e0 } /* Keyword.Namespace */ +html[data-theme="dark"] .highlight .kp { color: #dcc6e0 } /* Keyword.Pseudo */ +html[data-theme="dark"] .highlight .kr { color: #dcc6e0 } /* Keyword.Reserved */ +html[data-theme="dark"] .highlight .kt { color: #ffd900 } /* Keyword.Type */ +html[data-theme="dark"] .highlight .ld { color: #ffd900 } /* Literal.Date */ +html[data-theme="dark"] .highlight .m { color: #ffd900 } /* Literal.Number */ +html[data-theme="dark"] .highlight .s { color: #abe338 } /* Literal.String */ +html[data-theme="dark"] .highlight .na { color: #ffd900 } /* Name.Attribute */ +html[data-theme="dark"] .highlight .nb { color: #ffd900 } /* Name.Builtin */ +html[data-theme="dark"] .highlight .nc { color: #00e0e0 } /* Name.Class */ +html[data-theme="dark"] .highlight .no { color: #00e0e0 } /* Name.Constant */ +html[data-theme="dark"] .highlight .nd { color: #ffd900 } /* Name.Decorator */ +html[data-theme="dark"] .highlight .ni { color: #abe338 } /* Name.Entity */ +html[data-theme="dark"] .highlight .ne { color: #dcc6e0 } /* Name.Exception */ +html[data-theme="dark"] .highlight .nf { color: #00e0e0 } /* Name.Function */ +html[data-theme="dark"] .highlight .nl { color: #ffd900 } /* Name.Label */ +html[data-theme="dark"] .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ +html[data-theme="dark"] .highlight .nx { color: #f8f8f2 } /* Name.Other */ +html[data-theme="dark"] .highlight .py { color: #00e0e0 } /* Name.Property */ +html[data-theme="dark"] .highlight .nt { color: #00e0e0 } /* Name.Tag */ +html[data-theme="dark"] .highlight .nv { color: #ffa07a } /* Name.Variable */ +html[data-theme="dark"] .highlight .ow { color: #dcc6e0 } /* Operator.Word */ +html[data-theme="dark"] .highlight .pm { color: #f8f8f2 } /* Punctuation.Marker */ +html[data-theme="dark"] .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ +html[data-theme="dark"] .highlight .mb { color: #ffd900 } /* Literal.Number.Bin */ +html[data-theme="dark"] .highlight .mf { color: #ffd900 } /* Literal.Number.Float */ +html[data-theme="dark"] .highlight .mh { color: #ffd900 } /* Literal.Number.Hex */ +html[data-theme="dark"] .highlight .mi { color: #ffd900 } /* Literal.Number.Integer */ +html[data-theme="dark"] .highlight .mo { color: #ffd900 } /* Literal.Number.Oct */ +html[data-theme="dark"] .highlight .sa { color: #abe338 } /* Literal.String.Affix */ +html[data-theme="dark"] .highlight .sb { color: #abe338 } /* Literal.String.Backtick */ +html[data-theme="dark"] .highlight .sc { color: #abe338 } /* Literal.String.Char */ +html[data-theme="dark"] .highlight .dl { color: #abe338 } /* Literal.String.Delimiter */ +html[data-theme="dark"] .highlight .sd { color: #abe338 } /* Literal.String.Doc */ +html[data-theme="dark"] .highlight .s2 { color: #abe338 } /* Literal.String.Double */ +html[data-theme="dark"] .highlight .se { color: #abe338 } /* Literal.String.Escape */ +html[data-theme="dark"] .highlight .sh { color: #abe338 } /* Literal.String.Heredoc */ +html[data-theme="dark"] .highlight .si { color: #abe338 } /* Literal.String.Interpol */ +html[data-theme="dark"] .highlight .sx { color: #abe338 } /* Literal.String.Other */ +html[data-theme="dark"] .highlight .sr { color: #ffa07a } /* Literal.String.Regex */ +html[data-theme="dark"] .highlight .s1 { color: #abe338 } /* Literal.String.Single */ +html[data-theme="dark"] .highlight .ss { color: #00e0e0 } /* Literal.String.Symbol */ +html[data-theme="dark"] .highlight .bp { color: #ffd900 } /* Name.Builtin.Pseudo */ +html[data-theme="dark"] .highlight .fm { color: #00e0e0 } /* Name.Function.Magic */ +html[data-theme="dark"] .highlight .vc { color: #ffa07a } /* Name.Variable.Class */ +html[data-theme="dark"] .highlight .vg { color: #ffa07a } /* Name.Variable.Global */ +html[data-theme="dark"] .highlight .vi { color: #ffa07a } /* Name.Variable.Instance */ +html[data-theme="dark"] .highlight .vm { color: #ffd900 } /* Name.Variable.Magic */ +html[data-theme="dark"] .highlight .il { color: #ffd900 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/scripts/bootstrap.js b/_static/scripts/bootstrap.js new file mode 100644 index 00000000..4e209b0e --- /dev/null +++ b/_static/scripts/bootstrap.js @@ -0,0 +1,3 @@ +/*! For license information please see bootstrap.js.LICENSE.txt */ +(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{afterMain:()=>E,afterRead:()=>v,afterWrite:()=>C,applyStyles:()=>$,arrow:()=>J,auto:()=>a,basePlacements:()=>l,beforeMain:()=>y,beforeRead:()=>_,beforeWrite:()=>A,bottom:()=>s,clippingParents:()=>d,computeStyles:()=>it,createPopper:()=>Dt,createPopperBase:()=>St,createPopperLite:()=>$t,detectOverflow:()=>_t,end:()=>h,eventListeners:()=>st,flip:()=>bt,hide:()=>wt,left:()=>r,main:()=>w,modifierPhases:()=>O,offset:()=>Et,placements:()=>g,popper:()=>f,popperGenerator:()=>Lt,popperOffsets:()=>At,preventOverflow:()=>Tt,read:()=>b,reference:()=>p,right:()=>o,start:()=>c,top:()=>n,variationPlacements:()=>m,viewport:()=>u,write:()=>T});var i={};t.r(i),t.d(i,{Alert:()=>Oe,Button:()=>ke,Carousel:()=>ri,Collapse:()=>yi,Dropdown:()=>Vi,Modal:()=>xn,Offcanvas:()=>Vn,Popover:()=>fs,ScrollSpy:()=>Ts,Tab:()=>Ks,Toast:()=>lo,Tooltip:()=>hs});var n="top",s="bottom",o="right",r="left",a="auto",l=[n,s,o,r],c="start",h="end",d="clippingParents",u="viewport",f="popper",p="reference",m=l.reduce((function(t,e){return t.concat([e+"-"+c,e+"-"+h])}),[]),g=[].concat(l,[a]).reduce((function(t,e){return t.concat([e,e+"-"+c,e+"-"+h])}),[]),_="beforeRead",b="read",v="afterRead",y="beforeMain",w="main",E="afterMain",A="beforeWrite",T="write",C="afterWrite",O=[_,b,v,y,w,E,A,T,C];function x(t){return t?(t.nodeName||"").toLowerCase():null}function k(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function L(t){return t instanceof k(t).Element||t instanceof Element}function S(t){return t instanceof k(t).HTMLElement||t instanceof HTMLElement}function D(t){return"undefined"!=typeof ShadowRoot&&(t instanceof k(t).ShadowRoot||t instanceof ShadowRoot)}const $={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];S(s)&&x(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});S(n)&&x(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function I(t){return t.split("-")[0]}var N=Math.max,P=Math.min,M=Math.round;function j(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function F(){return!/^((?!chrome|android).)*safari/i.test(j())}function H(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&S(t)&&(s=t.offsetWidth>0&&M(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&M(n.height)/t.offsetHeight||1);var r=(L(t)?k(t):window).visualViewport,a=!F()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function B(t){var e=H(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function W(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&D(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function z(t){return k(t).getComputedStyle(t)}function R(t){return["table","td","th"].indexOf(x(t))>=0}function q(t){return((L(t)?t.ownerDocument:t.document)||window.document).documentElement}function V(t){return"html"===x(t)?t:t.assignedSlot||t.parentNode||(D(t)?t.host:null)||q(t)}function Y(t){return S(t)&&"fixed"!==z(t).position?t.offsetParent:null}function K(t){for(var e=k(t),i=Y(t);i&&R(i)&&"static"===z(i).position;)i=Y(i);return i&&("html"===x(i)||"body"===x(i)&&"static"===z(i).position)?e:i||function(t){var e=/firefox/i.test(j());if(/Trident/i.test(j())&&S(t)&&"fixed"===z(t).position)return null;var i=V(t);for(D(i)&&(i=i.host);S(i)&&["html","body"].indexOf(x(i))<0;){var n=z(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Q(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return N(t,P(e,i))}function U(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function G(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const J={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,a=t.name,c=t.options,h=i.elements.arrow,d=i.modifiersData.popperOffsets,u=I(i.placement),f=Q(u),p=[r,o].indexOf(u)>=0?"height":"width";if(h&&d){var m=function(t,e){return U("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:G(t,l))}(c.padding,i),g=B(h),_="y"===f?n:r,b="y"===f?s:o,v=i.rects.reference[p]+i.rects.reference[f]-d[f]-i.rects.popper[p],y=d[f]-i.rects.reference[f],w=K(h),E=w?"y"===f?w.clientHeight||0:w.clientWidth||0:0,A=v/2-y/2,T=m[_],C=E-g[p]-m[b],O=E/2-g[p]/2+A,x=X(T,O,C),k=f;i.modifiersData[a]=((e={})[k]=x,e.centerOffset=x-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&W(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Z(t){return t.split("-")[1]}var tt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function et(t){var e,i=t.popper,a=t.popperRect,l=t.placement,c=t.variation,d=t.offsets,u=t.position,f=t.gpuAcceleration,p=t.adaptive,m=t.roundOffsets,g=t.isFixed,_=d.x,b=void 0===_?0:_,v=d.y,y=void 0===v?0:v,w="function"==typeof m?m({x:b,y}):{x:b,y};b=w.x,y=w.y;var E=d.hasOwnProperty("x"),A=d.hasOwnProperty("y"),T=r,C=n,O=window;if(p){var x=K(i),L="clientHeight",S="clientWidth";x===k(i)&&"static"!==z(x=q(i)).position&&"absolute"===u&&(L="scrollHeight",S="scrollWidth"),(l===n||(l===r||l===o)&&c===h)&&(C=s,y-=(g&&x===O&&O.visualViewport?O.visualViewport.height:x[L])-a.height,y*=f?1:-1),l!==r&&(l!==n&&l!==s||c!==h)||(T=o,b-=(g&&x===O&&O.visualViewport?O.visualViewport.width:x[S])-a.width,b*=f?1:-1)}var D,$=Object.assign({position:u},p&&tt),I=!0===m?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:M(i*s)/s||0,y:M(n*s)/s||0}}({x:b,y},k(i)):{x:b,y};return b=I.x,y=I.y,f?Object.assign({},$,((D={})[C]=A?"0":"",D[T]=E?"0":"",D.transform=(O.devicePixelRatio||1)<=1?"translate("+b+"px, "+y+"px)":"translate3d("+b+"px, "+y+"px, 0)",D)):Object.assign({},$,((e={})[C]=A?y+"px":"",e[T]=E?b+"px":"",e.transform="",e))}const it={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:I(e.placement),variation:Z(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,et(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,et(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var nt={passive:!0};const st={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=k(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,nt)})),a&&l.addEventListener("resize",i.update,nt),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,nt)})),a&&l.removeEventListener("resize",i.update,nt)}},data:{}};var ot={left:"right",right:"left",bottom:"top",top:"bottom"};function rt(t){return t.replace(/left|right|bottom|top/g,(function(t){return ot[t]}))}var at={start:"end",end:"start"};function lt(t){return t.replace(/start|end/g,(function(t){return at[t]}))}function ct(t){var e=k(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ht(t){return H(q(t)).left+ct(t).scrollLeft}function dt(t){var e=z(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ut(t){return["html","body","#document"].indexOf(x(t))>=0?t.ownerDocument.body:S(t)&&dt(t)?t:ut(V(t))}function ft(t,e){var i;void 0===e&&(e=[]);var n=ut(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=k(n),r=s?[o].concat(o.visualViewport||[],dt(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(ft(V(r)))}function pt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function mt(t,e,i){return e===u?pt(function(t,e){var i=k(t),n=q(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=F();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+ht(t),y:l}}(t,i)):L(e)?function(t,e){var i=H(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):pt(function(t){var e,i=q(t),n=ct(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=N(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=N(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ht(t),l=-n.scrollTop;return"rtl"===z(s||i).direction&&(a+=N(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(q(t)))}function gt(t){var e,i=t.reference,a=t.element,l=t.placement,d=l?I(l):null,u=l?Z(l):null,f=i.x+i.width/2-a.width/2,p=i.y+i.height/2-a.height/2;switch(d){case n:e={x:f,y:i.y-a.height};break;case s:e={x:f,y:i.y+i.height};break;case o:e={x:i.x+i.width,y:p};break;case r:e={x:i.x-a.width,y:p};break;default:e={x:i.x,y:i.y}}var m=d?Q(d):null;if(null!=m){var g="y"===m?"height":"width";switch(u){case c:e[m]=e[m]-(i[g]/2-a[g]/2);break;case h:e[m]=e[m]+(i[g]/2-a[g]/2)}}return e}function _t(t,e){void 0===e&&(e={});var i=e,r=i.placement,a=void 0===r?t.placement:r,c=i.strategy,h=void 0===c?t.strategy:c,m=i.boundary,g=void 0===m?d:m,_=i.rootBoundary,b=void 0===_?u:_,v=i.elementContext,y=void 0===v?f:v,w=i.altBoundary,E=void 0!==w&&w,A=i.padding,T=void 0===A?0:A,C=U("number"!=typeof T?T:G(T,l)),O=y===f?p:f,k=t.rects.popper,D=t.elements[E?O:y],$=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=ft(V(t)),i=["absolute","fixed"].indexOf(z(t).position)>=0&&S(t)?K(t):t;return L(i)?e.filter((function(t){return L(t)&&W(t,i)&&"body"!==x(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=mt(t,i,n);return e.top=N(s.top,e.top),e.right=P(s.right,e.right),e.bottom=P(s.bottom,e.bottom),e.left=N(s.left,e.left),e}),mt(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(L(D)?D:D.contextElement||q(t.elements.popper),g,b,h),I=H(t.elements.reference),M=gt({reference:I,element:k,strategy:"absolute",placement:a}),j=pt(Object.assign({},k,M)),F=y===f?j:I,B={top:$.top-F.top+C.top,bottom:F.bottom-$.bottom+C.bottom,left:$.left-F.left+C.left,right:F.right-$.right+C.right},R=t.modifiersData.offset;if(y===f&&R){var Y=R[a];Object.keys(B).forEach((function(t){var e=[o,s].indexOf(t)>=0?1:-1,i=[n,s].indexOf(t)>=0?"y":"x";B[t]+=Y[i]*e}))}return B}const bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,h=t.name;if(!e.modifiersData[h]._skip){for(var d=i.mainAxis,u=void 0===d||d,f=i.altAxis,p=void 0===f||f,_=i.fallbackPlacements,b=i.padding,v=i.boundary,y=i.rootBoundary,w=i.altBoundary,E=i.flipVariations,A=void 0===E||E,T=i.allowedAutoPlacements,C=e.options.placement,O=I(C),x=_||(O!==C&&A?function(t){if(I(t)===a)return[];var e=rt(t);return[lt(t),e,lt(e)]}(C):[rt(C)]),k=[C].concat(x).reduce((function(t,i){return t.concat(I(i)===a?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,c=i.allowedAutoPlacements,h=void 0===c?g:c,d=Z(n),u=d?a?m:m.filter((function(t){return Z(t)===d})):l,f=u.filter((function(t){return h.indexOf(t)>=0}));0===f.length&&(f=u);var p=f.reduce((function(e,i){return e[i]=_t(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[I(i)],e}),{});return Object.keys(p).sort((function(t,e){return p[t]-p[e]}))}(e,{placement:i,boundary:v,rootBoundary:y,padding:b,flipVariations:A,allowedAutoPlacements:T}):i)}),[]),L=e.rects.reference,S=e.rects.popper,D=new Map,$=!0,N=k[0],P=0;P=0,B=H?"width":"height",W=_t(e,{placement:M,boundary:v,rootBoundary:y,altBoundary:w,padding:b}),z=H?F?o:r:F?s:n;L[B]>S[B]&&(z=rt(z));var R=rt(z),q=[];if(u&&q.push(W[j]<=0),p&&q.push(W[z]<=0,W[R]<=0),q.every((function(t){return t}))){N=M,$=!1;break}D.set(M,q)}if($)for(var V=function(t){var e=k.find((function(e){var i=D.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return N=e,"break"},Y=A?3:1;Y>0&&"break"!==V(Y);Y--);e.placement!==N&&(e.modifiersData[h]._skip=!0,e.placement=N,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function vt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function yt(t){return[n,o,s,r].some((function(e){return t[e]>=0}))}const wt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=_t(e,{elementContext:"reference"}),a=_t(e,{altBoundary:!0}),l=vt(r,n),c=vt(a,s,o),h=yt(l),d=yt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Et={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,s=t.name,a=i.offset,l=void 0===a?[0,0]:a,c=g.reduce((function(t,i){return t[i]=function(t,e,i){var s=I(t),a=[r,n].indexOf(s)>=0?-1:1,l="function"==typeof i?i(Object.assign({},e,{placement:t})):i,c=l[0],h=l[1];return c=c||0,h=(h||0)*a,[r,o].indexOf(s)>=0?{x:h,y:c}:{x:c,y:h}}(i,e.rects,l),t}),{}),h=c[e.placement],d=h.x,u=h.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=d,e.modifiersData.popperOffsets.y+=u),e.modifiersData[s]=c}},At={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=gt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},Tt={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,a=t.name,l=i.mainAxis,h=void 0===l||l,d=i.altAxis,u=void 0!==d&&d,f=i.boundary,p=i.rootBoundary,m=i.altBoundary,g=i.padding,_=i.tether,b=void 0===_||_,v=i.tetherOffset,y=void 0===v?0:v,w=_t(e,{boundary:f,rootBoundary:p,padding:g,altBoundary:m}),E=I(e.placement),A=Z(e.placement),T=!A,C=Q(E),O="x"===C?"y":"x",x=e.modifiersData.popperOffsets,k=e.rects.reference,L=e.rects.popper,S="function"==typeof y?y(Object.assign({},e.rects,{placement:e.placement})):y,D="number"==typeof S?{mainAxis:S,altAxis:S}:Object.assign({mainAxis:0,altAxis:0},S),$=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,M={x:0,y:0};if(x){if(h){var j,F="y"===C?n:r,H="y"===C?s:o,W="y"===C?"height":"width",z=x[C],R=z+w[F],q=z-w[H],V=b?-L[W]/2:0,Y=A===c?k[W]:L[W],U=A===c?-L[W]:-k[W],G=e.elements.arrow,J=b&&G?B(G):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[F],it=tt[H],nt=X(0,k[W],J[W]),st=T?k[W]/2-V-nt-et-D.mainAxis:Y-nt-et-D.mainAxis,ot=T?-k[W]/2+V+nt+it+D.mainAxis:U+nt+it+D.mainAxis,rt=e.elements.arrow&&K(e.elements.arrow),at=rt?"y"===C?rt.clientTop||0:rt.clientLeft||0:0,lt=null!=(j=null==$?void 0:$[C])?j:0,ct=z+ot-lt,ht=X(b?P(R,z+st-lt-at):R,z,b?N(q,ct):q);x[C]=ht,M[C]=ht-z}if(u){var dt,ut="x"===C?n:r,ft="x"===C?s:o,pt=x[O],mt="y"===O?"height":"width",gt=pt+w[ut],bt=pt-w[ft],vt=-1!==[n,r].indexOf(E),yt=null!=(dt=null==$?void 0:$[O])?dt:0,wt=vt?gt:pt-k[mt]-L[mt]-yt+D.altAxis,Et=vt?pt+k[mt]+L[mt]-yt-D.altAxis:bt,At=b&&vt?function(t,e,i){var n=X(t,e,i);return n>i?i:n}(wt,pt,Et):X(b?wt:gt,pt,b?Et:bt);x[O]=At,M[O]=At-pt}e.modifiersData[a]=M}},requiresIfExists:["offset"]};function Ct(t,e,i){void 0===i&&(i=!1);var n,s,o=S(e),r=S(e)&&function(t){var e=t.getBoundingClientRect(),i=M(e.width)/t.offsetWidth||1,n=M(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=q(e),l=H(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==x(e)||dt(a))&&(c=(n=e)!==k(n)&&S(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:ct(n)),S(e)?((h=H(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=ht(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function Ot(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var xt={placement:"bottom",modifiers:[],strategy:"absolute"};function kt(){for(var t=arguments.length,e=new Array(t),i=0;iIt.has(t)&&It.get(t).get(e)||null,remove(t,e){if(!It.has(t))return;const i=It.get(t);i.delete(e),0===i.size&&It.delete(t)}},Pt="transitionend",Mt=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),jt=t=>{t.dispatchEvent(new Event(Pt))},Ft=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),Ht=t=>Ft(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(Mt(t)):null,Bt=t=>{if(!Ft(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},Wt=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),zt=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?zt(t.parentNode):null},Rt=()=>{},qt=t=>{t.offsetHeight},Vt=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Yt=[],Kt=()=>"rtl"===document.documentElement.dir,Qt=t=>{var e;e=()=>{const e=Vt();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(Yt.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of Yt)t()})),Yt.push(e)):e()},Xt=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,Ut=(t,e,i=!0)=>{if(!i)return void Xt(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const o=({target:i})=>{i===e&&(s=!0,e.removeEventListener(Pt,o),Xt(t))};e.addEventListener(Pt,o),setTimeout((()=>{s||jt(e)}),n)},Gt=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},Jt=/[^.]*(?=\..*)\.|.*/,Zt=/\..*/,te=/::\d+$/,ee={};let ie=1;const ne={mouseenter:"mouseover",mouseleave:"mouseout"},se=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function oe(t,e){return e&&`${e}::${ie++}`||t.uidEvent||ie++}function re(t){const e=oe(t);return t.uidEvent=e,ee[e]=ee[e]||{},ee[e]}function ae(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function le(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=ue(t);return se.has(o)||(o=t),[n,s,o]}function ce(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=le(e,i,n);if(e in ne){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=re(t),c=l[a]||(l[a]={}),h=ae(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=oe(r,e.replace(Jt,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return pe(s,{delegateTarget:r}),n.oneOff&&fe.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return pe(n,{delegateTarget:t}),i.oneOff&&fe.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function he(t,e,i,n,s){const o=ae(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function de(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&he(t,e,i,r.callable,r.delegationSelector)}function ue(t){return t=t.replace(Zt,""),ne[t]||t}const fe={on(t,e,i,n){ce(t,e,i,n,!1)},one(t,e,i,n){ce(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=le(e,i,n),a=r!==e,l=re(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))de(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(te,"");a&&!e.includes(s)||he(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;he(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=Vt();let s=null,o=!0,r=!0,a=!1;e!==ue(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=pe(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function pe(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function me(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function ge(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const _e={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${ge(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${ge(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=me(t.dataset[n])}return e},getDataAttribute:(t,e)=>me(t.getAttribute(`data-bs-${ge(e)}`))};class be{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=Ft(e)?_e.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...Ft(e)?_e.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],o=Ft(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${o}" but expected type "${s}".`)}var i}}class ve extends be{constructor(t,e){super(),(t=Ht(t))&&(this._element=t,this._config=this._getConfig(e),Nt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){Nt.remove(this._element,this.constructor.DATA_KEY),fe.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){Ut(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return Nt.get(Ht(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.2"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const ye=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?Mt(i.trim()):null}return e},we={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!Wt(t)&&Bt(t)))},getSelectorFromElement(t){const e=ye(t);return e&&we.findOne(e)?e:null},getElementFromSelector(t){const e=ye(t);return e?we.findOne(e):null},getMultipleElementsFromSelector(t){const e=ye(t);return e?we.find(e):[]}},Ee=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;fe.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),Wt(this))return;const s=we.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},Ae=".bs.alert",Te=`close${Ae}`,Ce=`closed${Ae}`;class Oe extends ve{static get NAME(){return"alert"}close(){if(fe.trigger(this._element,Te).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),fe.trigger(this._element,Ce),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Oe.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}Ee(Oe,"close"),Qt(Oe);const xe='[data-bs-toggle="button"]';class ke extends ve{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=ke.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}fe.on(document,"click.bs.button.data-api",xe,(t=>{t.preventDefault();const e=t.target.closest(xe);ke.getOrCreateInstance(e).toggle()})),Qt(ke);const Le=".bs.swipe",Se=`touchstart${Le}`,De=`touchmove${Le}`,$e=`touchend${Le}`,Ie=`pointerdown${Le}`,Ne=`pointerup${Le}`,Pe={endCallback:null,leftCallback:null,rightCallback:null},Me={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class je extends be{constructor(t,e){super(),this._element=t,t&&je.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Pe}static get DefaultType(){return Me}static get NAME(){return"swipe"}dispose(){fe.off(this._element,Le)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),Xt(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&Xt(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(fe.on(this._element,Ie,(t=>this._start(t))),fe.on(this._element,Ne,(t=>this._end(t))),this._element.classList.add("pointer-event")):(fe.on(this._element,Se,(t=>this._start(t))),fe.on(this._element,De,(t=>this._move(t))),fe.on(this._element,$e,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const Fe=".bs.carousel",He=".data-api",Be="next",We="prev",ze="left",Re="right",qe=`slide${Fe}`,Ve=`slid${Fe}`,Ye=`keydown${Fe}`,Ke=`mouseenter${Fe}`,Qe=`mouseleave${Fe}`,Xe=`dragstart${Fe}`,Ue=`load${Fe}${He}`,Ge=`click${Fe}${He}`,Je="carousel",Ze="active",ti=".active",ei=".carousel-item",ii=ti+ei,ni={ArrowLeft:Re,ArrowRight:ze},si={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},oi={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class ri extends ve{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=we.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===Je&&this.cycle()}static get Default(){return si}static get DefaultType(){return oi}static get NAME(){return"carousel"}next(){this._slide(Be)}nextWhenVisible(){!document.hidden&&Bt(this._element)&&this.next()}prev(){this._slide(We)}pause(){this._isSliding&&jt(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?fe.one(this._element,Ve,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void fe.one(this._element,Ve,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?Be:We;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&fe.on(this._element,Ye,(t=>this._keydown(t))),"hover"===this._config.pause&&(fe.on(this._element,Ke,(()=>this.pause())),fe.on(this._element,Qe,(()=>this._maybeEnableCycle()))),this._config.touch&&je.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of we.find(".carousel-item img",this._element))fe.on(t,Xe,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ze)),rightCallback:()=>this._slide(this._directionToOrder(Re)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new je(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=ni[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=we.findOne(ti,this._indicatorsElement);e.classList.remove(Ze),e.removeAttribute("aria-current");const i=we.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(Ze),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===Be,s=e||Gt(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>fe.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(qe).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),qt(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(Ze),i.classList.remove(Ze,c,l),this._isSliding=!1,r(Ve)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return we.findOne(ii,this._element)}_getItems(){return we.find(ei,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return Kt()?t===ze?We:Be:t===ze?Be:We}_orderToDirection(t){return Kt()?t===We?ze:Re:t===We?Re:ze}static jQueryInterface(t){return this.each((function(){const e=ri.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}fe.on(document,Ge,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=we.getElementFromSelector(this);if(!e||!e.classList.contains(Je))return;t.preventDefault();const i=ri.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===_e.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),fe.on(window,Ue,(()=>{const t=we.find('[data-bs-ride="carousel"]');for(const e of t)ri.getOrCreateInstance(e)})),Qt(ri);const ai=".bs.collapse",li=`show${ai}`,ci=`shown${ai}`,hi=`hide${ai}`,di=`hidden${ai}`,ui=`click${ai}.data-api`,fi="show",pi="collapse",mi="collapsing",gi=`:scope .${pi} .${pi}`,_i='[data-bs-toggle="collapse"]',bi={parent:null,toggle:!0},vi={parent:"(null|element)",toggle:"boolean"};class yi extends ve{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=we.find(_i);for(const t of i){const e=we.getSelectorFromElement(t),i=we.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return bi}static get DefaultType(){return vi}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>yi.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(fe.trigger(this._element,li).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(pi),this._element.classList.add(mi),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(mi),this._element.classList.add(pi,fi),this._element.style[e]="",fe.trigger(this._element,ci)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(fe.trigger(this._element,hi).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,qt(this._element),this._element.classList.add(mi),this._element.classList.remove(pi,fi);for(const t of this._triggerArray){const e=we.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(mi),this._element.classList.add(pi),fe.trigger(this._element,di)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(fi)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=Ht(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(_i);for(const e of t){const t=we.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=we.find(gi,this._config.parent);return we.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=yi.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}fe.on(document,ui,_i,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of we.getMultipleElementsFromSelector(this))yi.getOrCreateInstance(t,{toggle:!1}).toggle()})),Qt(yi);const wi="dropdown",Ei=".bs.dropdown",Ai=".data-api",Ti="ArrowUp",Ci="ArrowDown",Oi=`hide${Ei}`,xi=`hidden${Ei}`,ki=`show${Ei}`,Li=`shown${Ei}`,Si=`click${Ei}${Ai}`,Di=`keydown${Ei}${Ai}`,$i=`keyup${Ei}${Ai}`,Ii="show",Ni='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Pi=`${Ni}.${Ii}`,Mi=".dropdown-menu",ji=Kt()?"top-end":"top-start",Fi=Kt()?"top-start":"top-end",Hi=Kt()?"bottom-end":"bottom-start",Bi=Kt()?"bottom-start":"bottom-end",Wi=Kt()?"left-start":"right-start",zi=Kt()?"right-start":"left-start",Ri={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},qi={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Vi extends ve{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=we.next(this._element,Mi)[0]||we.prev(this._element,Mi)[0]||we.findOne(Mi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Ri}static get DefaultType(){return qi}static get NAME(){return wi}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Wt(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!fe.trigger(this._element,ki,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Ii),this._element.classList.add(Ii),fe.trigger(this._element,Li,t)}}hide(){if(Wt(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!fe.trigger(this._element,Oi,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._popper&&this._popper.destroy(),this._menu.classList.remove(Ii),this._element.classList.remove(Ii),this._element.setAttribute("aria-expanded","false"),_e.removeDataAttribute(this._menu,"popper"),fe.trigger(this._element,xi,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!Ft(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${wi.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===e)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:Ft(this._config.reference)?t=Ht(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const i=this._getPopperConfig();this._popper=Dt(t,this._menu,i)}_isShown(){return this._menu.classList.contains(Ii)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return Wi;if(t.classList.contains("dropstart"))return zi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?Fi:ji:e?Bi:Hi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(_e.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...Xt(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=we.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>Bt(t)));i.length&&Gt(i,e,t===Ci,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=Vi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=we.find(Pi);for(const i of e){const e=Vi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ti,Ci].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ni)?this:we.prev(this,Ni)[0]||we.next(this,Ni)[0]||we.findOne(Ni,t.delegateTarget.parentNode),o=Vi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}fe.on(document,Di,Ni,Vi.dataApiKeydownHandler),fe.on(document,Di,Mi,Vi.dataApiKeydownHandler),fe.on(document,Si,Vi.clearMenus),fe.on(document,$i,Vi.clearMenus),fe.on(document,Si,Ni,(function(t){t.preventDefault(),Vi.getOrCreateInstance(this).toggle()})),Qt(Vi);const Yi="backdrop",Ki="show",Qi=`mousedown.bs.${Yi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ui={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Gi extends be{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Ui}static get NAME(){return Yi}show(t){if(!this._config.isVisible)return void Xt(t);this._append();const e=this._getElement();this._config.isAnimated&&qt(e),e.classList.add(Ki),this._emulateAnimation((()=>{Xt(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),Xt(t)}))):Xt(t)}dispose(){this._isAppended&&(fe.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=Ht(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),fe.on(t,Qi,(()=>{Xt(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){Ut(t,this._getElement(),this._config.isAnimated)}}const Ji=".bs.focustrap",Zi=`focusin${Ji}`,tn=`keydown.tab${Ji}`,en="backward",nn={autofocus:!0,trapElement:null},sn={autofocus:"boolean",trapElement:"element"};class on extends be{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return nn}static get DefaultType(){return sn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),fe.off(document,Ji),fe.on(document,Zi,(t=>this._handleFocusin(t))),fe.on(document,tn,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,fe.off(document,Ji))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=we.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===en?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?en:"forward")}}const rn=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",an=".sticky-top",ln="padding-right",cn="margin-right";class hn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,ln,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e+t)),this._setElementAttributes(an,cn,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,ln),this._resetElementAttributes(rn,ln),this._resetElementAttributes(an,cn)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&_e.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=_e.getDataAttribute(t,e);null!==i?(_e.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(Ft(t))e(t);else for(const i of we.find(t,this._element))e(i)}}const dn=".bs.modal",un=`hide${dn}`,fn=`hidePrevented${dn}`,pn=`hidden${dn}`,mn=`show${dn}`,gn=`shown${dn}`,_n=`resize${dn}`,bn=`click.dismiss${dn}`,vn=`mousedown.dismiss${dn}`,yn=`keydown.dismiss${dn}`,wn=`click${dn}.data-api`,En="modal-open",An="show",Tn="modal-static",Cn={backdrop:!0,focus:!0,keyboard:!0},On={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class xn extends ve{constructor(t,e){super(t,e),this._dialog=we.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new hn,this._addEventListeners()}static get Default(){return Cn}static get DefaultType(){return On}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||fe.trigger(this._element,mn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(En),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(fe.trigger(this._element,un).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){fe.off(window,dn),fe.off(this._dialog,dn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Gi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new on({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=we.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),qt(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,fe.trigger(this._element,gn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){fe.on(this._element,yn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),fe.on(window,_n,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),fe.on(this._element,vn,(t=>{fe.one(this._element,bn,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(En),this._resetAdjustments(),this._scrollBar.reset(),fe.trigger(this._element,pn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(fe.trigger(this._element,fn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Tn)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Tn),this._queueCallback((()=>{this._element.classList.remove(Tn),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=Kt()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=Kt()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=xn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}fe.on(document,wn,'[data-bs-toggle="modal"]',(function(t){const e=we.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),fe.one(e,mn,(t=>{t.defaultPrevented||fe.one(e,pn,(()=>{Bt(this)&&this.focus()}))}));const i=we.findOne(".modal.show");i&&xn.getInstance(i).hide(),xn.getOrCreateInstance(e).toggle(this)})),Ee(xn),Qt(xn);const kn=".bs.offcanvas",Ln=".data-api",Sn=`load${kn}${Ln}`,Dn="show",$n="showing",In="hiding",Nn=".offcanvas.show",Pn=`show${kn}`,Mn=`shown${kn}`,jn=`hide${kn}`,Fn=`hidePrevented${kn}`,Hn=`hidden${kn}`,Bn=`resize${kn}`,Wn=`click${kn}${Ln}`,zn=`keydown.dismiss${kn}`,Rn={backdrop:!0,keyboard:!0,scroll:!1},qn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Vn extends ve{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Rn}static get DefaultType(){return qn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||fe.trigger(this._element,Pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new hn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($n),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Dn),this._element.classList.remove($n),fe.trigger(this._element,Mn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(fe.trigger(this._element,jn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(In),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Dn,In),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new hn).reset(),fe.trigger(this._element,Hn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Gi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():fe.trigger(this._element,Fn)}:null})}_initializeFocusTrap(){return new on({trapElement:this._element})}_addEventListeners(){fe.on(this._element,zn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():fe.trigger(this._element,Fn))}))}static jQueryInterface(t){return this.each((function(){const e=Vn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}fe.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=we.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),Wt(this))return;fe.one(e,Hn,(()=>{Bt(this)&&this.focus()}));const i=we.findOne(Nn);i&&i!==e&&Vn.getInstance(i).hide(),Vn.getOrCreateInstance(e).toggle(this)})),fe.on(window,Sn,(()=>{for(const t of we.find(Nn))Vn.getOrCreateInstance(t).show()})),fe.on(window,Bn,(()=>{for(const t of we.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Vn.getOrCreateInstance(t).hide()})),Ee(Vn),Qt(Vn);const Yn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Un={allowList:Yn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Gn={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Jn={entry:"(string|element|function|null)",selector:"(string|element)"};class Zn extends be{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Un}static get DefaultType(){return Gn}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Jn)}_setContent(t,e,i){const n=we.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?Ft(e)?this._putElementInTemplate(Ht(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return Xt(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const ts=new Set(["sanitize","allowList","sanitizeFn"]),es="fade",is="show",ns=".modal",ss="hide.bs.modal",os="hover",rs="focus",as={AUTO:"auto",TOP:"top",RIGHT:Kt()?"left":"right",BOTTOM:"bottom",LEFT:Kt()?"right":"left"},ls={allowList:Yn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},cs={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class hs extends ve{constructor(t,i){if(void 0===e)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,i),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return ls}static get DefaultType(){return cs}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),fe.off(this._element.closest(ns),ss,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=fe.trigger(this._element,this.constructor.eventName("show")),e=(zt(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),fe.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(is),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.on(t,"mouseover",Rt);this._queueCallback((()=>{fe.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!fe.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(is),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))fe.off(t,"mouseover",Rt);this._activeTrigger.click=!1,this._activeTrigger[rs]=!1,this._activeTrigger[os]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),fe.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(es,is),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(es),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Zn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(es)}_isShown(){return this.tip&&this.tip.classList.contains(is)}_createPopper(t){const e=Xt(this._config.placement,[this,t,this._element]),i=as[e.toUpperCase()];return Dt(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return Xt(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...Xt(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)fe.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===os?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===os?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");fe.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?rs:os]=!0,e._enter()})),fe.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?rs:os]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},fe.on(this._element.closest(ns),ss,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=_e.getDataAttributes(this._element);for(const t of Object.keys(e))ts.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:Ht(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=hs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Qt(hs);const ds={...hs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},us={...hs.DefaultType,content:"(null|string|element|function)"};class fs extends hs{static get Default(){return ds}static get DefaultType(){return us}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=fs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}Qt(fs);const ps=".bs.scrollspy",ms=`activate${ps}`,gs=`click${ps}`,_s=`load${ps}.data-api`,bs="active",vs="[href]",ys=".nav-link",ws=`${ys}, .nav-item > ${ys}, .list-group-item`,Es={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Ts extends ve{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Es}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=Ht(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(fe.off(this._config.target,gs),fe.on(this._config.target,gs,vs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=we.find(vs,this._config.target);for(const e of t){if(!e.hash||Wt(e))continue;const t=we.findOne(decodeURI(e.hash),this._element);Bt(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(bs),this._activateParents(t),fe.trigger(this._element,ms,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))we.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(bs);else for(const e of we.parents(t,".nav, .list-group"))for(const t of we.prev(e,ws))t.classList.add(bs)}_clearActiveClass(t){t.classList.remove(bs);const e=we.find(`${vs}.${bs}`,t);for(const t of e)t.classList.remove(bs)}static jQueryInterface(t){return this.each((function(){const e=Ts.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(window,_s,(()=>{for(const t of we.find('[data-bs-spy="scroll"]'))Ts.getOrCreateInstance(t)})),Qt(Ts);const Cs=".bs.tab",Os=`hide${Cs}`,xs=`hidden${Cs}`,ks=`show${Cs}`,Ls=`shown${Cs}`,Ss=`click${Cs}`,Ds=`keydown${Cs}`,$s=`load${Cs}`,Is="ArrowLeft",Ns="ArrowRight",Ps="ArrowUp",Ms="ArrowDown",js="Home",Fs="End",Hs="active",Bs="fade",Ws="show",zs=".dropdown-toggle",Rs=`:not(${zs})`,qs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Vs=`.nav-link${Rs}, .list-group-item${Rs}, [role="tab"]${Rs}, ${qs}`,Ys=`.${Hs}[data-bs-toggle="tab"], .${Hs}[data-bs-toggle="pill"], .${Hs}[data-bs-toggle="list"]`;class Ks extends ve{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),fe.on(this._element,Ds,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?fe.trigger(e,Os,{relatedTarget:t}):null;fe.trigger(t,ks,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Hs),this._activate(we.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),fe.trigger(t,Ls,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Bs)))}_deactivate(t,e){t&&(t.classList.remove(Hs),t.blur(),this._deactivate(we.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),fe.trigger(t,xs,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Bs)))}_keydown(t){if(![Is,Ns,Ps,Ms,js,Fs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!Wt(t)));let i;if([js,Fs].includes(t.key))i=e[t.key===js?0:e.length-1];else{const n=[Ns,Ms].includes(t.key);i=Gt(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Ks.getOrCreateInstance(i).show())}_getChildren(){return we.find(Vs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=we.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=we.findOne(t,i);s&&s.classList.toggle(n,e)};n(zs,Hs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Hs)}_getInnerElement(t){return t.matches(Vs)?t:we.findOne(Vs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Ks.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}fe.on(document,Ss,qs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),Wt(this)||Ks.getOrCreateInstance(this).show()})),fe.on(window,$s,(()=>{for(const t of we.find(Ys))Ks.getOrCreateInstance(t)})),Qt(Ks);const Qs=".bs.toast",Xs=`mouseover${Qs}`,Us=`mouseout${Qs}`,Gs=`focusin${Qs}`,Js=`focusout${Qs}`,Zs=`hide${Qs}`,to=`hidden${Qs}`,eo=`show${Qs}`,io=`shown${Qs}`,no="hide",so="show",oo="showing",ro={animation:"boolean",autohide:"boolean",delay:"number"},ao={animation:!0,autohide:!0,delay:5e3};class lo extends ve{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return ao}static get DefaultType(){return ro}static get NAME(){return"toast"}show(){fe.trigger(this._element,eo).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(no),qt(this._element),this._element.classList.add(so,oo),this._queueCallback((()=>{this._element.classList.remove(oo),fe.trigger(this._element,io),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(fe.trigger(this._element,Zs).defaultPrevented||(this._element.classList.add(oo),this._queueCallback((()=>{this._element.classList.add(no),this._element.classList.remove(oo,so),fe.trigger(this._element,to)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(so),super.dispose()}isShown(){return this._element.classList.contains(so)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){fe.on(this._element,Xs,(t=>this._onInteraction(t,!0))),fe.on(this._element,Us,(t=>this._onInteraction(t,!1))),fe.on(this._element,Gs,(t=>this._onInteraction(t,!0))),fe.on(this._element,Js,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=lo.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}function co(t){"loading"!=document.readyState?t():document.addEventListener("DOMContentLoaded",t)}Ee(lo),Qt(lo),co((function(){[].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')).map((function(t){return new hs(t,{delay:{show:500,hide:100}})}))})),co((function(){document.getElementById("pst-back-to-top").addEventListener("click",(function(){document.body.scrollTop=0,document.documentElement.scrollTop=0}))})),co((function(){var t=document.getElementById("pst-back-to-top"),e=document.getElementsByClassName("bd-header")[0].getBoundingClientRect();window.addEventListener("scroll",(function(){this.oldScroll>this.scrollY&&this.scrollY>e.bottom?t.style.display="block":t.style.display="none",this.oldScroll=this.scrollY}))})),window.bootstrap=i})(); +//# sourceMappingURL=bootstrap.js.map \ No newline at end of file diff --git a/_static/scripts/bootstrap.js.LICENSE.txt b/_static/scripts/bootstrap.js.LICENSE.txt new file mode 100644 index 00000000..10f979d0 --- /dev/null +++ b/_static/scripts/bootstrap.js.LICENSE.txt @@ -0,0 +1,5 @@ +/*! + * Bootstrap v5.3.2 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ diff --git a/_static/scripts/bootstrap.js.map b/_static/scripts/bootstrap.js.map new file mode 100644 index 00000000..64e212b1 --- /dev/null +++ b/_static/scripts/bootstrap.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scripts/bootstrap.js","mappings":";mBACA,IAAIA,EAAsB,CCA1BA,EAAwB,CAACC,EAASC,KACjC,IAAI,IAAIC,KAAOD,EACXF,EAAoBI,EAAEF,EAAYC,KAASH,EAAoBI,EAAEH,EAASE,IAC5EE,OAAOC,eAAeL,EAASE,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,IAE1E,ECNDH,EAAwB,CAACS,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,GCClFV,EAAyBC,IACH,oBAAXa,QAA0BA,OAAOC,aAC1CV,OAAOC,eAAeL,EAASa,OAAOC,YAAa,CAAEC,MAAO,WAE7DX,OAAOC,eAAeL,EAAS,aAAc,CAAEe,OAAO,GAAO,01BCLvD,IAAI,EAAM,MACNC,EAAS,SACTC,EAAQ,QACRC,EAAO,OACPC,EAAO,OACPC,EAAiB,CAAC,EAAKJ,EAAQC,EAAOC,GACtCG,EAAQ,QACRC,EAAM,MACNC,EAAkB,kBAClBC,EAAW,WACXC,EAAS,SACTC,EAAY,YACZC,EAAmCP,EAAeQ,QAAO,SAAUC,EAAKC,GACjF,OAAOD,EAAIE,OAAO,CAACD,EAAY,IAAMT,EAAOS,EAAY,IAAMR,GAChE,GAAG,IACQ,EAA0B,GAAGS,OAAOX,EAAgB,CAACD,IAAOS,QAAO,SAAUC,EAAKC,GAC3F,OAAOD,EAAIE,OAAO,CAACD,EAAWA,EAAY,IAAMT,EAAOS,EAAY,IAAMR,GAC3E,GAAG,IAEQU,EAAa,aACbC,EAAO,OACPC,EAAY,YAEZC,EAAa,aACbC,EAAO,OACPC,EAAY,YAEZC,EAAc,cACdC,EAAQ,QACRC,EAAa,aACbC,EAAiB,CAACT,EAAYC,EAAMC,EAAWC,EAAYC,EAAMC,EAAWC,EAAaC,EAAOC,GC9B5F,SAASE,EAAYC,GAClC,OAAOA,GAAWA,EAAQC,UAAY,IAAIC,cAAgB,IAC5D,CCFe,SAASC,EAAUC,GAChC,GAAY,MAARA,EACF,OAAOC,OAGT,GAAwB,oBAApBD,EAAKE,WAAkC,CACzC,IAAIC,EAAgBH,EAAKG,cACzB,OAAOA,GAAgBA,EAAcC,aAAwBH,MAC/D,CAEA,OAAOD,CACT,CCTA,SAASK,EAAUL,GAEjB,OAAOA,aADUD,EAAUC,GAAMM,SACIN,aAAgBM,OACvD,CAEA,SAASC,EAAcP,GAErB,OAAOA,aADUD,EAAUC,GAAMQ,aACIR,aAAgBQ,WACvD,CAEA,SAASC,EAAaT,GAEpB,MAA0B,oBAAfU,aAKJV,aADUD,EAAUC,GAAMU,YACIV,aAAgBU,WACvD,CCwDA,SACEC,KAAM,cACNC,SAAS,EACTC,MAAO,QACPC,GA5EF,SAAqBC,GACnB,IAAIC,EAAQD,EAAKC,MACjB3D,OAAO4D,KAAKD,EAAME,UAAUC,SAAQ,SAAUR,GAC5C,IAAIS,EAAQJ,EAAMK,OAAOV,IAAS,CAAC,EAC/BW,EAAaN,EAAMM,WAAWX,IAAS,CAAC,EACxCf,EAAUoB,EAAME,SAASP,GAExBJ,EAAcX,IAAaD,EAAYC,KAO5CvC,OAAOkE,OAAO3B,EAAQwB,MAAOA,GAC7B/D,OAAO4D,KAAKK,GAAYH,SAAQ,SAAUR,GACxC,IAAI3C,EAAQsD,EAAWX,IAET,IAAV3C,EACF4B,EAAQ4B,gBAAgBb,GAExBf,EAAQ6B,aAAad,GAAgB,IAAV3C,EAAiB,GAAKA,EAErD,IACF,GACF,EAoDE0D,OAlDF,SAAgBC,GACd,IAAIX,EAAQW,EAAMX,MACdY,EAAgB,CAClBlD,OAAQ,CACNmD,SAAUb,EAAMc,QAAQC,SACxB5D,KAAM,IACN6D,IAAK,IACLC,OAAQ,KAEVC,MAAO,CACLL,SAAU,YAEZlD,UAAW,CAAC,GASd,OAPAtB,OAAOkE,OAAOP,EAAME,SAASxC,OAAO0C,MAAOQ,EAAclD,QACzDsC,EAAMK,OAASO,EAEXZ,EAAME,SAASgB,OACjB7E,OAAOkE,OAAOP,EAAME,SAASgB,MAAMd,MAAOQ,EAAcM,OAGnD,WACL7E,OAAO4D,KAAKD,EAAME,UAAUC,SAAQ,SAAUR,GAC5C,IAAIf,EAAUoB,EAAME,SAASP,GACzBW,EAAaN,EAAMM,WAAWX,IAAS,CAAC,EAGxCS,EAFkB/D,OAAO4D,KAAKD,EAAMK,OAAOzD,eAAe+C,GAAQK,EAAMK,OAAOV,GAAQiB,EAAcjB,IAE7E9B,QAAO,SAAUuC,EAAOe,GAElD,OADAf,EAAMe,GAAY,GACXf,CACT,GAAG,CAAC,GAECb,EAAcX,IAAaD,EAAYC,KAI5CvC,OAAOkE,OAAO3B,EAAQwB,MAAOA,GAC7B/D,OAAO4D,KAAKK,GAAYH,SAAQ,SAAUiB,GACxCxC,EAAQ4B,gBAAgBY,EAC1B,IACF,GACF,CACF,EASEC,SAAU,CAAC,kBCjFE,SAASC,EAAiBvD,GACvC,OAAOA,EAAUwD,MAAM,KAAK,EAC9B,CCHO,IAAI,EAAMC,KAAKC,IACX,EAAMD,KAAKE,IACXC,EAAQH,KAAKG,MCFT,SAASC,IACtB,IAAIC,EAASC,UAAUC,cAEvB,OAAc,MAAVF,GAAkBA,EAAOG,QAAUC,MAAMC,QAAQL,EAAOG,QACnDH,EAAOG,OAAOG,KAAI,SAAUC,GACjC,OAAOA,EAAKC,MAAQ,IAAMD,EAAKE,OACjC,IAAGC,KAAK,KAGHT,UAAUU,SACnB,CCTe,SAASC,IACtB,OAAQ,iCAAiCC,KAAKd,IAChD,CCCe,SAASe,EAAsB/D,EAASgE,EAAcC,QAC9C,IAAjBD,IACFA,GAAe,QAGO,IAApBC,IACFA,GAAkB,GAGpB,IAAIC,EAAalE,EAAQ+D,wBACrBI,EAAS,EACTC,EAAS,EAETJ,GAAgBrD,EAAcX,KAChCmE,EAASnE,EAAQqE,YAAc,GAAItB,EAAMmB,EAAWI,OAAStE,EAAQqE,aAAmB,EACxFD,EAASpE,EAAQuE,aAAe,GAAIxB,EAAMmB,EAAWM,QAAUxE,EAAQuE,cAAoB,GAG7F,IACIE,GADOhE,EAAUT,GAAWG,EAAUH,GAAWK,QAC3BoE,eAEtBC,GAAoBb,KAAsBI,EAC1CU,GAAKT,EAAW3F,MAAQmG,GAAoBD,EAAiBA,EAAeG,WAAa,IAAMT,EAC/FU,GAAKX,EAAW9B,KAAOsC,GAAoBD,EAAiBA,EAAeK,UAAY,IAAMV,EAC7FE,EAAQJ,EAAWI,MAAQH,EAC3BK,EAASN,EAAWM,OAASJ,EACjC,MAAO,CACLE,MAAOA,EACPE,OAAQA,EACRpC,IAAKyC,EACLvG,MAAOqG,EAAIL,EACXjG,OAAQwG,EAAIL,EACZjG,KAAMoG,EACNA,EAAGA,EACHE,EAAGA,EAEP,CCrCe,SAASE,EAAc/E,GACpC,IAAIkE,EAAaH,EAAsB/D,GAGnCsE,EAAQtE,EAAQqE,YAChBG,EAASxE,EAAQuE,aAUrB,OARI3B,KAAKoC,IAAId,EAAWI,MAAQA,IAAU,IACxCA,EAAQJ,EAAWI,OAGjB1B,KAAKoC,IAAId,EAAWM,OAASA,IAAW,IAC1CA,EAASN,EAAWM,QAGf,CACLG,EAAG3E,EAAQ4E,WACXC,EAAG7E,EAAQ8E,UACXR,MAAOA,EACPE,OAAQA,EAEZ,CCvBe,SAASS,EAASC,EAAQC,GACvC,IAAIC,EAAWD,EAAME,aAAeF,EAAME,cAE1C,GAAIH,EAAOD,SAASE,GAClB,OAAO,EAEJ,GAAIC,GAAYvE,EAAauE,GAAW,CACzC,IAAIE,EAAOH,EAEX,EAAG,CACD,GAAIG,GAAQJ,EAAOK,WAAWD,GAC5B,OAAO,EAITA,EAAOA,EAAKE,YAAcF,EAAKG,IACjC,OAASH,EACX,CAGF,OAAO,CACT,CCrBe,SAAS,EAAiBtF,GACvC,OAAOG,EAAUH,GAAS0F,iBAAiB1F,EAC7C,CCFe,SAAS2F,EAAe3F,GACrC,MAAO,CAAC,QAAS,KAAM,MAAM4F,QAAQ7F,EAAYC,KAAa,CAChE,CCFe,SAAS6F,EAAmB7F,GAEzC,QAASS,EAAUT,GAAWA,EAAQO,cACtCP,EAAQ8F,WAAazF,OAAOyF,UAAUC,eACxC,CCFe,SAASC,EAAchG,GACpC,MAA6B,SAAzBD,EAAYC,GACPA,EAMPA,EAAQiG,cACRjG,EAAQwF,aACR3E,EAAab,GAAWA,EAAQyF,KAAO,OAEvCI,EAAmB7F,EAGvB,CCVA,SAASkG,EAAoBlG,GAC3B,OAAKW,EAAcX,IACoB,UAAvC,EAAiBA,GAASiC,SAInBjC,EAAQmG,aAHN,IAIX,CAwCe,SAASC,EAAgBpG,GAItC,IAHA,IAAIK,EAASF,EAAUH,GACnBmG,EAAeD,EAAoBlG,GAEhCmG,GAAgBR,EAAeQ,IAA6D,WAA5C,EAAiBA,GAAclE,UACpFkE,EAAeD,EAAoBC,GAGrC,OAAIA,IAA+C,SAA9BpG,EAAYoG,IAA0D,SAA9BpG,EAAYoG,IAAwE,WAA5C,EAAiBA,GAAclE,UAC3H5B,EAGF8F,GAhDT,SAA4BnG,GAC1B,IAAIqG,EAAY,WAAWvC,KAAKd,KAGhC,GAFW,WAAWc,KAAKd,MAEfrC,EAAcX,IAII,UAFX,EAAiBA,GAEnBiC,SACb,OAAO,KAIX,IAAIqE,EAAcN,EAAchG,GAMhC,IAJIa,EAAayF,KACfA,EAAcA,EAAYb,MAGrB9E,EAAc2F,IAAgB,CAAC,OAAQ,QAAQV,QAAQ7F,EAAYuG,IAAgB,GAAG,CAC3F,IAAIC,EAAM,EAAiBD,GAI3B,GAAsB,SAAlBC,EAAIC,WAA4C,SAApBD,EAAIE,aAA0C,UAAhBF,EAAIG,UAAiF,IAA1D,CAAC,YAAa,eAAed,QAAQW,EAAII,aAAsBN,GAAgC,WAAnBE,EAAII,YAA2BN,GAAaE,EAAIK,QAAyB,SAAfL,EAAIK,OACjO,OAAON,EAEPA,EAAcA,EAAYd,UAE9B,CAEA,OAAO,IACT,CAgByBqB,CAAmB7G,IAAYK,CACxD,CCpEe,SAASyG,EAAyB3H,GAC/C,MAAO,CAAC,MAAO,UAAUyG,QAAQzG,IAAc,EAAI,IAAM,GAC3D,CCDO,SAAS4H,EAAOjE,EAAK1E,EAAOyE,GACjC,OAAO,EAAQC,EAAK,EAAQ1E,EAAOyE,GACrC,CCFe,SAASmE,EAAmBC,GACzC,OAAOxJ,OAAOkE,OAAO,CAAC,ECDf,CACLS,IAAK,EACL9D,MAAO,EACPD,OAAQ,EACRE,KAAM,GDHuC0I,EACjD,CEHe,SAASC,EAAgB9I,EAAOiD,GAC7C,OAAOA,EAAKpC,QAAO,SAAUkI,EAAS5J,GAEpC,OADA4J,EAAQ5J,GAAOa,EACR+I,CACT,GAAG,CAAC,EACN,CC4EA,SACEpG,KAAM,QACNC,SAAS,EACTC,MAAO,OACPC,GApEF,SAAeC,GACb,IAAIiG,EAEAhG,EAAQD,EAAKC,MACbL,EAAOI,EAAKJ,KACZmB,EAAUf,EAAKe,QACfmF,EAAejG,EAAME,SAASgB,MAC9BgF,EAAgBlG,EAAMmG,cAAcD,cACpCE,EAAgB9E,EAAiBtB,EAAMjC,WACvCsI,EAAOX,EAAyBU,GAEhCE,EADa,CAACnJ,EAAMD,GAAOsH,QAAQ4B,IAAkB,EAClC,SAAW,QAElC,GAAKH,GAAiBC,EAAtB,CAIA,IAAIL,EAxBgB,SAAyBU,EAASvG,GAItD,OAAO4F,EAAsC,iBAH7CW,EAA6B,mBAAZA,EAAyBA,EAAQlK,OAAOkE,OAAO,CAAC,EAAGP,EAAMwG,MAAO,CAC/EzI,UAAWiC,EAAMjC,aACbwI,GACkDA,EAAUT,EAAgBS,EAASlJ,GAC7F,CAmBsBoJ,CAAgB3F,EAAQyF,QAASvG,GACjD0G,EAAY/C,EAAcsC,GAC1BU,EAAmB,MAATN,EAAe,EAAMlJ,EAC/ByJ,EAAmB,MAATP,EAAepJ,EAASC,EAClC2J,EAAU7G,EAAMwG,MAAM7I,UAAU2I,GAAOtG,EAAMwG,MAAM7I,UAAU0I,GAAQH,EAAcG,GAAQrG,EAAMwG,MAAM9I,OAAO4I,GAC9GQ,EAAYZ,EAAcG,GAAQrG,EAAMwG,MAAM7I,UAAU0I,GACxDU,EAAoB/B,EAAgBiB,GACpCe,EAAaD,EAA6B,MAATV,EAAeU,EAAkBE,cAAgB,EAAIF,EAAkBG,aAAe,EAAI,EAC3HC,EAAoBN,EAAU,EAAIC,EAAY,EAG9CpF,EAAMmE,EAAcc,GACpBlF,EAAMuF,EAAaN,EAAUJ,GAAOT,EAAce,GAClDQ,EAASJ,EAAa,EAAIN,EAAUJ,GAAO,EAAIa,EAC/CE,EAAS1B,EAAOjE,EAAK0F,EAAQ3F,GAE7B6F,EAAWjB,EACfrG,EAAMmG,cAAcxG,KAASqG,EAAwB,CAAC,GAAyBsB,GAAYD,EAAQrB,EAAsBuB,aAAeF,EAASD,EAAQpB,EAnBzJ,CAoBF,EAkCEtF,OAhCF,SAAgBC,GACd,IAAIX,EAAQW,EAAMX,MAEdwH,EADU7G,EAAMG,QACWlC,QAC3BqH,OAAoC,IAArBuB,EAA8B,sBAAwBA,EAErD,MAAhBvB,IAKwB,iBAAjBA,IACTA,EAAejG,EAAME,SAASxC,OAAO+J,cAAcxB,MAOhDpC,EAAS7D,EAAME,SAASxC,OAAQuI,KAIrCjG,EAAME,SAASgB,MAAQ+E,EACzB,EASE5E,SAAU,CAAC,iBACXqG,iBAAkB,CAAC,oBCxFN,SAASC,EAAa5J,GACnC,OAAOA,EAAUwD,MAAM,KAAK,EAC9B,CCOA,IAAIqG,GAAa,CACf5G,IAAK,OACL9D,MAAO,OACPD,OAAQ,OACRE,KAAM,QAeD,SAAS0K,GAAYlH,GAC1B,IAAImH,EAEApK,EAASiD,EAAMjD,OACfqK,EAAapH,EAAMoH,WACnBhK,EAAY4C,EAAM5C,UAClBiK,EAAYrH,EAAMqH,UAClBC,EAAUtH,EAAMsH,QAChBpH,EAAWF,EAAME,SACjBqH,EAAkBvH,EAAMuH,gBACxBC,EAAWxH,EAAMwH,SACjBC,EAAezH,EAAMyH,aACrBC,EAAU1H,EAAM0H,QAChBC,EAAaL,EAAQ1E,EACrBA,OAAmB,IAAf+E,EAAwB,EAAIA,EAChCC,EAAaN,EAAQxE,EACrBA,OAAmB,IAAf8E,EAAwB,EAAIA,EAEhCC,EAAgC,mBAAjBJ,EAA8BA,EAAa,CAC5D7E,EAAGA,EACHE,IACG,CACHF,EAAGA,EACHE,GAGFF,EAAIiF,EAAMjF,EACVE,EAAI+E,EAAM/E,EACV,IAAIgF,EAAOR,EAAQrL,eAAe,KAC9B8L,EAAOT,EAAQrL,eAAe,KAC9B+L,EAAQxL,EACRyL,EAAQ,EACRC,EAAM5J,OAEV,GAAIkJ,EAAU,CACZ,IAAIpD,EAAeC,EAAgBtH,GAC/BoL,EAAa,eACbC,EAAY,cAEZhE,IAAiBhG,EAAUrB,IAGmB,WAA5C,EAFJqH,EAAeN,EAAmB/G,IAECmD,UAAsC,aAAbA,IAC1DiI,EAAa,eACbC,EAAY,gBAOZhL,IAAc,IAAQA,IAAcZ,GAAQY,IAAcb,IAAU8K,IAAczK,KACpFqL,EAAQ3L,EAGRwG,IAFc4E,GAAWtD,IAAiB8D,GAAOA,EAAIxF,eAAiBwF,EAAIxF,eAAeD,OACzF2B,EAAa+D,IACEf,EAAW3E,OAC1BK,GAAKyE,EAAkB,GAAK,GAG1BnK,IAAcZ,IAASY,IAAc,GAAOA,IAAcd,GAAW+K,IAAczK,KACrFoL,EAAQzL,EAGRqG,IAFc8E,GAAWtD,IAAiB8D,GAAOA,EAAIxF,eAAiBwF,EAAIxF,eAAeH,MACzF6B,EAAagE,IACEhB,EAAW7E,MAC1BK,GAAK2E,EAAkB,GAAK,EAEhC,CAEA,IAgBMc,EAhBFC,EAAe5M,OAAOkE,OAAO,CAC/BM,SAAUA,GACTsH,GAAYP,IAEXsB,GAAyB,IAAjBd,EAlFd,SAA2BrI,EAAM8I,GAC/B,IAAItF,EAAIxD,EAAKwD,EACTE,EAAI1D,EAAK0D,EACT0F,EAAMN,EAAIO,kBAAoB,EAClC,MAAO,CACL7F,EAAG5B,EAAM4B,EAAI4F,GAAOA,GAAO,EAC3B1F,EAAG9B,EAAM8B,EAAI0F,GAAOA,GAAO,EAE/B,CA0EsCE,CAAkB,CACpD9F,EAAGA,EACHE,GACC1E,EAAUrB,IAAW,CACtB6F,EAAGA,EACHE,GAMF,OAHAF,EAAI2F,EAAM3F,EACVE,EAAIyF,EAAMzF,EAENyE,EAGK7L,OAAOkE,OAAO,CAAC,EAAG0I,IAAeD,EAAiB,CAAC,GAAkBJ,GAASF,EAAO,IAAM,GAAIM,EAAeL,GAASF,EAAO,IAAM,GAAIO,EAAe5D,WAAayD,EAAIO,kBAAoB,IAAM,EAAI,aAAe7F,EAAI,OAASE,EAAI,MAAQ,eAAiBF,EAAI,OAASE,EAAI,SAAUuF,IAG5R3M,OAAOkE,OAAO,CAAC,EAAG0I,IAAenB,EAAkB,CAAC,GAAmBc,GAASF,EAAOjF,EAAI,KAAO,GAAIqE,EAAgBa,GAASF,EAAOlF,EAAI,KAAO,GAAIuE,EAAgB1C,UAAY,GAAI0C,GAC9L,CA4CA,UACEnI,KAAM,gBACNC,SAAS,EACTC,MAAO,cACPC,GA9CF,SAAuBwJ,GACrB,IAAItJ,EAAQsJ,EAAMtJ,MACdc,EAAUwI,EAAMxI,QAChByI,EAAwBzI,EAAQoH,gBAChCA,OAA4C,IAA1BqB,GAA0CA,EAC5DC,EAAoB1I,EAAQqH,SAC5BA,OAAiC,IAAtBqB,GAAsCA,EACjDC,EAAwB3I,EAAQsH,aAChCA,OAAyC,IAA1BqB,GAA0CA,EACzDR,EAAe,CACjBlL,UAAWuD,EAAiBtB,EAAMjC,WAClCiK,UAAWL,EAAa3H,EAAMjC,WAC9BL,OAAQsC,EAAME,SAASxC,OACvBqK,WAAY/H,EAAMwG,MAAM9I,OACxBwK,gBAAiBA,EACjBG,QAAoC,UAA3BrI,EAAMc,QAAQC,UAGgB,MAArCf,EAAMmG,cAAcD,gBACtBlG,EAAMK,OAAO3C,OAASrB,OAAOkE,OAAO,CAAC,EAAGP,EAAMK,OAAO3C,OAAQmK,GAAYxL,OAAOkE,OAAO,CAAC,EAAG0I,EAAc,CACvGhB,QAASjI,EAAMmG,cAAcD,cAC7BrF,SAAUb,EAAMc,QAAQC,SACxBoH,SAAUA,EACVC,aAAcA,OAIe,MAA7BpI,EAAMmG,cAAcjF,QACtBlB,EAAMK,OAAOa,MAAQ7E,OAAOkE,OAAO,CAAC,EAAGP,EAAMK,OAAOa,MAAO2G,GAAYxL,OAAOkE,OAAO,CAAC,EAAG0I,EAAc,CACrGhB,QAASjI,EAAMmG,cAAcjF,MAC7BL,SAAU,WACVsH,UAAU,EACVC,aAAcA,OAIlBpI,EAAMM,WAAW5C,OAASrB,OAAOkE,OAAO,CAAC,EAAGP,EAAMM,WAAW5C,OAAQ,CACnE,wBAAyBsC,EAAMjC,WAEnC,EAQE2L,KAAM,CAAC,GCrKT,IAAIC,GAAU,CACZA,SAAS,GAsCX,UACEhK,KAAM,iBACNC,SAAS,EACTC,MAAO,QACPC,GAAI,WAAe,EACnBY,OAxCF,SAAgBX,GACd,IAAIC,EAAQD,EAAKC,MACb4J,EAAW7J,EAAK6J,SAChB9I,EAAUf,EAAKe,QACf+I,EAAkB/I,EAAQgJ,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7CE,EAAkBjJ,EAAQkJ,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7C9K,EAASF,EAAUiB,EAAME,SAASxC,QAClCuM,EAAgB,GAAGjM,OAAOgC,EAAMiK,cAActM,UAAWqC,EAAMiK,cAAcvM,QAYjF,OAVIoM,GACFG,EAAc9J,SAAQ,SAAU+J,GAC9BA,EAAaC,iBAAiB,SAAUP,EAASQ,OAAQT,GAC3D,IAGEK,GACF/K,EAAOkL,iBAAiB,SAAUP,EAASQ,OAAQT,IAG9C,WACDG,GACFG,EAAc9J,SAAQ,SAAU+J,GAC9BA,EAAaG,oBAAoB,SAAUT,EAASQ,OAAQT,GAC9D,IAGEK,GACF/K,EAAOoL,oBAAoB,SAAUT,EAASQ,OAAQT,GAE1D,CACF,EASED,KAAM,CAAC,GC/CT,IAAIY,GAAO,CACTnN,KAAM,QACND,MAAO,OACPD,OAAQ,MACR+D,IAAK,UAEQ,SAASuJ,GAAqBxM,GAC3C,OAAOA,EAAUyM,QAAQ,0BAA0B,SAAUC,GAC3D,OAAOH,GAAKG,EACd,GACF,CCVA,IAAI,GAAO,CACTnN,MAAO,MACPC,IAAK,SAEQ,SAASmN,GAA8B3M,GACpD,OAAOA,EAAUyM,QAAQ,cAAc,SAAUC,GAC/C,OAAO,GAAKA,EACd,GACF,CCPe,SAASE,GAAgB3L,GACtC,IAAI6J,EAAM9J,EAAUC,GAGpB,MAAO,CACL4L,WAHe/B,EAAIgC,YAInBC,UAHcjC,EAAIkC,YAKtB,CCNe,SAASC,GAAoBpM,GAQ1C,OAAO+D,EAAsB8B,EAAmB7F,IAAUzB,KAAOwN,GAAgB/L,GAASgM,UAC5F,CCXe,SAASK,GAAerM,GAErC,IAAIsM,EAAoB,EAAiBtM,GACrCuM,EAAWD,EAAkBC,SAC7BC,EAAYF,EAAkBE,UAC9BC,EAAYH,EAAkBG,UAElC,MAAO,6BAA6B3I,KAAKyI,EAAWE,EAAYD,EAClE,CCLe,SAASE,GAAgBtM,GACtC,MAAI,CAAC,OAAQ,OAAQ,aAAawF,QAAQ7F,EAAYK,KAAU,EAEvDA,EAAKG,cAAcoM,KAGxBhM,EAAcP,IAASiM,GAAejM,GACjCA,EAGFsM,GAAgB1G,EAAc5F,GACvC,CCJe,SAASwM,GAAkB5M,EAAS6M,GACjD,IAAIC,OAES,IAATD,IACFA,EAAO,IAGT,IAAIvB,EAAeoB,GAAgB1M,GAC/B+M,EAASzB,KAAqE,OAAlDwB,EAAwB9M,EAAQO,oBAAyB,EAASuM,EAAsBH,MACpH1C,EAAM9J,EAAUmL,GAChB0B,EAASD,EAAS,CAAC9C,GAAK7K,OAAO6K,EAAIxF,gBAAkB,GAAI4H,GAAef,GAAgBA,EAAe,IAAMA,EAC7G2B,EAAcJ,EAAKzN,OAAO4N,GAC9B,OAAOD,EAASE,EAChBA,EAAY7N,OAAOwN,GAAkB5G,EAAcgH,IACrD,CCzBe,SAASE,GAAiBC,GACvC,OAAO1P,OAAOkE,OAAO,CAAC,EAAGwL,EAAM,CAC7B5O,KAAM4O,EAAKxI,EACXvC,IAAK+K,EAAKtI,EACVvG,MAAO6O,EAAKxI,EAAIwI,EAAK7I,MACrBjG,OAAQ8O,EAAKtI,EAAIsI,EAAK3I,QAE1B,CCqBA,SAAS4I,GAA2BpN,EAASqN,EAAgBlL,GAC3D,OAAOkL,IAAmBxO,EAAWqO,GCzBxB,SAAyBlN,EAASmC,GAC/C,IAAI8H,EAAM9J,EAAUH,GAChBsN,EAAOzH,EAAmB7F,GAC1ByE,EAAiBwF,EAAIxF,eACrBH,EAAQgJ,EAAKhF,YACb9D,EAAS8I,EAAKjF,aACd1D,EAAI,EACJE,EAAI,EAER,GAAIJ,EAAgB,CAClBH,EAAQG,EAAeH,MACvBE,EAASC,EAAeD,OACxB,IAAI+I,EAAiB1J,KAEjB0J,IAAmBA,GAA+B,UAAbpL,KACvCwC,EAAIF,EAAeG,WACnBC,EAAIJ,EAAeK,UAEvB,CAEA,MAAO,CACLR,MAAOA,EACPE,OAAQA,EACRG,EAAGA,EAAIyH,GAAoBpM,GAC3B6E,EAAGA,EAEP,CDDwD2I,CAAgBxN,EAASmC,IAAa1B,EAAU4M,GAdxG,SAAoCrN,EAASmC,GAC3C,IAAIgL,EAAOpJ,EAAsB/D,GAAS,EAAoB,UAAbmC,GASjD,OARAgL,EAAK/K,IAAM+K,EAAK/K,IAAMpC,EAAQyN,UAC9BN,EAAK5O,KAAO4O,EAAK5O,KAAOyB,EAAQ0N,WAChCP,EAAK9O,OAAS8O,EAAK/K,IAAMpC,EAAQqI,aACjC8E,EAAK7O,MAAQ6O,EAAK5O,KAAOyB,EAAQsI,YACjC6E,EAAK7I,MAAQtE,EAAQsI,YACrB6E,EAAK3I,OAASxE,EAAQqI,aACtB8E,EAAKxI,EAAIwI,EAAK5O,KACd4O,EAAKtI,EAAIsI,EAAK/K,IACP+K,CACT,CAG0HQ,CAA2BN,EAAgBlL,GAAY+K,GEtBlK,SAAyBlN,GACtC,IAAI8M,EAEAQ,EAAOzH,EAAmB7F,GAC1B4N,EAAY7B,GAAgB/L,GAC5B2M,EAA0D,OAAlDG,EAAwB9M,EAAQO,oBAAyB,EAASuM,EAAsBH,KAChGrI,EAAQ,EAAIgJ,EAAKO,YAAaP,EAAKhF,YAAaqE,EAAOA,EAAKkB,YAAc,EAAGlB,EAAOA,EAAKrE,YAAc,GACvG9D,EAAS,EAAI8I,EAAKQ,aAAcR,EAAKjF,aAAcsE,EAAOA,EAAKmB,aAAe,EAAGnB,EAAOA,EAAKtE,aAAe,GAC5G1D,GAAKiJ,EAAU5B,WAAaI,GAAoBpM,GAChD6E,GAAK+I,EAAU1B,UAMnB,MAJiD,QAA7C,EAAiBS,GAAQW,GAAMS,YACjCpJ,GAAK,EAAI2I,EAAKhF,YAAaqE,EAAOA,EAAKrE,YAAc,GAAKhE,GAGrD,CACLA,MAAOA,EACPE,OAAQA,EACRG,EAAGA,EACHE,EAAGA,EAEP,CFCkMmJ,CAAgBnI,EAAmB7F,IACrO,CG1Be,SAASiO,GAAe9M,GACrC,IAOIkI,EAPAtK,EAAYoC,EAAKpC,UACjBiB,EAAUmB,EAAKnB,QACfb,EAAYgC,EAAKhC,UACjBqI,EAAgBrI,EAAYuD,EAAiBvD,GAAa,KAC1DiK,EAAYjK,EAAY4J,EAAa5J,GAAa,KAClD+O,EAAUnP,EAAU4F,EAAI5F,EAAUuF,MAAQ,EAAItE,EAAQsE,MAAQ,EAC9D6J,EAAUpP,EAAU8F,EAAI9F,EAAUyF,OAAS,EAAIxE,EAAQwE,OAAS,EAGpE,OAAQgD,GACN,KAAK,EACH6B,EAAU,CACR1E,EAAGuJ,EACHrJ,EAAG9F,EAAU8F,EAAI7E,EAAQwE,QAE3B,MAEF,KAAKnG,EACHgL,EAAU,CACR1E,EAAGuJ,EACHrJ,EAAG9F,EAAU8F,EAAI9F,EAAUyF,QAE7B,MAEF,KAAKlG,EACH+K,EAAU,CACR1E,EAAG5F,EAAU4F,EAAI5F,EAAUuF,MAC3BO,EAAGsJ,GAEL,MAEF,KAAK5P,EACH8K,EAAU,CACR1E,EAAG5F,EAAU4F,EAAI3E,EAAQsE,MACzBO,EAAGsJ,GAEL,MAEF,QACE9E,EAAU,CACR1E,EAAG5F,EAAU4F,EACbE,EAAG9F,EAAU8F,GAInB,IAAIuJ,EAAW5G,EAAgBV,EAAyBU,GAAiB,KAEzE,GAAgB,MAAZ4G,EAAkB,CACpB,IAAI1G,EAAmB,MAAb0G,EAAmB,SAAW,QAExC,OAAQhF,GACN,KAAK1K,EACH2K,EAAQ+E,GAAY/E,EAAQ+E,IAAarP,EAAU2I,GAAO,EAAI1H,EAAQ0H,GAAO,GAC7E,MAEF,KAAK/I,EACH0K,EAAQ+E,GAAY/E,EAAQ+E,IAAarP,EAAU2I,GAAO,EAAI1H,EAAQ0H,GAAO,GAKnF,CAEA,OAAO2B,CACT,CC3De,SAASgF,GAAejN,EAAOc,QAC5B,IAAZA,IACFA,EAAU,CAAC,GAGb,IAAIoM,EAAWpM,EACXqM,EAAqBD,EAASnP,UAC9BA,OAAmC,IAAvBoP,EAAgCnN,EAAMjC,UAAYoP,EAC9DC,EAAoBF,EAASnM,SAC7BA,OAAiC,IAAtBqM,EAA+BpN,EAAMe,SAAWqM,EAC3DC,EAAoBH,EAASI,SAC7BA,OAAiC,IAAtBD,EAA+B7P,EAAkB6P,EAC5DE,EAAwBL,EAASM,aACjCA,OAAyC,IAA1BD,EAAmC9P,EAAW8P,EAC7DE,EAAwBP,EAASQ,eACjCA,OAA2C,IAA1BD,EAAmC/P,EAAS+P,EAC7DE,EAAuBT,EAASU,YAChCA,OAAuC,IAAzBD,GAA0CA,EACxDE,EAAmBX,EAAS3G,QAC5BA,OAA+B,IAArBsH,EAA8B,EAAIA,EAC5ChI,EAAgBD,EAAsC,iBAAZW,EAAuBA,EAAUT,EAAgBS,EAASlJ,IACpGyQ,EAAaJ,IAAmBhQ,EAASC,EAAYD,EACrDqK,EAAa/H,EAAMwG,MAAM9I,OACzBkB,EAAUoB,EAAME,SAAS0N,EAAcE,EAAaJ,GACpDK,EJkBS,SAAyBnP,EAAS0O,EAAUE,EAAczM,GACvE,IAAIiN,EAAmC,oBAAbV,EAlB5B,SAA4B1O,GAC1B,IAAIpB,EAAkBgO,GAAkB5G,EAAchG,IAElDqP,EADoB,CAAC,WAAY,SAASzJ,QAAQ,EAAiB5F,GAASiC,WAAa,GACnDtB,EAAcX,GAAWoG,EAAgBpG,GAAWA,EAE9F,OAAKS,EAAU4O,GAKRzQ,EAAgBgI,QAAO,SAAUyG,GACtC,OAAO5M,EAAU4M,IAAmBpI,EAASoI,EAAgBgC,IAAmD,SAAhCtP,EAAYsN,EAC9F,IANS,EAOX,CAK6DiC,CAAmBtP,GAAW,GAAGZ,OAAOsP,GAC/F9P,EAAkB,GAAGQ,OAAOgQ,EAAqB,CAACR,IAClDW,EAAsB3Q,EAAgB,GACtC4Q,EAAe5Q,EAAgBK,QAAO,SAAUwQ,EAASpC,GAC3D,IAAIF,EAAOC,GAA2BpN,EAASqN,EAAgBlL,GAK/D,OAJAsN,EAAQrN,IAAM,EAAI+K,EAAK/K,IAAKqN,EAAQrN,KACpCqN,EAAQnR,MAAQ,EAAI6O,EAAK7O,MAAOmR,EAAQnR,OACxCmR,EAAQpR,OAAS,EAAI8O,EAAK9O,OAAQoR,EAAQpR,QAC1CoR,EAAQlR,KAAO,EAAI4O,EAAK5O,KAAMkR,EAAQlR,MAC/BkR,CACT,GAAGrC,GAA2BpN,EAASuP,EAAqBpN,IAK5D,OAJAqN,EAAalL,MAAQkL,EAAalR,MAAQkR,EAAajR,KACvDiR,EAAahL,OAASgL,EAAanR,OAASmR,EAAapN,IACzDoN,EAAa7K,EAAI6K,EAAajR,KAC9BiR,EAAa3K,EAAI2K,EAAapN,IACvBoN,CACT,CInC2BE,CAAgBjP,EAAUT,GAAWA,EAAUA,EAAQ2P,gBAAkB9J,EAAmBzE,EAAME,SAASxC,QAAS4P,EAAUE,EAAczM,GACjKyN,EAAsB7L,EAAsB3C,EAAME,SAASvC,WAC3DuI,EAAgB2G,GAAe,CACjClP,UAAW6Q,EACX5P,QAASmJ,EACThH,SAAU,WACVhD,UAAWA,IAET0Q,EAAmB3C,GAAiBzP,OAAOkE,OAAO,CAAC,EAAGwH,EAAY7B,IAClEwI,EAAoBhB,IAAmBhQ,EAAS+Q,EAAmBD,EAGnEG,EAAkB,CACpB3N,IAAK+M,EAAmB/M,IAAM0N,EAAkB1N,IAAM6E,EAAc7E,IACpE/D,OAAQyR,EAAkBzR,OAAS8Q,EAAmB9Q,OAAS4I,EAAc5I,OAC7EE,KAAM4Q,EAAmB5Q,KAAOuR,EAAkBvR,KAAO0I,EAAc1I,KACvED,MAAOwR,EAAkBxR,MAAQ6Q,EAAmB7Q,MAAQ2I,EAAc3I,OAExE0R,EAAa5O,EAAMmG,cAAckB,OAErC,GAAIqG,IAAmBhQ,GAAUkR,EAAY,CAC3C,IAAIvH,EAASuH,EAAW7Q,GACxB1B,OAAO4D,KAAK0O,GAAiBxO,SAAQ,SAAUhE,GAC7C,IAAI0S,EAAW,CAAC3R,EAAOD,GAAQuH,QAAQrI,IAAQ,EAAI,GAAK,EACpDkK,EAAO,CAAC,EAAKpJ,GAAQuH,QAAQrI,IAAQ,EAAI,IAAM,IACnDwS,EAAgBxS,IAAQkL,EAAOhB,GAAQwI,CACzC,GACF,CAEA,OAAOF,CACT,CCyEA,UACEhP,KAAM,OACNC,SAAS,EACTC,MAAO,OACPC,GA5HF,SAAcC,GACZ,IAAIC,EAAQD,EAAKC,MACbc,EAAUf,EAAKe,QACfnB,EAAOI,EAAKJ,KAEhB,IAAIK,EAAMmG,cAAcxG,GAAMmP,MAA9B,CAoCA,IAhCA,IAAIC,EAAoBjO,EAAQkM,SAC5BgC,OAAsC,IAAtBD,GAAsCA,EACtDE,EAAmBnO,EAAQoO,QAC3BC,OAAoC,IAArBF,GAAqCA,EACpDG,EAA8BtO,EAAQuO,mBACtC9I,EAAUzF,EAAQyF,QAClB+G,EAAWxM,EAAQwM,SACnBE,EAAe1M,EAAQ0M,aACvBI,EAAc9M,EAAQ8M,YACtB0B,EAAwBxO,EAAQyO,eAChCA,OAA2C,IAA1BD,GAA0CA,EAC3DE,EAAwB1O,EAAQ0O,sBAChCC,EAAqBzP,EAAMc,QAAQ/C,UACnCqI,EAAgB9E,EAAiBmO,GAEjCJ,EAAqBD,IADHhJ,IAAkBqJ,GACqCF,EAjC/E,SAAuCxR,GACrC,GAAIuD,EAAiBvD,KAAeX,EAClC,MAAO,GAGT,IAAIsS,EAAoBnF,GAAqBxM,GAC7C,MAAO,CAAC2M,GAA8B3M,GAAY2R,EAAmBhF,GAA8BgF,GACrG,CA0B6IC,CAA8BF,GAA3E,CAAClF,GAAqBkF,KAChHG,EAAa,CAACH,GAAoBzR,OAAOqR,GAAoBxR,QAAO,SAAUC,EAAKC,GACrF,OAAOD,EAAIE,OAAOsD,EAAiBvD,KAAeX,ECvCvC,SAA8B4C,EAAOc,QAClC,IAAZA,IACFA,EAAU,CAAC,GAGb,IAAIoM,EAAWpM,EACX/C,EAAYmP,EAASnP,UACrBuP,EAAWJ,EAASI,SACpBE,EAAeN,EAASM,aACxBjH,EAAU2G,EAAS3G,QACnBgJ,EAAiBrC,EAASqC,eAC1BM,EAAwB3C,EAASsC,sBACjCA,OAAkD,IAA1BK,EAAmC,EAAgBA,EAC3E7H,EAAYL,EAAa5J,GACzB6R,EAAa5H,EAAYuH,EAAiB3R,EAAsBA,EAAoB4H,QAAO,SAAUzH,GACvG,OAAO4J,EAAa5J,KAAeiK,CACrC,IAAK3K,EACDyS,EAAoBF,EAAWpK,QAAO,SAAUzH,GAClD,OAAOyR,EAAsBhL,QAAQzG,IAAc,CACrD,IAEiC,IAA7B+R,EAAkBC,SACpBD,EAAoBF,GAItB,IAAII,EAAYF,EAAkBjS,QAAO,SAAUC,EAAKC,GAOtD,OANAD,EAAIC,GAAakP,GAAejN,EAAO,CACrCjC,UAAWA,EACXuP,SAAUA,EACVE,aAAcA,EACdjH,QAASA,IACRjF,EAAiBvD,IACbD,CACT,GAAG,CAAC,GACJ,OAAOzB,OAAO4D,KAAK+P,GAAWC,MAAK,SAAUC,EAAGC,GAC9C,OAAOH,EAAUE,GAAKF,EAAUG,EAClC,GACF,CDC6DC,CAAqBpQ,EAAO,CACnFjC,UAAWA,EACXuP,SAAUA,EACVE,aAAcA,EACdjH,QAASA,EACTgJ,eAAgBA,EAChBC,sBAAuBA,IACpBzR,EACP,GAAG,IACCsS,EAAgBrQ,EAAMwG,MAAM7I,UAC5BoK,EAAa/H,EAAMwG,MAAM9I,OACzB4S,EAAY,IAAIC,IAChBC,GAAqB,EACrBC,EAAwBb,EAAW,GAE9Bc,EAAI,EAAGA,EAAId,EAAWG,OAAQW,IAAK,CAC1C,IAAI3S,EAAY6R,EAAWc,GAEvBC,EAAiBrP,EAAiBvD,GAElC6S,EAAmBjJ,EAAa5J,KAAeT,EAC/CuT,EAAa,CAAC,EAAK5T,GAAQuH,QAAQmM,IAAmB,EACtDrK,EAAMuK,EAAa,QAAU,SAC7B1F,EAAW8B,GAAejN,EAAO,CACnCjC,UAAWA,EACXuP,SAAUA,EACVE,aAAcA,EACdI,YAAaA,EACbrH,QAASA,IAEPuK,EAAoBD,EAAaD,EAAmB1T,EAAQC,EAAOyT,EAAmB3T,EAAS,EAE/FoT,EAAc/J,GAAOyB,EAAWzB,KAClCwK,EAAoBvG,GAAqBuG,IAG3C,IAAIC,EAAmBxG,GAAqBuG,GACxCE,EAAS,GAUb,GARIhC,GACFgC,EAAOC,KAAK9F,EAASwF,IAAmB,GAGtCxB,GACF6B,EAAOC,KAAK9F,EAAS2F,IAAsB,EAAG3F,EAAS4F,IAAqB,GAG1EC,EAAOE,OAAM,SAAUC,GACzB,OAAOA,CACT,IAAI,CACFV,EAAwB1S,EACxByS,GAAqB,EACrB,KACF,CAEAF,EAAUc,IAAIrT,EAAWiT,EAC3B,CAEA,GAAIR,EAqBF,IAnBA,IAEIa,EAAQ,SAAeC,GACzB,IAAIC,EAAmB3B,EAAW4B,MAAK,SAAUzT,GAC/C,IAAIiT,EAASV,EAAU9T,IAAIuB,GAE3B,GAAIiT,EACF,OAAOA,EAAOS,MAAM,EAAGH,GAAIJ,OAAM,SAAUC,GACzC,OAAOA,CACT,GAEJ,IAEA,GAAII,EAEF,OADAd,EAAwBc,EACjB,OAEX,EAESD,EAnBY/B,EAAiB,EAAI,EAmBZ+B,EAAK,GAGpB,UAFFD,EAAMC,GADmBA,KAOpCtR,EAAMjC,YAAc0S,IACtBzQ,EAAMmG,cAAcxG,GAAMmP,OAAQ,EAClC9O,EAAMjC,UAAY0S,EAClBzQ,EAAM0R,OAAQ,EA5GhB,CA8GF,EAQEhK,iBAAkB,CAAC,UACnBgC,KAAM,CACJoF,OAAO,IE7IX,SAAS6C,GAAexG,EAAUY,EAAM6F,GAQtC,YAPyB,IAArBA,IACFA,EAAmB,CACjBrO,EAAG,EACHE,EAAG,IAIA,CACLzC,IAAKmK,EAASnK,IAAM+K,EAAK3I,OAASwO,EAAiBnO,EACnDvG,MAAOiO,EAASjO,MAAQ6O,EAAK7I,MAAQ0O,EAAiBrO,EACtDtG,OAAQkO,EAASlO,OAAS8O,EAAK3I,OAASwO,EAAiBnO,EACzDtG,KAAMgO,EAAShO,KAAO4O,EAAK7I,MAAQ0O,EAAiBrO,EAExD,CAEA,SAASsO,GAAsB1G,GAC7B,MAAO,CAAC,EAAKjO,EAAOD,EAAQE,GAAM2U,MAAK,SAAUC,GAC/C,OAAO5G,EAAS4G,IAAS,CAC3B,GACF,CA+BA,UACEpS,KAAM,OACNC,SAAS,EACTC,MAAO,OACP6H,iBAAkB,CAAC,mBACnB5H,GAlCF,SAAcC,GACZ,IAAIC,EAAQD,EAAKC,MACbL,EAAOI,EAAKJ,KACZ0Q,EAAgBrQ,EAAMwG,MAAM7I,UAC5BoK,EAAa/H,EAAMwG,MAAM9I,OACzBkU,EAAmB5R,EAAMmG,cAAc6L,gBACvCC,EAAoBhF,GAAejN,EAAO,CAC5C0N,eAAgB,cAEdwE,EAAoBjF,GAAejN,EAAO,CAC5C4N,aAAa,IAEXuE,EAA2BR,GAAeM,EAAmB5B,GAC7D+B,EAAsBT,GAAeO,EAAmBnK,EAAY6J,GACpES,EAAoBR,GAAsBM,GAC1CG,EAAmBT,GAAsBO,GAC7CpS,EAAMmG,cAAcxG,GAAQ,CAC1BwS,yBAA0BA,EAC1BC,oBAAqBA,EACrBC,kBAAmBA,EACnBC,iBAAkBA,GAEpBtS,EAAMM,WAAW5C,OAASrB,OAAOkE,OAAO,CAAC,EAAGP,EAAMM,WAAW5C,OAAQ,CACnE,+BAAgC2U,EAChC,sBAAuBC,GAE3B,GCJA,IACE3S,KAAM,SACNC,SAAS,EACTC,MAAO,OACPwB,SAAU,CAAC,iBACXvB,GA5BF,SAAgBa,GACd,IAAIX,EAAQW,EAAMX,MACdc,EAAUH,EAAMG,QAChBnB,EAAOgB,EAAMhB,KACb4S,EAAkBzR,EAAQuG,OAC1BA,OAA6B,IAApBkL,EAA6B,CAAC,EAAG,GAAKA,EAC/C7I,EAAO,EAAW7L,QAAO,SAAUC,EAAKC,GAE1C,OADAD,EAAIC,GA5BD,SAAiCA,EAAWyI,EAAOa,GACxD,IAAIjB,EAAgB9E,EAAiBvD,GACjCyU,EAAiB,CAACrV,EAAM,GAAKqH,QAAQ4B,IAAkB,GAAK,EAAI,EAEhErG,EAAyB,mBAAXsH,EAAwBA,EAAOhL,OAAOkE,OAAO,CAAC,EAAGiG,EAAO,CACxEzI,UAAWA,KACPsJ,EACFoL,EAAW1S,EAAK,GAChB2S,EAAW3S,EAAK,GAIpB,OAFA0S,EAAWA,GAAY,EACvBC,GAAYA,GAAY,GAAKF,EACtB,CAACrV,EAAMD,GAAOsH,QAAQ4B,IAAkB,EAAI,CACjD7C,EAAGmP,EACHjP,EAAGgP,GACD,CACFlP,EAAGkP,EACHhP,EAAGiP,EAEP,CASqBC,CAAwB5U,EAAWiC,EAAMwG,MAAOa,GAC1DvJ,CACT,GAAG,CAAC,GACA8U,EAAwBlJ,EAAK1J,EAAMjC,WACnCwF,EAAIqP,EAAsBrP,EAC1BE,EAAImP,EAAsBnP,EAEW,MAArCzD,EAAMmG,cAAcD,gBACtBlG,EAAMmG,cAAcD,cAAc3C,GAAKA,EACvCvD,EAAMmG,cAAcD,cAAczC,GAAKA,GAGzCzD,EAAMmG,cAAcxG,GAAQ+J,CAC9B,GC1BA,IACE/J,KAAM,gBACNC,SAAS,EACTC,MAAO,OACPC,GApBF,SAAuBC,GACrB,IAAIC,EAAQD,EAAKC,MACbL,EAAOI,EAAKJ,KAKhBK,EAAMmG,cAAcxG,GAAQkN,GAAe,CACzClP,UAAWqC,EAAMwG,MAAM7I,UACvBiB,QAASoB,EAAMwG,MAAM9I,OACrBqD,SAAU,WACVhD,UAAWiC,EAAMjC,WAErB,EAQE2L,KAAM,CAAC,GCgHT,IACE/J,KAAM,kBACNC,SAAS,EACTC,MAAO,OACPC,GA/HF,SAAyBC,GACvB,IAAIC,EAAQD,EAAKC,MACbc,EAAUf,EAAKe,QACfnB,EAAOI,EAAKJ,KACZoP,EAAoBjO,EAAQkM,SAC5BgC,OAAsC,IAAtBD,GAAsCA,EACtDE,EAAmBnO,EAAQoO,QAC3BC,OAAoC,IAArBF,GAAsCA,EACrD3B,EAAWxM,EAAQwM,SACnBE,EAAe1M,EAAQ0M,aACvBI,EAAc9M,EAAQ8M,YACtBrH,EAAUzF,EAAQyF,QAClBsM,EAAkB/R,EAAQgS,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7CE,EAAwBjS,EAAQkS,aAChCA,OAAyC,IAA1BD,EAAmC,EAAIA,EACtD5H,EAAW8B,GAAejN,EAAO,CACnCsN,SAAUA,EACVE,aAAcA,EACdjH,QAASA,EACTqH,YAAaA,IAEXxH,EAAgB9E,EAAiBtB,EAAMjC,WACvCiK,EAAYL,EAAa3H,EAAMjC,WAC/BkV,GAAmBjL,EACnBgF,EAAWtH,EAAyBU,GACpC8I,ECrCY,MDqCSlC,ECrCH,IAAM,IDsCxB9G,EAAgBlG,EAAMmG,cAAcD,cACpCmK,EAAgBrQ,EAAMwG,MAAM7I,UAC5BoK,EAAa/H,EAAMwG,MAAM9I,OACzBwV,EAA4C,mBAAjBF,EAA8BA,EAAa3W,OAAOkE,OAAO,CAAC,EAAGP,EAAMwG,MAAO,CACvGzI,UAAWiC,EAAMjC,aACbiV,EACFG,EAA2D,iBAAtBD,EAAiC,CACxElG,SAAUkG,EACVhE,QAASgE,GACP7W,OAAOkE,OAAO,CAChByM,SAAU,EACVkC,QAAS,GACRgE,GACCE,EAAsBpT,EAAMmG,cAAckB,OAASrH,EAAMmG,cAAckB,OAAOrH,EAAMjC,WAAa,KACjG2L,EAAO,CACTnG,EAAG,EACHE,EAAG,GAGL,GAAKyC,EAAL,CAIA,GAAI8I,EAAe,CACjB,IAAIqE,EAEAC,EAAwB,MAAbtG,EAAmB,EAAM7P,EACpCoW,EAAuB,MAAbvG,EAAmB/P,EAASC,EACtCoJ,EAAmB,MAAb0G,EAAmB,SAAW,QACpC3F,EAASnB,EAAc8G,GACvBtL,EAAM2F,EAAS8D,EAASmI,GACxB7R,EAAM4F,EAAS8D,EAASoI,GACxBC,EAAWV,GAAU/K,EAAWzB,GAAO,EAAI,EAC3CmN,EAASzL,IAAc1K,EAAQ+S,EAAc/J,GAAOyB,EAAWzB,GAC/DoN,EAAS1L,IAAc1K,GAASyK,EAAWzB,IAAQ+J,EAAc/J,GAGjEL,EAAejG,EAAME,SAASgB,MAC9BwF,EAAYoM,GAAU7M,EAAetC,EAAcsC,GAAgB,CACrE/C,MAAO,EACPE,OAAQ,GAENuQ,GAAqB3T,EAAMmG,cAAc,oBAAsBnG,EAAMmG,cAAc,oBAAoBI,QxBhFtG,CACLvF,IAAK,EACL9D,MAAO,EACPD,OAAQ,EACRE,KAAM,GwB6EFyW,GAAkBD,GAAmBL,GACrCO,GAAkBF,GAAmBJ,GAMrCO,GAAWnO,EAAO,EAAG0K,EAAc/J,GAAMI,EAAUJ,IACnDyN,GAAYd,EAAkB5C,EAAc/J,GAAO,EAAIkN,EAAWM,GAAWF,GAAkBT,EAA4BnG,SAAWyG,EAASK,GAAWF,GAAkBT,EAA4BnG,SACxMgH,GAAYf,GAAmB5C,EAAc/J,GAAO,EAAIkN,EAAWM,GAAWD,GAAkBV,EAA4BnG,SAAW0G,EAASI,GAAWD,GAAkBV,EAA4BnG,SACzMjG,GAAoB/G,EAAME,SAASgB,OAAS8D,EAAgBhF,EAAME,SAASgB,OAC3E+S,GAAelN,GAAiC,MAAbiG,EAAmBjG,GAAkBsF,WAAa,EAAItF,GAAkBuF,YAAc,EAAI,EAC7H4H,GAAwH,OAAjGb,EAA+C,MAAvBD,OAA8B,EAASA,EAAoBpG,IAAqBqG,EAAwB,EAEvJc,GAAY9M,EAAS2M,GAAYE,GACjCE,GAAkBzO,EAAOmN,EAAS,EAAQpR,EAF9B2F,EAAS0M,GAAYG,GAAsBD,IAEKvS,EAAK2F,EAAQyL,EAAS,EAAQrR,EAAK0S,IAAa1S,GAChHyE,EAAc8G,GAAYoH,GAC1B1K,EAAKsD,GAAYoH,GAAkB/M,CACrC,CAEA,GAAI8H,EAAc,CAChB,IAAIkF,GAEAC,GAAyB,MAAbtH,EAAmB,EAAM7P,EAErCoX,GAAwB,MAAbvH,EAAmB/P,EAASC,EAEvCsX,GAAUtO,EAAcgJ,GAExBuF,GAAmB,MAAZvF,EAAkB,SAAW,QAEpCwF,GAAOF,GAAUrJ,EAASmJ,IAE1BK,GAAOH,GAAUrJ,EAASoJ,IAE1BK,IAAuD,IAAxC,CAAC,EAAKzX,GAAMqH,QAAQ4B,GAEnCyO,GAAyH,OAAjGR,GAAgD,MAAvBjB,OAA8B,EAASA,EAAoBlE,IAAoBmF,GAAyB,EAEzJS,GAAaF,GAAeF,GAAOF,GAAUnE,EAAcoE,IAAQ1M,EAAW0M,IAAQI,GAAuB1B,EAA4BjE,QAEzI6F,GAAaH,GAAeJ,GAAUnE,EAAcoE,IAAQ1M,EAAW0M,IAAQI,GAAuB1B,EAA4BjE,QAAUyF,GAE5IK,GAAmBlC,GAAU8B,G1BzH9B,SAAwBlT,EAAK1E,EAAOyE,GACzC,IAAIwT,EAAItP,EAAOjE,EAAK1E,EAAOyE,GAC3B,OAAOwT,EAAIxT,EAAMA,EAAMwT,CACzB,C0BsHoDC,CAAeJ,GAAYN,GAASO,IAAcpP,EAAOmN,EAASgC,GAAaJ,GAAMF,GAAS1B,EAASiC,GAAaJ,IAEpKzO,EAAcgJ,GAAW8F,GACzBtL,EAAKwF,GAAW8F,GAAmBR,EACrC,CAEAxU,EAAMmG,cAAcxG,GAAQ+J,CAvE5B,CAwEF,EAQEhC,iBAAkB,CAAC,WE1HN,SAASyN,GAAiBC,EAAyBrQ,EAAcsD,QAC9D,IAAZA,IACFA,GAAU,GAGZ,ICnBoCrJ,ECJOJ,EFuBvCyW,EAA0B9V,EAAcwF,GACxCuQ,EAAuB/V,EAAcwF,IAf3C,SAAyBnG,GACvB,IAAImN,EAAOnN,EAAQ+D,wBACfI,EAASpB,EAAMoK,EAAK7I,OAAStE,EAAQqE,aAAe,EACpDD,EAASrB,EAAMoK,EAAK3I,QAAUxE,EAAQuE,cAAgB,EAC1D,OAAkB,IAAXJ,GAA2B,IAAXC,CACzB,CAU4DuS,CAAgBxQ,GACtEJ,EAAkBF,EAAmBM,GACrCgH,EAAOpJ,EAAsByS,EAAyBE,EAAsBjN,GAC5EyB,EAAS,CACXc,WAAY,EACZE,UAAW,GAET7C,EAAU,CACZ1E,EAAG,EACHE,EAAG,GAkBL,OAfI4R,IAA4BA,IAA4BhN,MACxB,SAA9B1J,EAAYoG,IAChBkG,GAAetG,MACbmF,GCnCgC9K,EDmCT+F,KClCdhG,EAAUC,IAAUO,EAAcP,GCJxC,CACL4L,YAFyChM,EDQbI,GCNR4L,WACpBE,UAAWlM,EAAQkM,WDGZH,GAAgB3L,IDoCnBO,EAAcwF,KAChBkD,EAAUtF,EAAsBoC,GAAc,IACtCxB,GAAKwB,EAAauH,WAC1BrE,EAAQxE,GAAKsB,EAAasH,WACjB1H,IACTsD,EAAQ1E,EAAIyH,GAAoBrG,KAI7B,CACLpB,EAAGwI,EAAK5O,KAAO2M,EAAOc,WAAa3C,EAAQ1E,EAC3CE,EAAGsI,EAAK/K,IAAM8I,EAAOgB,UAAY7C,EAAQxE,EACzCP,MAAO6I,EAAK7I,MACZE,OAAQ2I,EAAK3I,OAEjB,CGvDA,SAASoS,GAAMC,GACb,IAAItT,EAAM,IAAIoO,IACVmF,EAAU,IAAIC,IACdC,EAAS,GAKb,SAAS3F,EAAK4F,GACZH,EAAQI,IAAID,EAASlW,MACN,GAAG3B,OAAO6X,EAASxU,UAAY,GAAIwU,EAASnO,kBAAoB,IACtEvH,SAAQ,SAAU4V,GACzB,IAAKL,EAAQM,IAAID,GAAM,CACrB,IAAIE,EAAc9T,EAAI3F,IAAIuZ,GAEtBE,GACFhG,EAAKgG,EAET,CACF,IACAL,EAAO3E,KAAK4E,EACd,CAQA,OAzBAJ,EAAUtV,SAAQ,SAAU0V,GAC1B1T,EAAIiP,IAAIyE,EAASlW,KAAMkW,EACzB,IAiBAJ,EAAUtV,SAAQ,SAAU0V,GACrBH,EAAQM,IAAIH,EAASlW,OAExBsQ,EAAK4F,EAET,IACOD,CACT,CCvBA,IAAIM,GAAkB,CACpBnY,UAAW,SACX0X,UAAW,GACX1U,SAAU,YAGZ,SAASoV,KACP,IAAK,IAAI1B,EAAO2B,UAAUrG,OAAQsG,EAAO,IAAIpU,MAAMwS,GAAO6B,EAAO,EAAGA,EAAO7B,EAAM6B,IAC/ED,EAAKC,GAAQF,UAAUE,GAGzB,OAAQD,EAAKvE,MAAK,SAAUlT,GAC1B,QAASA,GAAoD,mBAAlCA,EAAQ+D,sBACrC,GACF,CAEO,SAAS4T,GAAgBC,QACL,IAArBA,IACFA,EAAmB,CAAC,GAGtB,IAAIC,EAAoBD,EACpBE,EAAwBD,EAAkBE,iBAC1CA,OAA6C,IAA1BD,EAAmC,GAAKA,EAC3DE,EAAyBH,EAAkBI,eAC3CA,OAA4C,IAA3BD,EAAoCV,GAAkBU,EAC3E,OAAO,SAAsBjZ,EAAWD,EAAQoD,QAC9B,IAAZA,IACFA,EAAU+V,GAGZ,ICxC6B/W,EAC3BgX,EDuCE9W,EAAQ,CACVjC,UAAW,SACXgZ,iBAAkB,GAClBjW,QAASzE,OAAOkE,OAAO,CAAC,EAAG2V,GAAiBW,GAC5C1Q,cAAe,CAAC,EAChBjG,SAAU,CACRvC,UAAWA,EACXD,OAAQA,GAEV4C,WAAY,CAAC,EACbD,OAAQ,CAAC,GAEP2W,EAAmB,GACnBC,GAAc,EACdrN,EAAW,CACb5J,MAAOA,EACPkX,WAAY,SAAoBC,GAC9B,IAAIrW,EAAsC,mBAArBqW,EAAkCA,EAAiBnX,EAAMc,SAAWqW,EACzFC,IACApX,EAAMc,QAAUzE,OAAOkE,OAAO,CAAC,EAAGsW,EAAgB7W,EAAMc,QAASA,GACjEd,EAAMiK,cAAgB,CACpBtM,UAAW0B,EAAU1B,GAAa6N,GAAkB7N,GAAaA,EAAU4Q,eAAiB/C,GAAkB7N,EAAU4Q,gBAAkB,GAC1I7Q,OAAQ8N,GAAkB9N,IAI5B,IElE4B+X,EAC9B4B,EFiEMN,EDhCG,SAAwBtB,GAErC,IAAIsB,EAAmBvB,GAAMC,GAE7B,OAAO/W,EAAeb,QAAO,SAAUC,EAAK+B,GAC1C,OAAO/B,EAAIE,OAAO+Y,EAAiBvR,QAAO,SAAUqQ,GAClD,OAAOA,EAAShW,QAAUA,CAC5B,IACF,GAAG,GACL,CCuB+ByX,EElEK7B,EFkEsB,GAAGzX,OAAO2Y,EAAkB3W,EAAMc,QAAQ2U,WEjE9F4B,EAAS5B,EAAU5X,QAAO,SAAUwZ,EAAQE,GAC9C,IAAIC,EAAWH,EAAOE,EAAQ5X,MAK9B,OAJA0X,EAAOE,EAAQ5X,MAAQ6X,EAAWnb,OAAOkE,OAAO,CAAC,EAAGiX,EAAUD,EAAS,CACrEzW,QAASzE,OAAOkE,OAAO,CAAC,EAAGiX,EAAS1W,QAASyW,EAAQzW,SACrD4I,KAAMrN,OAAOkE,OAAO,CAAC,EAAGiX,EAAS9N,KAAM6N,EAAQ7N,QAC5C6N,EACEF,CACT,GAAG,CAAC,GAEGhb,OAAO4D,KAAKoX,GAAQlV,KAAI,SAAUhG,GACvC,OAAOkb,EAAOlb,EAChB,MF4DM,OAJA6D,EAAM+W,iBAAmBA,EAAiBvR,QAAO,SAAUiS,GACzD,OAAOA,EAAE7X,OACX,IA+FFI,EAAM+W,iBAAiB5W,SAAQ,SAAUJ,GACvC,IAAIJ,EAAOI,EAAKJ,KACZ+X,EAAe3X,EAAKe,QACpBA,OAA2B,IAAjB4W,EAA0B,CAAC,EAAIA,EACzChX,EAASX,EAAKW,OAElB,GAAsB,mBAAXA,EAAuB,CAChC,IAAIiX,EAAYjX,EAAO,CACrBV,MAAOA,EACPL,KAAMA,EACNiK,SAAUA,EACV9I,QAASA,IAKXkW,EAAiB/F,KAAK0G,GAFT,WAAmB,EAGlC,CACF,IA/GS/N,EAASQ,QAClB,EAMAwN,YAAa,WACX,IAAIX,EAAJ,CAIA,IAAIY,EAAkB7X,EAAME,SACxBvC,EAAYka,EAAgBla,UAC5BD,EAASma,EAAgBna,OAG7B,GAAKyY,GAAiBxY,EAAWD,GAAjC,CAKAsC,EAAMwG,MAAQ,CACZ7I,UAAWwX,GAAiBxX,EAAWqH,EAAgBtH,GAAoC,UAA3BsC,EAAMc,QAAQC,UAC9ErD,OAAQiG,EAAcjG,IAOxBsC,EAAM0R,OAAQ,EACd1R,EAAMjC,UAAYiC,EAAMc,QAAQ/C,UAKhCiC,EAAM+W,iBAAiB5W,SAAQ,SAAU0V,GACvC,OAAO7V,EAAMmG,cAAc0P,EAASlW,MAAQtD,OAAOkE,OAAO,CAAC,EAAGsV,EAASnM,KACzE,IAEA,IAAK,IAAIoO,EAAQ,EAAGA,EAAQ9X,EAAM+W,iBAAiBhH,OAAQ+H,IACzD,IAAoB,IAAhB9X,EAAM0R,MAAV,CAMA,IAAIqG,EAAwB/X,EAAM+W,iBAAiBe,GAC/ChY,EAAKiY,EAAsBjY,GAC3BkY,EAAyBD,EAAsBjX,QAC/CoM,OAAsC,IAA3B8K,EAAoC,CAAC,EAAIA,EACpDrY,EAAOoY,EAAsBpY,KAEf,mBAAPG,IACTE,EAAQF,EAAG,CACTE,MAAOA,EACPc,QAASoM,EACTvN,KAAMA,EACNiK,SAAUA,KACN5J,EAdR,MAHEA,EAAM0R,OAAQ,EACdoG,GAAS,CAzBb,CATA,CAqDF,EAGA1N,QC1I2BtK,ED0IV,WACf,OAAO,IAAImY,SAAQ,SAAUC,GAC3BtO,EAASgO,cACTM,EAAQlY,EACV,GACF,EC7IG,WAUL,OATK8W,IACHA,EAAU,IAAImB,SAAQ,SAAUC,GAC9BD,QAAQC,UAAUC,MAAK,WACrBrB,OAAUsB,EACVF,EAAQpY,IACV,GACF,KAGKgX,CACT,GDmIIuB,QAAS,WACPjB,IACAH,GAAc,CAChB,GAGF,IAAKd,GAAiBxY,EAAWD,GAC/B,OAAOkM,EAmCT,SAASwN,IACPJ,EAAiB7W,SAAQ,SAAUL,GACjC,OAAOA,GACT,IACAkX,EAAmB,EACrB,CAEA,OAvCApN,EAASsN,WAAWpW,GAASqX,MAAK,SAAUnY,IACrCiX,GAAenW,EAAQwX,eAC1BxX,EAAQwX,cAActY,EAE1B,IAmCO4J,CACT,CACF,CACO,IAAI2O,GAA4BhC,KGzLnC,GAA4BA,GAAgB,CAC9CI,iBAFqB,CAAC6B,GAAgB,GAAe,GAAe,EAAa,GAAQ,GAAM,GAAiB,EAAO,MCJrH,GAA4BjC,GAAgB,CAC9CI,iBAFqB,CAAC6B,GAAgB,GAAe,GAAe,KCatE,MAAMC,GAAa,IAAIlI,IACjBmI,GAAO,CACX,GAAAtH,CAAIxS,EAASzC,EAAKyN,GACX6O,GAAWzC,IAAIpX,IAClB6Z,GAAWrH,IAAIxS,EAAS,IAAI2R,KAE9B,MAAMoI,EAAcF,GAAWjc,IAAIoC,GAI9B+Z,EAAY3C,IAAI7Z,IAA6B,IAArBwc,EAAYC,KAKzCD,EAAYvH,IAAIjV,EAAKyN,GAHnBiP,QAAQC,MAAM,+EAA+E7W,MAAM8W,KAAKJ,EAAY1Y,QAAQ,MAIhI,EACAzD,IAAG,CAACoC,EAASzC,IACPsc,GAAWzC,IAAIpX,IACV6Z,GAAWjc,IAAIoC,GAASpC,IAAIL,IAE9B,KAET,MAAA6c,CAAOpa,EAASzC,GACd,IAAKsc,GAAWzC,IAAIpX,GAClB,OAEF,MAAM+Z,EAAcF,GAAWjc,IAAIoC,GACnC+Z,EAAYM,OAAO9c,GAGM,IAArBwc,EAAYC,MACdH,GAAWQ,OAAOra,EAEtB,GAYIsa,GAAiB,gBAOjBC,GAAgBC,IAChBA,GAAYna,OAAOoa,KAAOpa,OAAOoa,IAAIC,SAEvCF,EAAWA,EAAS5O,QAAQ,iBAAiB,CAAC+O,EAAOC,IAAO,IAAIH,IAAIC,OAAOE,QAEtEJ,GA4CHK,GAAuB7a,IAC3BA,EAAQ8a,cAAc,IAAIC,MAAMT,IAAgB,EAE5C,GAAYU,MACXA,GAA4B,iBAAXA,UAGO,IAAlBA,EAAOC,SAChBD,EAASA,EAAO,SAEgB,IAApBA,EAAOE,UAEjBC,GAAaH,GAEb,GAAUA,GACLA,EAAOC,OAASD,EAAO,GAAKA,EAEf,iBAAXA,GAAuBA,EAAO7J,OAAS,EACzCrL,SAAS+C,cAAc0R,GAAcS,IAEvC,KAEHI,GAAYpb,IAChB,IAAK,GAAUA,IAAgD,IAApCA,EAAQqb,iBAAiBlK,OAClD,OAAO,EAET,MAAMmK,EAAgF,YAA7D5V,iBAAiB1F,GAASub,iBAAiB,cAE9DC,EAAgBxb,EAAQyb,QAAQ,uBACtC,IAAKD,EACH,OAAOF,EAET,GAAIE,IAAkBxb,EAAS,CAC7B,MAAM0b,EAAU1b,EAAQyb,QAAQ,WAChC,GAAIC,GAAWA,EAAQlW,aAAegW,EACpC,OAAO,EAET,GAAgB,OAAZE,EACF,OAAO,CAEX,CACA,OAAOJ,CAAgB,EAEnBK,GAAa3b,IACZA,GAAWA,EAAQkb,WAAaU,KAAKC,gBAGtC7b,EAAQ8b,UAAU7W,SAAS,mBAGC,IAArBjF,EAAQ+b,SACV/b,EAAQ+b,SAEV/b,EAAQgc,aAAa,aAAoD,UAArChc,EAAQic,aAAa,aAE5DC,GAAiBlc,IACrB,IAAK8F,SAASC,gBAAgBoW,aAC5B,OAAO,KAIT,GAAmC,mBAAxBnc,EAAQqF,YAA4B,CAC7C,MAAM+W,EAAOpc,EAAQqF,cACrB,OAAO+W,aAAgBtb,WAAasb,EAAO,IAC7C,CACA,OAAIpc,aAAmBc,WACdd,EAIJA,EAAQwF,WAGN0W,GAAelc,EAAQwF,YAFrB,IAEgC,EAErC6W,GAAO,OAUPC,GAAStc,IACbA,EAAQuE,YAAY,EAGhBgY,GAAY,IACZlc,OAAOmc,SAAW1W,SAAS6G,KAAKqP,aAAa,qBACxC3b,OAAOmc,OAET,KAEHC,GAA4B,GAgB5BC,GAAQ,IAAuC,QAAjC5W,SAASC,gBAAgB4W,IACvCC,GAAqBC,IAhBAC,QAiBN,KACjB,MAAMC,EAAIR,KAEV,GAAIQ,EAAG,CACL,MAAMhc,EAAO8b,EAAOG,KACdC,EAAqBF,EAAE7b,GAAGH,GAChCgc,EAAE7b,GAAGH,GAAQ8b,EAAOK,gBACpBH,EAAE7b,GAAGH,GAAMoc,YAAcN,EACzBE,EAAE7b,GAAGH,GAAMqc,WAAa,KACtBL,EAAE7b,GAAGH,GAAQkc,EACNJ,EAAOK,gBAElB,GA5B0B,YAAxBpX,SAASuX,YAENZ,GAA0BtL,QAC7BrL,SAASyF,iBAAiB,oBAAoB,KAC5C,IAAK,MAAMuR,KAAYL,GACrBK,GACF,IAGJL,GAA0BpK,KAAKyK,IAE/BA,GAkBA,EAEEQ,GAAU,CAACC,EAAkB9F,EAAO,GAAI+F,EAAeD,IACxB,mBAArBA,EAAkCA,KAAoB9F,GAAQ+F,EAExEC,GAAyB,CAACX,EAAUY,EAAmBC,GAAoB,KAC/E,IAAKA,EAEH,YADAL,GAAQR,GAGV,MACMc,EAhKiC5d,KACvC,IAAKA,EACH,OAAO,EAIT,IAAI,mBACF6d,EAAkB,gBAClBC,GACEzd,OAAOqF,iBAAiB1F,GAC5B,MAAM+d,EAA0BC,OAAOC,WAAWJ,GAC5CK,EAAuBF,OAAOC,WAAWH,GAG/C,OAAKC,GAA4BG,GAKjCL,EAAqBA,EAAmBlb,MAAM,KAAK,GACnDmb,EAAkBA,EAAgBnb,MAAM,KAAK,GAtDf,KAuDtBqb,OAAOC,WAAWJ,GAAsBG,OAAOC,WAAWH,KANzD,CAMoG,EA2IpFK,CAAiCT,GADlC,EAExB,IAAIU,GAAS,EACb,MAAMC,EAAU,EACdrR,aAEIA,IAAW0Q,IAGfU,GAAS,EACTV,EAAkBjS,oBAAoB6O,GAAgB+D,GACtDf,GAAQR,GAAS,EAEnBY,EAAkBnS,iBAAiB+O,GAAgB+D,GACnDC,YAAW,KACJF,GACHvD,GAAqB6C,EACvB,GACCE,EAAiB,EAYhBW,GAAuB,CAAC1R,EAAM2R,EAAeC,EAAeC,KAChE,MAAMC,EAAa9R,EAAKsE,OACxB,IAAI+H,EAAQrM,EAAKjH,QAAQ4Y,GAIzB,OAAe,IAAXtF,GACMuF,GAAiBC,EAAiB7R,EAAK8R,EAAa,GAAK9R,EAAK,IAExEqM,GAASuF,EAAgB,GAAK,EAC1BC,IACFxF,GAASA,EAAQyF,GAAcA,GAE1B9R,EAAKjK,KAAKC,IAAI,EAAGD,KAAKE,IAAIoW,EAAOyF,EAAa,KAAI,EAerDC,GAAiB,qBACjBC,GAAiB,OACjBC,GAAgB,SAChBC,GAAgB,CAAC,EACvB,IAAIC,GAAW,EACf,MAAMC,GAAe,CACnBC,WAAY,YACZC,WAAY,YAERC,GAAe,IAAIrI,IAAI,CAAC,QAAS,WAAY,UAAW,YAAa,cAAe,aAAc,iBAAkB,YAAa,WAAY,YAAa,cAAe,YAAa,UAAW,WAAY,QAAS,oBAAqB,aAAc,YAAa,WAAY,cAAe,cAAe,cAAe,YAAa,eAAgB,gBAAiB,eAAgB,gBAAiB,aAAc,QAAS,OAAQ,SAAU,QAAS,SAAU,SAAU,UAAW,WAAY,OAAQ,SAAU,eAAgB,SAAU,OAAQ,mBAAoB,mBAAoB,QAAS,QAAS,WAM/lB,SAASsI,GAAarf,EAASsf,GAC7B,OAAOA,GAAO,GAAGA,MAAQN,QAAgBhf,EAAQgf,UAAYA,IAC/D,CACA,SAASO,GAAiBvf,GACxB,MAAMsf,EAAMD,GAAarf,GAGzB,OAFAA,EAAQgf,SAAWM,EACnBP,GAAcO,GAAOP,GAAcO,IAAQ,CAAC,EACrCP,GAAcO,EACvB,CAiCA,SAASE,GAAYC,EAAQC,EAAUC,EAAqB,MAC1D,OAAOliB,OAAOmiB,OAAOH,GAAQ7M,MAAKiN,GAASA,EAAMH,WAAaA,GAAYG,EAAMF,qBAAuBA,GACzG,CACA,SAASG,GAAoBC,EAAmB1B,EAAS2B,GACvD,MAAMC,EAAiC,iBAAZ5B,EAErBqB,EAAWO,EAAcD,EAAqB3B,GAAW2B,EAC/D,IAAIE,EAAYC,GAAaJ,GAI7B,OAHKX,GAAahI,IAAI8I,KACpBA,EAAYH,GAEP,CAACE,EAAaP,EAAUQ,EACjC,CACA,SAASE,GAAWpgB,EAAS+f,EAAmB1B,EAAS2B,EAAoBK,GAC3E,GAAiC,iBAAtBN,IAAmC/f,EAC5C,OAEF,IAAKigB,EAAaP,EAAUQ,GAAaJ,GAAoBC,EAAmB1B,EAAS2B,GAIzF,GAAID,KAAqBd,GAAc,CACrC,MAAMqB,EAAepf,GACZ,SAAU2e,GACf,IAAKA,EAAMU,eAAiBV,EAAMU,gBAAkBV,EAAMW,iBAAmBX,EAAMW,eAAevb,SAAS4a,EAAMU,eAC/G,OAAOrf,EAAGjD,KAAKwiB,KAAMZ,EAEzB,EAEFH,EAAWY,EAAaZ,EAC1B,CACA,MAAMD,EAASF,GAAiBvf,GAC1B0gB,EAAWjB,EAAOS,KAAeT,EAAOS,GAAa,CAAC,GACtDS,EAAmBnB,GAAYkB,EAAUhB,EAAUO,EAAc5B,EAAU,MACjF,GAAIsC,EAEF,YADAA,EAAiBN,OAASM,EAAiBN,QAAUA,GAGvD,MAAMf,EAAMD,GAAaK,EAAUK,EAAkBnU,QAAQgT,GAAgB,KACvE1d,EAAK+e,EA5Db,SAAoCjgB,EAASwa,EAAUtZ,GACrD,OAAO,SAASmd,EAAQwB,GACtB,MAAMe,EAAc5gB,EAAQ6gB,iBAAiBrG,GAC7C,IAAK,IAAI,OACPxN,GACE6S,EAAO7S,GAAUA,IAAWyT,KAAMzT,EAASA,EAAOxH,WACpD,IAAK,MAAMsb,KAAcF,EACvB,GAAIE,IAAe9T,EASnB,OANA+T,GAAWlB,EAAO,CAChBW,eAAgBxT,IAEdqR,EAAQgC,QACVW,GAAaC,IAAIjhB,EAAS6f,EAAMqB,KAAM1G,EAAUtZ,GAE3CA,EAAGigB,MAAMnU,EAAQ,CAAC6S,GAG/B,CACF,CAwC2BuB,CAA2BphB,EAASqe,EAASqB,GAvExE,SAA0B1f,EAASkB,GACjC,OAAO,SAASmd,EAAQwB,GAOtB,OANAkB,GAAWlB,EAAO,CAChBW,eAAgBxgB,IAEdqe,EAAQgC,QACVW,GAAaC,IAAIjhB,EAAS6f,EAAMqB,KAAMhgB,GAEjCA,EAAGigB,MAAMnhB,EAAS,CAAC6f,GAC5B,CACF,CA6DoFwB,CAAiBrhB,EAAS0f,GAC5Gxe,EAAGye,mBAAqBM,EAAc5B,EAAU,KAChDnd,EAAGwe,SAAWA,EACdxe,EAAGmf,OAASA,EACZnf,EAAG8d,SAAWM,EACdoB,EAASpB,GAAOpe,EAChBlB,EAAQuL,iBAAiB2U,EAAWhf,EAAI+e,EAC1C,CACA,SAASqB,GAActhB,EAASyf,EAAQS,EAAW7B,EAASsB,GAC1D,MAAMze,EAAKse,GAAYC,EAAOS,GAAY7B,EAASsB,GAC9Cze,IAGLlB,EAAQyL,oBAAoByU,EAAWhf,EAAIqgB,QAAQ5B,WAC5CF,EAAOS,GAAWhf,EAAG8d,UAC9B,CACA,SAASwC,GAAyBxhB,EAASyf,EAAQS,EAAWuB,GAC5D,MAAMC,EAAoBjC,EAAOS,IAAc,CAAC,EAChD,IAAK,MAAOyB,EAAY9B,KAAUpiB,OAAOmkB,QAAQF,GAC3CC,EAAWE,SAASJ,IACtBH,GAActhB,EAASyf,EAAQS,EAAWL,EAAMH,SAAUG,EAAMF,mBAGtE,CACA,SAASQ,GAAaN,GAGpB,OADAA,EAAQA,EAAMjU,QAAQiT,GAAgB,IAC/BI,GAAaY,IAAUA,CAChC,CACA,MAAMmB,GAAe,CACnB,EAAAc,CAAG9hB,EAAS6f,EAAOxB,EAAS2B,GAC1BI,GAAWpgB,EAAS6f,EAAOxB,EAAS2B,GAAoB,EAC1D,EACA,GAAA+B,CAAI/hB,EAAS6f,EAAOxB,EAAS2B,GAC3BI,GAAWpgB,EAAS6f,EAAOxB,EAAS2B,GAAoB,EAC1D,EACA,GAAAiB,CAAIjhB,EAAS+f,EAAmB1B,EAAS2B,GACvC,GAAiC,iBAAtBD,IAAmC/f,EAC5C,OAEF,MAAOigB,EAAaP,EAAUQ,GAAaJ,GAAoBC,EAAmB1B,EAAS2B,GACrFgC,EAAc9B,IAAcH,EAC5BN,EAASF,GAAiBvf,GAC1B0hB,EAAoBjC,EAAOS,IAAc,CAAC,EAC1C+B,EAAclC,EAAkBmC,WAAW,KACjD,QAAwB,IAAbxC,EAAX,CAQA,GAAIuC,EACF,IAAK,MAAME,KAAgB1kB,OAAO4D,KAAKoe,GACrC+B,GAAyBxhB,EAASyf,EAAQ0C,EAAcpC,EAAkBlN,MAAM,IAGpF,IAAK,MAAOuP,EAAavC,KAAUpiB,OAAOmkB,QAAQF,GAAoB,CACpE,MAAMC,EAAaS,EAAYxW,QAAQkT,GAAe,IACjDkD,IAAejC,EAAkB8B,SAASF,IAC7CL,GAActhB,EAASyf,EAAQS,EAAWL,EAAMH,SAAUG,EAAMF,mBAEpE,CAXA,KAPA,CAEE,IAAKliB,OAAO4D,KAAKqgB,GAAmBvQ,OAClC,OAEFmQ,GAActhB,EAASyf,EAAQS,EAAWR,EAAUO,EAAc5B,EAAU,KAE9E,CAYF,EACA,OAAAgE,CAAQriB,EAAS6f,EAAOpI,GACtB,GAAqB,iBAAVoI,IAAuB7f,EAChC,OAAO,KAET,MAAM+c,EAAIR,KAGV,IAAI+F,EAAc,KACdC,GAAU,EACVC,GAAiB,EACjBC,GAAmB,EAJH5C,IADFM,GAAaN,IAMZ9C,IACjBuF,EAAcvF,EAAEhC,MAAM8E,EAAOpI,GAC7BsF,EAAE/c,GAASqiB,QAAQC,GACnBC,GAAWD,EAAYI,uBACvBF,GAAkBF,EAAYK,gCAC9BF,EAAmBH,EAAYM,sBAEjC,MAAMC,EAAM9B,GAAW,IAAIhG,MAAM8E,EAAO,CACtC0C,UACAO,YAAY,IACVrL,GAUJ,OATIgL,GACFI,EAAIE,iBAEFP,GACFxiB,EAAQ8a,cAAc+H,GAEpBA,EAAIJ,kBAAoBH,GAC1BA,EAAYS,iBAEPF,CACT,GAEF,SAAS9B,GAAWljB,EAAKmlB,EAAO,CAAC,GAC/B,IAAK,MAAOzlB,EAAKa,KAAUX,OAAOmkB,QAAQoB,GACxC,IACEnlB,EAAIN,GAAOa,CACb,CAAE,MAAO6kB,GACPxlB,OAAOC,eAAeG,EAAKN,EAAK,CAC9B2lB,cAAc,EACdtlB,IAAG,IACMQ,GAGb,CAEF,OAAOP,CACT,CASA,SAASslB,GAAc/kB,GACrB,GAAc,SAAVA,EACF,OAAO,EAET,GAAc,UAAVA,EACF,OAAO,EAET,GAAIA,IAAU4f,OAAO5f,GAAOkC,WAC1B,OAAO0d,OAAO5f,GAEhB,GAAc,KAAVA,GAA0B,SAAVA,EAClB,OAAO,KAET,GAAqB,iBAAVA,EACT,OAAOA,EAET,IACE,OAAOglB,KAAKC,MAAMC,mBAAmBllB,GACvC,CAAE,MAAO6kB,GACP,OAAO7kB,CACT,CACF,CACA,SAASmlB,GAAiBhmB,GACxB,OAAOA,EAAIqO,QAAQ,UAAU4X,GAAO,IAAIA,EAAItjB,iBAC9C,CACA,MAAMujB,GAAc,CAClB,gBAAAC,CAAiB1jB,EAASzC,EAAKa,GAC7B4B,EAAQ6B,aAAa,WAAW0hB,GAAiBhmB,KAAQa,EAC3D,EACA,mBAAAulB,CAAoB3jB,EAASzC,GAC3ByC,EAAQ4B,gBAAgB,WAAW2hB,GAAiBhmB,KACtD,EACA,iBAAAqmB,CAAkB5jB,GAChB,IAAKA,EACH,MAAO,CAAC,EAEV,MAAM0B,EAAa,CAAC,EACdmiB,EAASpmB,OAAO4D,KAAKrB,EAAQ8jB,SAASld,QAAOrJ,GAAOA,EAAI2kB,WAAW,QAAU3kB,EAAI2kB,WAAW,cAClG,IAAK,MAAM3kB,KAAOsmB,EAAQ,CACxB,IAAIE,EAAUxmB,EAAIqO,QAAQ,MAAO,IACjCmY,EAAUA,EAAQC,OAAO,GAAG9jB,cAAgB6jB,EAAQlR,MAAM,EAAGkR,EAAQ5S,QACrEzP,EAAWqiB,GAAWZ,GAAcnjB,EAAQ8jB,QAAQvmB,GACtD,CACA,OAAOmE,CACT,EACAuiB,iBAAgB,CAACjkB,EAASzC,IACjB4lB,GAAcnjB,EAAQic,aAAa,WAAWsH,GAAiBhmB,QAgB1E,MAAM2mB,GAEJ,kBAAWC,GACT,MAAO,CAAC,CACV,CACA,sBAAWC,GACT,MAAO,CAAC,CACV,CACA,eAAWpH,GACT,MAAM,IAAIqH,MAAM,sEAClB,CACA,UAAAC,CAAWC,GAIT,OAHAA,EAAS9D,KAAK+D,gBAAgBD,GAC9BA,EAAS9D,KAAKgE,kBAAkBF,GAChC9D,KAAKiE,iBAAiBH,GACfA,CACT,CACA,iBAAAE,CAAkBF,GAChB,OAAOA,CACT,CACA,eAAAC,CAAgBD,EAAQvkB,GACtB,MAAM2kB,EAAa,GAAU3kB,GAAWyjB,GAAYQ,iBAAiBjkB,EAAS,UAAY,CAAC,EAE3F,MAAO,IACFygB,KAAKmE,YAAYT,WACM,iBAAfQ,EAA0BA,EAAa,CAAC,KAC/C,GAAU3kB,GAAWyjB,GAAYG,kBAAkB5jB,GAAW,CAAC,KAC7C,iBAAXukB,EAAsBA,EAAS,CAAC,EAE/C,CACA,gBAAAG,CAAiBH,EAAQM,EAAcpE,KAAKmE,YAAYR,aACtD,IAAK,MAAO7hB,EAAUuiB,KAAkBrnB,OAAOmkB,QAAQiD,GAAc,CACnE,MAAMzmB,EAAQmmB,EAAOhiB,GACfwiB,EAAY,GAAU3mB,GAAS,UAjiBrC4c,OADSA,EAkiB+C5c,GAhiBnD,GAAG4c,IAELvd,OAAOM,UAAUuC,SAASrC,KAAK+c,GAAQL,MAAM,eAAe,GAAGza,cA+hBlE,IAAK,IAAI8kB,OAAOF,GAAehhB,KAAKihB,GAClC,MAAM,IAAIE,UAAU,GAAGxE,KAAKmE,YAAY5H,KAAKkI,0BAA0B3iB,qBAA4BwiB,yBAAiCD,MAExI,CAtiBW9J,KAuiBb,EAqBF,MAAMmK,WAAsBjB,GAC1B,WAAAU,CAAY5kB,EAASukB,GACnBa,SACAplB,EAAUmb,GAAWnb,MAIrBygB,KAAK4E,SAAWrlB,EAChBygB,KAAK6E,QAAU7E,KAAK6D,WAAWC,GAC/BzK,GAAKtH,IAAIiO,KAAK4E,SAAU5E,KAAKmE,YAAYW,SAAU9E,MACrD,CAGA,OAAA+E,GACE1L,GAAKM,OAAOqG,KAAK4E,SAAU5E,KAAKmE,YAAYW,UAC5CvE,GAAaC,IAAIR,KAAK4E,SAAU5E,KAAKmE,YAAYa,WACjD,IAAK,MAAMC,KAAgBjoB,OAAOkoB,oBAAoBlF,MACpDA,KAAKiF,GAAgB,IAEzB,CACA,cAAAE,CAAe9I,EAAU9c,EAAS6lB,GAAa,GAC7CpI,GAAuBX,EAAU9c,EAAS6lB,EAC5C,CACA,UAAAvB,CAAWC,GAIT,OAHAA,EAAS9D,KAAK+D,gBAAgBD,EAAQ9D,KAAK4E,UAC3Cd,EAAS9D,KAAKgE,kBAAkBF,GAChC9D,KAAKiE,iBAAiBH,GACfA,CACT,CAGA,kBAAOuB,CAAY9lB,GACjB,OAAO8Z,GAAKlc,IAAIud,GAAWnb,GAAUygB,KAAK8E,SAC5C,CACA,0BAAOQ,CAAoB/lB,EAASukB,EAAS,CAAC,GAC5C,OAAO9D,KAAKqF,YAAY9lB,IAAY,IAAIygB,KAAKzgB,EAA2B,iBAAXukB,EAAsBA,EAAS,KAC9F,CACA,kBAAWyB,GACT,MA5CY,OA6Cd,CACA,mBAAWT,GACT,MAAO,MAAM9E,KAAKzD,MACpB,CACA,oBAAWyI,GACT,MAAO,IAAIhF,KAAK8E,UAClB,CACA,gBAAOU,CAAUllB,GACf,MAAO,GAAGA,IAAO0f,KAAKgF,WACxB,EAUF,MAAMS,GAAclmB,IAClB,IAAIwa,EAAWxa,EAAQic,aAAa,kBACpC,IAAKzB,GAAyB,MAAbA,EAAkB,CACjC,IAAI2L,EAAgBnmB,EAAQic,aAAa,QAMzC,IAAKkK,IAAkBA,EAActE,SAAS,OAASsE,EAAcjE,WAAW,KAC9E,OAAO,KAILiE,EAActE,SAAS,OAASsE,EAAcjE,WAAW,OAC3DiE,EAAgB,IAAIA,EAAcxjB,MAAM,KAAK,MAE/C6X,EAAW2L,GAAmC,MAAlBA,EAAwB5L,GAAc4L,EAAcC,QAAU,IAC5F,CACA,OAAO5L,CAAQ,EAEX6L,GAAiB,CACrBzT,KAAI,CAAC4H,EAAUxa,EAAU8F,SAASC,kBACzB,GAAG3G,UAAUsB,QAAQ3C,UAAU8iB,iBAAiB5iB,KAAK+B,EAASwa,IAEvE8L,QAAO,CAAC9L,EAAUxa,EAAU8F,SAASC,kBAC5BrF,QAAQ3C,UAAU8K,cAAc5K,KAAK+B,EAASwa,GAEvD+L,SAAQ,CAACvmB,EAASwa,IACT,GAAGpb,UAAUY,EAAQumB,UAAU3f,QAAOzB,GAASA,EAAMqhB,QAAQhM,KAEtE,OAAAiM,CAAQzmB,EAASwa,GACf,MAAMiM,EAAU,GAChB,IAAIC,EAAW1mB,EAAQwF,WAAWiW,QAAQjB,GAC1C,KAAOkM,GACLD,EAAQpU,KAAKqU,GACbA,EAAWA,EAASlhB,WAAWiW,QAAQjB,GAEzC,OAAOiM,CACT,EACA,IAAAE,CAAK3mB,EAASwa,GACZ,IAAIoM,EAAW5mB,EAAQ6mB,uBACvB,KAAOD,GAAU,CACf,GAAIA,EAASJ,QAAQhM,GACnB,MAAO,CAACoM,GAEVA,EAAWA,EAASC,sBACtB,CACA,MAAO,EACT,EAEA,IAAAvhB,CAAKtF,EAASwa,GACZ,IAAIlV,EAAOtF,EAAQ8mB,mBACnB,KAAOxhB,GAAM,CACX,GAAIA,EAAKkhB,QAAQhM,GACf,MAAO,CAAClV,GAEVA,EAAOA,EAAKwhB,kBACd,CACA,MAAO,EACT,EACA,iBAAAC,CAAkB/mB,GAChB,MAAMgnB,EAAa,CAAC,IAAK,SAAU,QAAS,WAAY,SAAU,UAAW,aAAc,4BAA4BzjB,KAAIiX,GAAY,GAAGA,2BAAiC7W,KAAK,KAChL,OAAO8c,KAAK7N,KAAKoU,EAAYhnB,GAAS4G,QAAOqgB,IAAOtL,GAAWsL,IAAO7L,GAAU6L,IAClF,EACA,sBAAAC,CAAuBlnB,GACrB,MAAMwa,EAAW0L,GAAYlmB,GAC7B,OAAIwa,GACK6L,GAAeC,QAAQ9L,GAAYA,EAErC,IACT,EACA,sBAAA2M,CAAuBnnB,GACrB,MAAMwa,EAAW0L,GAAYlmB,GAC7B,OAAOwa,EAAW6L,GAAeC,QAAQ9L,GAAY,IACvD,EACA,+BAAA4M,CAAgCpnB,GAC9B,MAAMwa,EAAW0L,GAAYlmB,GAC7B,OAAOwa,EAAW6L,GAAezT,KAAK4H,GAAY,EACpD,GAUI6M,GAAuB,CAACC,EAAWC,EAAS,UAChD,MAAMC,EAAa,gBAAgBF,EAAU7B,YACvC1kB,EAAOumB,EAAUtK,KACvBgE,GAAac,GAAGhc,SAAU0hB,EAAY,qBAAqBzmB,OAAU,SAAU8e,GAI7E,GAHI,CAAC,IAAK,QAAQgC,SAASpB,KAAKgH,UAC9B5H,EAAMkD,iBAEJpH,GAAW8E,MACb,OAEF,MAAMzT,EAASqZ,GAAec,uBAAuB1G,OAASA,KAAKhF,QAAQ,IAAI1a,KAC9DumB,EAAUvB,oBAAoB/Y,GAGtCua,IACX,GAAE,EAiBEG,GAAc,YACdC,GAAc,QAAQD,KACtBE,GAAe,SAASF,KAQ9B,MAAMG,WAAc1C,GAElB,eAAWnI,GACT,MAfW,OAgBb,CAGA,KAAA8K,GAEE,GADmB9G,GAAaqB,QAAQ5B,KAAK4E,SAAUsC,IACxClF,iBACb,OAEFhC,KAAK4E,SAASvJ,UAAU1B,OAlBF,QAmBtB,MAAMyL,EAAapF,KAAK4E,SAASvJ,UAAU7W,SApBrB,QAqBtBwb,KAAKmF,gBAAe,IAAMnF,KAAKsH,mBAAmBtH,KAAK4E,SAAUQ,EACnE,CAGA,eAAAkC,GACEtH,KAAK4E,SAASjL,SACd4G,GAAaqB,QAAQ5B,KAAK4E,SAAUuC,IACpCnH,KAAK+E,SACP,CAGA,sBAAOtI,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAO+c,GAAM9B,oBAAoBtF,MACvC,GAAsB,iBAAX8D,EAAX,CAGA,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,GAAQ9D,KAJb,CAKF,GACF,EAOF4G,GAAqBQ,GAAO,SAM5BjL,GAAmBiL,IAcnB,MAKMI,GAAyB,4BAO/B,MAAMC,WAAe/C,GAEnB,eAAWnI,GACT,MAfW,QAgBb,CAGA,MAAAmL,GAEE1H,KAAK4E,SAASxjB,aAAa,eAAgB4e,KAAK4E,SAASvJ,UAAUqM,OAjB3C,UAkB1B,CAGA,sBAAOjL,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAOod,GAAOnC,oBAAoBtF,MACzB,WAAX8D,GACFzZ,EAAKyZ,IAET,GACF,EAOFvD,GAAac,GAAGhc,SAjCe,2BAiCmBmiB,IAAwBpI,IACxEA,EAAMkD,iBACN,MAAMqF,EAASvI,EAAM7S,OAAOyO,QAAQwM,IACvBC,GAAOnC,oBAAoBqC,GACnCD,QAAQ,IAOfvL,GAAmBsL,IAcnB,MACMG,GAAc,YACdC,GAAmB,aAAaD,KAChCE,GAAkB,YAAYF,KAC9BG,GAAiB,WAAWH,KAC5BI,GAAoB,cAAcJ,KAClCK,GAAkB,YAAYL,KAK9BM,GAAY,CAChBC,YAAa,KACbC,aAAc,KACdC,cAAe,MAEXC,GAAgB,CACpBH,YAAa,kBACbC,aAAc,kBACdC,cAAe,mBAOjB,MAAME,WAAc9E,GAClB,WAAAU,CAAY5kB,EAASukB,GACnBa,QACA3E,KAAK4E,SAAWrlB,EACXA,GAAYgpB,GAAMC,gBAGvBxI,KAAK6E,QAAU7E,KAAK6D,WAAWC,GAC/B9D,KAAKyI,QAAU,EACfzI,KAAK0I,sBAAwB5H,QAAQlhB,OAAO+oB,cAC5C3I,KAAK4I,cACP,CAGA,kBAAWlF,GACT,OAAOwE,EACT,CACA,sBAAWvE,GACT,OAAO2E,EACT,CACA,eAAW/L,GACT,MA/CW,OAgDb,CAGA,OAAAwI,GACExE,GAAaC,IAAIR,KAAK4E,SAAUgD,GAClC,CAGA,MAAAiB,CAAOzJ,GACAY,KAAK0I,sBAIN1I,KAAK8I,wBAAwB1J,KAC/BY,KAAKyI,QAAUrJ,EAAM2J,SAJrB/I,KAAKyI,QAAUrJ,EAAM4J,QAAQ,GAAGD,OAMpC,CACA,IAAAE,CAAK7J,GACCY,KAAK8I,wBAAwB1J,KAC/BY,KAAKyI,QAAUrJ,EAAM2J,QAAU/I,KAAKyI,SAEtCzI,KAAKkJ,eACLrM,GAAQmD,KAAK6E,QAAQsD,YACvB,CACA,KAAAgB,CAAM/J,GACJY,KAAKyI,QAAUrJ,EAAM4J,SAAW5J,EAAM4J,QAAQtY,OAAS,EAAI,EAAI0O,EAAM4J,QAAQ,GAAGD,QAAU/I,KAAKyI,OACjG,CACA,YAAAS,GACE,MAAME,EAAYjnB,KAAKoC,IAAIyb,KAAKyI,SAChC,GAAIW,GAnEgB,GAoElB,OAEF,MAAM9b,EAAY8b,EAAYpJ,KAAKyI,QACnCzI,KAAKyI,QAAU,EACVnb,GAGLuP,GAAQvP,EAAY,EAAI0S,KAAK6E,QAAQwD,cAAgBrI,KAAK6E,QAAQuD,aACpE,CACA,WAAAQ,GACM5I,KAAK0I,uBACPnI,GAAac,GAAGrB,KAAK4E,SAAUoD,IAAmB5I,GAASY,KAAK6I,OAAOzJ,KACvEmB,GAAac,GAAGrB,KAAK4E,SAAUqD,IAAiB7I,GAASY,KAAKiJ,KAAK7J,KACnEY,KAAK4E,SAASvJ,UAAU5E,IAlFG,mBAoF3B8J,GAAac,GAAGrB,KAAK4E,SAAUiD,IAAkBzI,GAASY,KAAK6I,OAAOzJ,KACtEmB,GAAac,GAAGrB,KAAK4E,SAAUkD,IAAiB1I,GAASY,KAAKmJ,MAAM/J,KACpEmB,GAAac,GAAGrB,KAAK4E,SAAUmD,IAAgB3I,GAASY,KAAKiJ,KAAK7J,KAEtE,CACA,uBAAA0J,CAAwB1J,GACtB,OAAOY,KAAK0I,wBA3FS,QA2FiBtJ,EAAMiK,aA5FrB,UA4FyDjK,EAAMiK,YACxF,CAGA,kBAAOb,GACL,MAAO,iBAAkBnjB,SAASC,iBAAmB7C,UAAU6mB,eAAiB,CAClF,EAeF,MAEMC,GAAc,eACdC,GAAiB,YAKjBC,GAAa,OACbC,GAAa,OACbC,GAAiB,OACjBC,GAAkB,QAClBC,GAAc,QAAQN,KACtBO,GAAa,OAAOP,KACpBQ,GAAkB,UAAUR,KAC5BS,GAAqB,aAAaT,KAClCU,GAAqB,aAAaV,KAClCW,GAAmB,YAAYX,KAC/BY,GAAwB,OAAOZ,KAAcC,KAC7CY,GAAyB,QAAQb,KAAcC,KAC/Ca,GAAsB,WACtBC,GAAsB,SAMtBC,GAAkB,UAClBC,GAAgB,iBAChBC,GAAuBF,GAAkBC,GAKzCE,GAAmB,CACvB,UAAoBd,GACpB,WAAqBD,IAEjBgB,GAAY,CAChBC,SAAU,IACVC,UAAU,EACVC,MAAO,QACPC,MAAM,EACNC,OAAO,EACPC,MAAM,GAEFC,GAAgB,CACpBN,SAAU,mBAEVC,SAAU,UACVC,MAAO,mBACPC,KAAM,mBACNC,MAAO,UACPC,KAAM,WAOR,MAAME,WAAiBzG,GACrB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKoL,UAAY,KACjBpL,KAAKqL,eAAiB,KACtBrL,KAAKsL,YAAa,EAClBtL,KAAKuL,aAAe,KACpBvL,KAAKwL,aAAe,KACpBxL,KAAKyL,mBAAqB7F,GAAeC,QArCjB,uBAqC8C7F,KAAK4E,UAC3E5E,KAAK0L,qBACD1L,KAAK6E,QAAQkG,OAASV,IACxBrK,KAAK2L,OAET,CAGA,kBAAWjI,GACT,OAAOiH,EACT,CACA,sBAAWhH,GACT,OAAOuH,EACT,CACA,eAAW3O,GACT,MAnFW,UAoFb,CAGA,IAAA1X,GACEmb,KAAK4L,OAAOnC,GACd,CACA,eAAAoC,IAIOxmB,SAASymB,QAAUnR,GAAUqF,KAAK4E,WACrC5E,KAAKnb,MAET,CACA,IAAAqhB,GACElG,KAAK4L,OAAOlC,GACd,CACA,KAAAoB,GACM9K,KAAKsL,YACPlR,GAAqB4F,KAAK4E,UAE5B5E,KAAK+L,gBACP,CACA,KAAAJ,GACE3L,KAAK+L,iBACL/L,KAAKgM,kBACLhM,KAAKoL,UAAYa,aAAY,IAAMjM,KAAK6L,mBAAmB7L,KAAK6E,QAAQ+F,SAC1E,CACA,iBAAAsB,GACOlM,KAAK6E,QAAQkG,OAGd/K,KAAKsL,WACP/K,GAAae,IAAItB,KAAK4E,SAAUkF,IAAY,IAAM9J,KAAK2L,UAGzD3L,KAAK2L,QACP,CACA,EAAAQ,CAAG1T,GACD,MAAM2T,EAAQpM,KAAKqM,YACnB,GAAI5T,EAAQ2T,EAAM1b,OAAS,GAAK+H,EAAQ,EACtC,OAEF,GAAIuH,KAAKsL,WAEP,YADA/K,GAAae,IAAItB,KAAK4E,SAAUkF,IAAY,IAAM9J,KAAKmM,GAAG1T,KAG5D,MAAM6T,EAActM,KAAKuM,cAAcvM,KAAKwM,cAC5C,GAAIF,IAAgB7T,EAClB,OAEF,MAAMtC,EAAQsC,EAAQ6T,EAAc7C,GAAaC,GACjD1J,KAAK4L,OAAOzV,EAAOiW,EAAM3T,GAC3B,CACA,OAAAsM,GACM/E,KAAKwL,cACPxL,KAAKwL,aAAazG,UAEpBJ,MAAMI,SACR,CAGA,iBAAAf,CAAkBF,GAEhB,OADAA,EAAO2I,gBAAkB3I,EAAO8G,SACzB9G,CACT,CACA,kBAAA4H,GACM1L,KAAK6E,QAAQgG,UACftK,GAAac,GAAGrB,KAAK4E,SAAUmF,IAAiB3K,GAASY,KAAK0M,SAAStN,KAE9C,UAAvBY,KAAK6E,QAAQiG,QACfvK,GAAac,GAAGrB,KAAK4E,SAAUoF,IAAoB,IAAMhK,KAAK8K,UAC9DvK,GAAac,GAAGrB,KAAK4E,SAAUqF,IAAoB,IAAMjK,KAAKkM,uBAE5DlM,KAAK6E,QAAQmG,OAASzC,GAAMC,eAC9BxI,KAAK2M,yBAET,CACA,uBAAAA,GACE,IAAK,MAAMC,KAAOhH,GAAezT,KArIX,qBAqImC6N,KAAK4E,UAC5DrE,GAAac,GAAGuL,EAAK1C,IAAkB9K,GAASA,EAAMkD,mBAExD,MAmBMuK,EAAc,CAClBzE,aAAc,IAAMpI,KAAK4L,OAAO5L,KAAK8M,kBAAkBnD,KACvDtB,cAAe,IAAMrI,KAAK4L,OAAO5L,KAAK8M,kBAAkBlD,KACxDzB,YAtBkB,KACS,UAAvBnI,KAAK6E,QAAQiG,QAYjB9K,KAAK8K,QACD9K,KAAKuL,cACPwB,aAAa/M,KAAKuL,cAEpBvL,KAAKuL,aAAe1N,YAAW,IAAMmC,KAAKkM,qBAjLjB,IAiL+DlM,KAAK6E,QAAQ+F,UAAS,GAOhH5K,KAAKwL,aAAe,IAAIjD,GAAMvI,KAAK4E,SAAUiI,EAC/C,CACA,QAAAH,CAAStN,GACP,GAAI,kBAAkB/b,KAAK+b,EAAM7S,OAAOya,SACtC,OAEF,MAAM1Z,EAAYod,GAAiBtL,EAAMtiB,KACrCwQ,IACF8R,EAAMkD,iBACNtC,KAAK4L,OAAO5L,KAAK8M,kBAAkBxf,IAEvC,CACA,aAAAif,CAAchtB,GACZ,OAAOygB,KAAKqM,YAAYlnB,QAAQ5F,EAClC,CACA,0BAAAytB,CAA2BvU,GACzB,IAAKuH,KAAKyL,mBACR,OAEF,MAAMwB,EAAkBrH,GAAeC,QAAQ0E,GAAiBvK,KAAKyL,oBACrEwB,EAAgB5R,UAAU1B,OAAO2Q,IACjC2C,EAAgB9rB,gBAAgB,gBAChC,MAAM+rB,EAAqBtH,GAAeC,QAAQ,sBAAsBpN,MAAWuH,KAAKyL,oBACpFyB,IACFA,EAAmB7R,UAAU5E,IAAI6T,IACjC4C,EAAmB9rB,aAAa,eAAgB,QAEpD,CACA,eAAA4qB,GACE,MAAMzsB,EAAUygB,KAAKqL,gBAAkBrL,KAAKwM,aAC5C,IAAKjtB,EACH,OAEF,MAAM4tB,EAAkB5P,OAAO6P,SAAS7tB,EAAQic,aAAa,oBAAqB,IAClFwE,KAAK6E,QAAQ+F,SAAWuC,GAAmBnN,KAAK6E,QAAQ4H,eAC1D,CACA,MAAAb,CAAOzV,EAAO5W,EAAU,MACtB,GAAIygB,KAAKsL,WACP,OAEF,MAAMvN,EAAgBiC,KAAKwM,aACrBa,EAASlX,IAAUsT,GACnB6D,EAAc/tB,GAAWue,GAAqBkC,KAAKqM,YAAatO,EAAesP,EAAQrN,KAAK6E,QAAQoG,MAC1G,GAAIqC,IAAgBvP,EAClB,OAEF,MAAMwP,EAAmBvN,KAAKuM,cAAce,GACtCE,EAAehI,GACZjF,GAAaqB,QAAQ5B,KAAK4E,SAAUY,EAAW,CACpD1F,cAAewN,EACfhgB,UAAW0S,KAAKyN,kBAAkBtX,GAClCuD,KAAMsG,KAAKuM,cAAcxO,GACzBoO,GAAIoB,IAIR,GADmBC,EAAa3D,IACjB7H,iBACb,OAEF,IAAKjE,IAAkBuP,EAGrB,OAEF,MAAMI,EAAY5M,QAAQd,KAAKoL,WAC/BpL,KAAK8K,QACL9K,KAAKsL,YAAa,EAClBtL,KAAKgN,2BAA2BO,GAChCvN,KAAKqL,eAAiBiC,EACtB,MAAMK,EAAuBN,EA3OR,sBADF,oBA6ObO,EAAiBP,EA3OH,qBACA,qBA2OpBC,EAAYjS,UAAU5E,IAAImX,GAC1B/R,GAAOyR,GACPvP,EAAc1C,UAAU5E,IAAIkX,GAC5BL,EAAYjS,UAAU5E,IAAIkX,GAQ1B3N,KAAKmF,gBAPoB,KACvBmI,EAAYjS,UAAU1B,OAAOgU,EAAsBC,GACnDN,EAAYjS,UAAU5E,IAAI6T,IAC1BvM,EAAc1C,UAAU1B,OAAO2Q,GAAqBsD,EAAgBD,GACpE3N,KAAKsL,YAAa,EAClBkC,EAAa1D,GAAW,GAEY/L,EAAeiC,KAAK6N,eACtDH,GACF1N,KAAK2L,OAET,CACA,WAAAkC,GACE,OAAO7N,KAAK4E,SAASvJ,UAAU7W,SAhQV,QAiQvB,CACA,UAAAgoB,GACE,OAAO5G,GAAeC,QAAQ4E,GAAsBzK,KAAK4E,SAC3D,CACA,SAAAyH,GACE,OAAOzG,GAAezT,KAAKqY,GAAexK,KAAK4E,SACjD,CACA,cAAAmH,GACM/L,KAAKoL,YACP0C,cAAc9N,KAAKoL,WACnBpL,KAAKoL,UAAY,KAErB,CACA,iBAAA0B,CAAkBxf,GAChB,OAAI2O,KACK3O,IAAcqc,GAAiBD,GAAaD,GAE9Cnc,IAAcqc,GAAiBF,GAAaC,EACrD,CACA,iBAAA+D,CAAkBtX,GAChB,OAAI8F,KACK9F,IAAUuT,GAAaC,GAAiBC,GAE1CzT,IAAUuT,GAAaE,GAAkBD,EAClD,CAGA,sBAAOlN,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAO8gB,GAAS7F,oBAAoBtF,KAAM8D,GAChD,GAAsB,iBAAXA,GAIX,GAAsB,iBAAXA,EAAqB,CAC9B,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IACP,OAREzZ,EAAK8hB,GAAGrI,EASZ,GACF,EAOFvD,GAAac,GAAGhc,SAAU+kB,GAvSE,uCAuS2C,SAAUhL,GAC/E,MAAM7S,EAASqZ,GAAec,uBAAuB1G,MACrD,IAAKzT,IAAWA,EAAO8O,UAAU7W,SAAS6lB,IACxC,OAEFjL,EAAMkD,iBACN,MAAMyL,EAAW5C,GAAS7F,oBAAoB/Y,GACxCyhB,EAAahO,KAAKxE,aAAa,oBACrC,OAAIwS,GACFD,EAAS5B,GAAG6B,QACZD,EAAS7B,qBAGyC,SAAhDlJ,GAAYQ,iBAAiBxD,KAAM,UACrC+N,EAASlpB,YACTkpB,EAAS7B,sBAGX6B,EAAS7H,YACT6H,EAAS7B,oBACX,IACA3L,GAAac,GAAGzhB,OAAQuqB,IAAuB,KAC7C,MAAM8D,EAAYrI,GAAezT,KA5TR,6BA6TzB,IAAK,MAAM4b,KAAYE,EACrB9C,GAAS7F,oBAAoByI,EAC/B,IAOF5R,GAAmBgP,IAcnB,MAEM+C,GAAc,eAEdC,GAAe,OAAOD,KACtBE,GAAgB,QAAQF,KACxBG,GAAe,OAAOH,KACtBI,GAAiB,SAASJ,KAC1BK,GAAyB,QAAQL,cACjCM,GAAoB,OACpBC,GAAsB,WACtBC,GAAwB,aAExBC,GAA6B,WAAWF,OAAwBA,KAKhEG,GAAyB,8BACzBC,GAAY,CAChBpqB,OAAQ,KACRijB,QAAQ,GAEJoH,GAAgB,CACpBrqB,OAAQ,iBACRijB,OAAQ,WAOV,MAAMqH,WAAiBrK,GACrB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKgP,kBAAmB,EACxBhP,KAAKiP,cAAgB,GACrB,MAAMC,EAAatJ,GAAezT,KAAKyc,IACvC,IAAK,MAAMO,KAAQD,EAAY,CAC7B,MAAMnV,EAAW6L,GAAea,uBAAuB0I,GACjDC,EAAgBxJ,GAAezT,KAAK4H,GAAU5T,QAAOkpB,GAAgBA,IAAiBrP,KAAK4E,WAChF,OAAb7K,GAAqBqV,EAAc1e,QACrCsP,KAAKiP,cAAcrd,KAAKud,EAE5B,CACAnP,KAAKsP,sBACAtP,KAAK6E,QAAQpgB,QAChBub,KAAKuP,0BAA0BvP,KAAKiP,cAAejP,KAAKwP,YAEtDxP,KAAK6E,QAAQ6C,QACf1H,KAAK0H,QAET,CAGA,kBAAWhE,GACT,OAAOmL,EACT,CACA,sBAAWlL,GACT,OAAOmL,EACT,CACA,eAAWvS,GACT,MA9DW,UA+Db,CAGA,MAAAmL,GACM1H,KAAKwP,WACPxP,KAAKyP,OAELzP,KAAK0P,MAET,CACA,IAAAA,GACE,GAAI1P,KAAKgP,kBAAoBhP,KAAKwP,WAChC,OAEF,IAAIG,EAAiB,GAQrB,GALI3P,KAAK6E,QAAQpgB,SACfkrB,EAAiB3P,KAAK4P,uBAhEH,wCAgE4CzpB,QAAO5G,GAAWA,IAAYygB,KAAK4E,WAAU9hB,KAAIvD,GAAWwvB,GAASzJ,oBAAoB/lB,EAAS,CAC/JmoB,QAAQ,OAGRiI,EAAejf,QAAUif,EAAe,GAAGX,iBAC7C,OAGF,GADmBzO,GAAaqB,QAAQ5B,KAAK4E,SAAUuJ,IACxCnM,iBACb,OAEF,IAAK,MAAM6N,KAAkBF,EAC3BE,EAAeJ,OAEjB,MAAMK,EAAY9P,KAAK+P,gBACvB/P,KAAK4E,SAASvJ,UAAU1B,OAAO8U,IAC/BzO,KAAK4E,SAASvJ,UAAU5E,IAAIiY,IAC5B1O,KAAK4E,SAAS7jB,MAAM+uB,GAAa,EACjC9P,KAAKuP,0BAA0BvP,KAAKiP,eAAe,GACnDjP,KAAKgP,kBAAmB,EACxB,MAQMgB,EAAa,SADUF,EAAU,GAAGrL,cAAgBqL,EAAU1d,MAAM,KAE1E4N,KAAKmF,gBATY,KACfnF,KAAKgP,kBAAmB,EACxBhP,KAAK4E,SAASvJ,UAAU1B,OAAO+U,IAC/B1O,KAAK4E,SAASvJ,UAAU5E,IAAIgY,GAAqBD,IACjDxO,KAAK4E,SAAS7jB,MAAM+uB,GAAa,GACjCvP,GAAaqB,QAAQ5B,KAAK4E,SAAUwJ,GAAc,GAItBpO,KAAK4E,UAAU,GAC7C5E,KAAK4E,SAAS7jB,MAAM+uB,GAAa,GAAG9P,KAAK4E,SAASoL,MACpD,CACA,IAAAP,GACE,GAAIzP,KAAKgP,mBAAqBhP,KAAKwP,WACjC,OAGF,GADmBjP,GAAaqB,QAAQ5B,KAAK4E,SAAUyJ,IACxCrM,iBACb,OAEF,MAAM8N,EAAY9P,KAAK+P,gBACvB/P,KAAK4E,SAAS7jB,MAAM+uB,GAAa,GAAG9P,KAAK4E,SAASthB,wBAAwBwsB,OAC1EjU,GAAOmE,KAAK4E,UACZ5E,KAAK4E,SAASvJ,UAAU5E,IAAIiY,IAC5B1O,KAAK4E,SAASvJ,UAAU1B,OAAO8U,GAAqBD,IACpD,IAAK,MAAM5M,KAAW5B,KAAKiP,cAAe,CACxC,MAAM1vB,EAAUqmB,GAAec,uBAAuB9E,GAClDriB,IAAYygB,KAAKwP,SAASjwB,IAC5BygB,KAAKuP,0BAA0B,CAAC3N,IAAU,EAE9C,CACA5B,KAAKgP,kBAAmB,EAOxBhP,KAAK4E,SAAS7jB,MAAM+uB,GAAa,GACjC9P,KAAKmF,gBAPY,KACfnF,KAAKgP,kBAAmB,EACxBhP,KAAK4E,SAASvJ,UAAU1B,OAAO+U,IAC/B1O,KAAK4E,SAASvJ,UAAU5E,IAAIgY,IAC5BlO,GAAaqB,QAAQ5B,KAAK4E,SAAU0J,GAAe,GAGvBtO,KAAK4E,UAAU,EAC/C,CACA,QAAA4K,CAASjwB,EAAUygB,KAAK4E,UACtB,OAAOrlB,EAAQ8b,UAAU7W,SAASgqB,GACpC,CAGA,iBAAAxK,CAAkBF,GAGhB,OAFAA,EAAO4D,OAAS5G,QAAQgD,EAAO4D,QAC/B5D,EAAOrf,OAASiW,GAAWoJ,EAAOrf,QAC3Bqf,CACT,CACA,aAAAiM,GACE,OAAO/P,KAAK4E,SAASvJ,UAAU7W,SA3IL,uBAChB,QACC,QA0Ib,CACA,mBAAA8qB,GACE,IAAKtP,KAAK6E,QAAQpgB,OAChB,OAEF,MAAMqhB,EAAW9F,KAAK4P,uBAAuBhB,IAC7C,IAAK,MAAMrvB,KAAWumB,EAAU,CAC9B,MAAMmK,EAAWrK,GAAec,uBAAuBnnB,GACnD0wB,GACFjQ,KAAKuP,0BAA0B,CAAChwB,GAAUygB,KAAKwP,SAASS,GAE5D,CACF,CACA,sBAAAL,CAAuB7V,GACrB,MAAM+L,EAAWF,GAAezT,KAAKwc,GAA4B3O,KAAK6E,QAAQpgB,QAE9E,OAAOmhB,GAAezT,KAAK4H,EAAUiG,KAAK6E,QAAQpgB,QAAQ0B,QAAO5G,IAAYumB,EAAS1E,SAAS7hB,IACjG,CACA,yBAAAgwB,CAA0BW,EAAcC,GACtC,GAAKD,EAAaxf,OAGlB,IAAK,MAAMnR,KAAW2wB,EACpB3wB,EAAQ8b,UAAUqM,OArKK,aAqKyByI,GAChD5wB,EAAQ6B,aAAa,gBAAiB+uB,EAE1C,CAGA,sBAAO1T,CAAgBqH,GACrB,MAAMe,EAAU,CAAC,EAIjB,MAHsB,iBAAXf,GAAuB,YAAYzgB,KAAKygB,KACjDe,EAAQ6C,QAAS,GAEZ1H,KAAKuH,MAAK,WACf,MAAMld,EAAO0kB,GAASzJ,oBAAoBtF,KAAM6E,GAChD,GAAsB,iBAAXf,EAAqB,CAC9B,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IACP,CACF,GACF,EAOFvD,GAAac,GAAGhc,SAAUkpB,GAAwBK,IAAwB,SAAUxP,IAErD,MAAzBA,EAAM7S,OAAOya,SAAmB5H,EAAMW,gBAAmD,MAAjCX,EAAMW,eAAeiH,UAC/E5H,EAAMkD,iBAER,IAAK,MAAM/iB,KAAWqmB,GAAee,gCAAgC3G,MACnE+O,GAASzJ,oBAAoB/lB,EAAS,CACpCmoB,QAAQ,IACPA,QAEP,IAMAvL,GAAmB4S,IAcnB,MAAMqB,GAAS,WAETC,GAAc,eACdC,GAAiB,YAGjBC,GAAiB,UACjBC,GAAmB,YAGnBC,GAAe,OAAOJ,KACtBK,GAAiB,SAASL,KAC1BM,GAAe,OAAON,KACtBO,GAAgB,QAAQP,KACxBQ,GAAyB,QAAQR,KAAcC,KAC/CQ,GAAyB,UAAUT,KAAcC,KACjDS,GAAuB,QAAQV,KAAcC,KAC7CU,GAAoB,OAMpBC,GAAyB,4DACzBC,GAA6B,GAAGD,MAA0BD,KAC1DG,GAAgB,iBAIhBC,GAAgBnV,KAAU,UAAY,YACtCoV,GAAmBpV,KAAU,YAAc,UAC3CqV,GAAmBrV,KAAU,aAAe,eAC5CsV,GAAsBtV,KAAU,eAAiB,aACjDuV,GAAkBvV,KAAU,aAAe,cAC3CwV,GAAiBxV,KAAU,cAAgB,aAG3CyV,GAAY,CAChBC,WAAW,EACX1jB,SAAU,kBACV2jB,QAAS,UACT5pB,OAAQ,CAAC,EAAG,GACZ6pB,aAAc,KACdvzB,UAAW,UAEPwzB,GAAgB,CACpBH,UAAW,mBACX1jB,SAAU,mBACV2jB,QAAS,SACT5pB,OAAQ,0BACR6pB,aAAc,yBACdvzB,UAAW,2BAOb,MAAMyzB,WAAiBrN,GACrB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKgS,QAAU,KACfhS,KAAKiS,QAAUjS,KAAK4E,SAAS7f,WAE7Bib,KAAKkS,MAAQtM,GAAe/gB,KAAKmb,KAAK4E,SAAUuM,IAAe,IAAMvL,GAAeM,KAAKlG,KAAK4E,SAAUuM,IAAe,IAAMvL,GAAeC,QAAQsL,GAAenR,KAAKiS,SACxKjS,KAAKmS,UAAYnS,KAAKoS,eACxB,CAGA,kBAAW1O,GACT,OAAOgO,EACT,CACA,sBAAW/N,GACT,OAAOmO,EACT,CACA,eAAWvV,GACT,OAAO6T,EACT,CAGA,MAAA1I,GACE,OAAO1H,KAAKwP,WAAaxP,KAAKyP,OAASzP,KAAK0P,MAC9C,CACA,IAAAA,GACE,GAAIxU,GAAW8E,KAAK4E,WAAa5E,KAAKwP,WACpC,OAEF,MAAM1P,EAAgB,CACpBA,cAAeE,KAAK4E,UAGtB,IADkBrE,GAAaqB,QAAQ5B,KAAK4E,SAAU+L,GAAc7Q,GACtDkC,iBAAd,CASA,GANAhC,KAAKqS,gBAMD,iBAAkBhtB,SAASC,kBAAoB0a,KAAKiS,QAAQjX,QAzExC,eA0EtB,IAAK,MAAMzb,IAAW,GAAGZ,UAAU0G,SAAS6G,KAAK4Z,UAC/CvF,GAAac,GAAG9hB,EAAS,YAAaqc,IAG1CoE,KAAK4E,SAAS0N,QACdtS,KAAK4E,SAASxjB,aAAa,iBAAiB,GAC5C4e,KAAKkS,MAAM7W,UAAU5E,IAAIua,IACzBhR,KAAK4E,SAASvJ,UAAU5E,IAAIua,IAC5BzQ,GAAaqB,QAAQ5B,KAAK4E,SAAUgM,GAAe9Q,EAhBnD,CAiBF,CACA,IAAA2P,GACE,GAAIvU,GAAW8E,KAAK4E,YAAc5E,KAAKwP,WACrC,OAEF,MAAM1P,EAAgB,CACpBA,cAAeE,KAAK4E,UAEtB5E,KAAKuS,cAAczS,EACrB,CACA,OAAAiF,GACM/E,KAAKgS,SACPhS,KAAKgS,QAAQhZ,UAEf2L,MAAMI,SACR,CACA,MAAAha,GACEiV,KAAKmS,UAAYnS,KAAKoS,gBAClBpS,KAAKgS,SACPhS,KAAKgS,QAAQjnB,QAEjB,CAGA,aAAAwnB,CAAczS,GAEZ,IADkBS,GAAaqB,QAAQ5B,KAAK4E,SAAU6L,GAAc3Q,GACtDkC,iBAAd,CAMA,GAAI,iBAAkB3c,SAASC,gBAC7B,IAAK,MAAM/F,IAAW,GAAGZ,UAAU0G,SAAS6G,KAAK4Z,UAC/CvF,GAAaC,IAAIjhB,EAAS,YAAaqc,IAGvCoE,KAAKgS,SACPhS,KAAKgS,QAAQhZ,UAEfgH,KAAKkS,MAAM7W,UAAU1B,OAAOqX,IAC5BhR,KAAK4E,SAASvJ,UAAU1B,OAAOqX,IAC/BhR,KAAK4E,SAASxjB,aAAa,gBAAiB,SAC5C4hB,GAAYE,oBAAoBlD,KAAKkS,MAAO,UAC5C3R,GAAaqB,QAAQ5B,KAAK4E,SAAU8L,GAAgB5Q,EAhBpD,CAiBF,CACA,UAAA+D,CAAWC,GAET,GAAgC,iBADhCA,EAASa,MAAMd,WAAWC,IACRxlB,YAA2B,GAAUwlB,EAAOxlB,YAAgE,mBAA3CwlB,EAAOxlB,UAAUgF,sBAElG,MAAM,IAAIkhB,UAAU,GAAG4L,GAAO3L,+GAEhC,OAAOX,CACT,CACA,aAAAuO,GACE,QAAsB,IAAX,EACT,MAAM,IAAI7N,UAAU,gEAEtB,IAAIgO,EAAmBxS,KAAK4E,SACG,WAA3B5E,KAAK6E,QAAQvmB,UACfk0B,EAAmBxS,KAAKiS,QACf,GAAUjS,KAAK6E,QAAQvmB,WAChCk0B,EAAmB9X,GAAWsF,KAAK6E,QAAQvmB,WACA,iBAA3B0hB,KAAK6E,QAAQvmB,YAC7Bk0B,EAAmBxS,KAAK6E,QAAQvmB,WAElC,MAAMuzB,EAAe7R,KAAKyS,mBAC1BzS,KAAKgS,QAAU,GAAoBQ,EAAkBxS,KAAKkS,MAAOL,EACnE,CACA,QAAArC,GACE,OAAOxP,KAAKkS,MAAM7W,UAAU7W,SAASwsB,GACvC,CACA,aAAA0B,GACE,MAAMC,EAAiB3S,KAAKiS,QAC5B,GAAIU,EAAetX,UAAU7W,SArKN,WAsKrB,OAAOgtB,GAET,GAAImB,EAAetX,UAAU7W,SAvKJ,aAwKvB,OAAOitB,GAET,GAAIkB,EAAetX,UAAU7W,SAzKA,iBA0K3B,MA5JsB,MA8JxB,GAAImuB,EAAetX,UAAU7W,SA3KE,mBA4K7B,MA9JyB,SAkK3B,MAAMouB,EAAkF,QAA1E3tB,iBAAiB+a,KAAKkS,OAAOpX,iBAAiB,iBAAiB6K,OAC7E,OAAIgN,EAAetX,UAAU7W,SArLP,UAsLbouB,EAAQvB,GAAmBD,GAE7BwB,EAAQrB,GAAsBD,EACvC,CACA,aAAAc,GACE,OAAkD,OAA3CpS,KAAK4E,SAAS5J,QAnLD,UAoLtB,CACA,UAAA6X,GACE,MAAM,OACJ7qB,GACEgY,KAAK6E,QACT,MAAsB,iBAAX7c,EACFA,EAAO9F,MAAM,KAAKY,KAAInF,GAAS4f,OAAO6P,SAASzvB,EAAO,MAEzC,mBAAXqK,EACF8qB,GAAc9qB,EAAO8qB,EAAY9S,KAAK4E,UAExC5c,CACT,CACA,gBAAAyqB,GACE,MAAMM,EAAwB,CAC5Br0B,UAAWshB,KAAK0S,gBAChBtc,UAAW,CAAC,CACV9V,KAAM,kBACNmB,QAAS,CACPwM,SAAU+R,KAAK6E,QAAQ5W,WAExB,CACD3N,KAAM,SACNmB,QAAS,CACPuG,OAAQgY,KAAK6S,iBAanB,OAPI7S,KAAKmS,WAAsC,WAAzBnS,KAAK6E,QAAQ+M,WACjC5O,GAAYC,iBAAiBjD,KAAKkS,MAAO,SAAU,UACnDa,EAAsB3c,UAAY,CAAC,CACjC9V,KAAM,cACNC,SAAS,KAGN,IACFwyB,KACAlW,GAAQmD,KAAK6E,QAAQgN,aAAc,CAACkB,IAE3C,CACA,eAAAC,EAAgB,IACdl2B,EAAG,OACHyP,IAEA,MAAM6f,EAAQxG,GAAezT,KAhOF,8DAgO+B6N,KAAKkS,OAAO/rB,QAAO5G,GAAWob,GAAUpb,KAC7F6sB,EAAM1b,QAMXoN,GAAqBsO,EAAO7f,EAAQzP,IAAQ0zB,IAAmBpE,EAAMhL,SAAS7U,IAAS+lB,OACzF,CAGA,sBAAO7V,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAO0nB,GAASzM,oBAAoBtF,KAAM8D,GAChD,GAAsB,iBAAXA,EAAX,CAGA,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,CACA,iBAAOmP,CAAW7T,GAChB,GA5QuB,IA4QnBA,EAAMuI,QAAgD,UAAfvI,EAAMqB,MA/QnC,QA+QuDrB,EAAMtiB,IACzE,OAEF,MAAMo2B,EAActN,GAAezT,KAAK+e,IACxC,IAAK,MAAMxJ,KAAUwL,EAAa,CAChC,MAAMC,EAAUpB,GAAS1M,YAAYqC,GACrC,IAAKyL,IAAyC,IAA9BA,EAAQtO,QAAQ8M,UAC9B,SAEF,MAAMyB,EAAehU,EAAMgU,eACrBC,EAAeD,EAAahS,SAAS+R,EAAQjB,OACnD,GAAIkB,EAAahS,SAAS+R,EAAQvO,WAA2C,WAA9BuO,EAAQtO,QAAQ8M,YAA2B0B,GAA8C,YAA9BF,EAAQtO,QAAQ8M,WAA2B0B,EACnJ,SAIF,GAAIF,EAAQjB,MAAM1tB,SAAS4a,EAAM7S,UAA2B,UAAf6S,EAAMqB,MA/RvC,QA+R2DrB,EAAMtiB,KAAqB,qCAAqCuG,KAAK+b,EAAM7S,OAAOya,UACvJ,SAEF,MAAMlH,EAAgB,CACpBA,cAAeqT,EAAQvO,UAEN,UAAfxF,EAAMqB,OACRX,EAAciH,WAAa3H,GAE7B+T,EAAQZ,cAAczS,EACxB,CACF,CACA,4BAAOwT,CAAsBlU,GAI3B,MAAMmU,EAAU,kBAAkBlwB,KAAK+b,EAAM7S,OAAOya,SAC9CwM,EAjTW,WAiTKpU,EAAMtiB,IACtB22B,EAAkB,CAAClD,GAAgBC,IAAkBpP,SAAShC,EAAMtiB,KAC1E,IAAK22B,IAAoBD,EACvB,OAEF,GAAID,IAAYC,EACd,OAEFpU,EAAMkD,iBAGN,MAAMoR,EAAkB1T,KAAK+F,QAAQkL,IAA0BjR,KAAO4F,GAAeM,KAAKlG,KAAMiR,IAAwB,IAAMrL,GAAe/gB,KAAKmb,KAAMiR,IAAwB,IAAMrL,GAAeC,QAAQoL,GAAwB7R,EAAMW,eAAehb,YACpPwF,EAAWwnB,GAASzM,oBAAoBoO,GAC9C,GAAID,EAIF,OAHArU,EAAMuU,kBACNppB,EAASmlB,YACTnlB,EAASyoB,gBAAgB5T,GAGvB7U,EAASilB,aAEXpQ,EAAMuU,kBACNppB,EAASklB,OACTiE,EAAgBpB,QAEpB,EAOF/R,GAAac,GAAGhc,SAAUyrB,GAAwBG,GAAwBc,GAASuB,uBACnF/S,GAAac,GAAGhc,SAAUyrB,GAAwBK,GAAeY,GAASuB,uBAC1E/S,GAAac,GAAGhc,SAAUwrB,GAAwBkB,GAASkB,YAC3D1S,GAAac,GAAGhc,SAAU0rB,GAAsBgB,GAASkB,YACzD1S,GAAac,GAAGhc,SAAUwrB,GAAwBI,IAAwB,SAAU7R,GAClFA,EAAMkD,iBACNyP,GAASzM,oBAAoBtF,MAAM0H,QACrC,IAMAvL,GAAmB4V,IAcnB,MAAM6B,GAAS,WAETC,GAAoB,OACpBC,GAAkB,gBAAgBF,KAClCG,GAAY,CAChBC,UAAW,iBACXC,cAAe,KACf7O,YAAY,EACZzK,WAAW,EAEXuZ,YAAa,QAGTC,GAAgB,CACpBH,UAAW,SACXC,cAAe,kBACf7O,WAAY,UACZzK,UAAW,UACXuZ,YAAa,oBAOf,MAAME,WAAiB3Q,GACrB,WAAAU,CAAYL,GACVa,QACA3E,KAAK6E,QAAU7E,KAAK6D,WAAWC,GAC/B9D,KAAKqU,aAAc,EACnBrU,KAAK4E,SAAW,IAClB,CAGA,kBAAWlB,GACT,OAAOqQ,EACT,CACA,sBAAWpQ,GACT,OAAOwQ,EACT,CACA,eAAW5X,GACT,OAAOqX,EACT,CAGA,IAAAlE,CAAKrT,GACH,IAAK2D,KAAK6E,QAAQlK,UAEhB,YADAkC,GAAQR,GAGV2D,KAAKsU,UACL,MAAM/0B,EAAUygB,KAAKuU,cACjBvU,KAAK6E,QAAQO,YACfvJ,GAAOtc,GAETA,EAAQ8b,UAAU5E,IAAIod,IACtB7T,KAAKwU,mBAAkB,KACrB3X,GAAQR,EAAS,GAErB,CACA,IAAAoT,CAAKpT,GACE2D,KAAK6E,QAAQlK,WAIlBqF,KAAKuU,cAAclZ,UAAU1B,OAAOka,IACpC7T,KAAKwU,mBAAkB,KACrBxU,KAAK+E,UACLlI,GAAQR,EAAS,KANjBQ,GAAQR,EAQZ,CACA,OAAA0I,GACO/E,KAAKqU,cAGV9T,GAAaC,IAAIR,KAAK4E,SAAUkP,IAChC9T,KAAK4E,SAASjL,SACdqG,KAAKqU,aAAc,EACrB,CAGA,WAAAE,GACE,IAAKvU,KAAK4E,SAAU,CAClB,MAAM6P,EAAWpvB,SAASqvB,cAAc,OACxCD,EAAST,UAAYhU,KAAK6E,QAAQmP,UAC9BhU,KAAK6E,QAAQO,YACfqP,EAASpZ,UAAU5E,IArFD,QAuFpBuJ,KAAK4E,SAAW6P,CAClB,CACA,OAAOzU,KAAK4E,QACd,CACA,iBAAAZ,CAAkBF,GAGhB,OADAA,EAAOoQ,YAAcxZ,GAAWoJ,EAAOoQ,aAChCpQ,CACT,CACA,OAAAwQ,GACE,GAAItU,KAAKqU,YACP,OAEF,MAAM90B,EAAUygB,KAAKuU,cACrBvU,KAAK6E,QAAQqP,YAAYS,OAAOp1B,GAChCghB,GAAac,GAAG9hB,EAASu0B,IAAiB,KACxCjX,GAAQmD,KAAK6E,QAAQoP,cAAc,IAErCjU,KAAKqU,aAAc,CACrB,CACA,iBAAAG,CAAkBnY,GAChBW,GAAuBX,EAAU2D,KAAKuU,cAAevU,KAAK6E,QAAQO,WACpE,EAeF,MAEMwP,GAAc,gBACdC,GAAkB,UAAUD,KAC5BE,GAAoB,cAAcF,KAGlCG,GAAmB,WACnBC,GAAY,CAChBC,WAAW,EACXC,YAAa,MAGTC,GAAgB,CACpBF,UAAW,UACXC,YAAa,WAOf,MAAME,WAAkB3R,GACtB,WAAAU,CAAYL,GACVa,QACA3E,KAAK6E,QAAU7E,KAAK6D,WAAWC,GAC/B9D,KAAKqV,WAAY,EACjBrV,KAAKsV,qBAAuB,IAC9B,CAGA,kBAAW5R,GACT,OAAOsR,EACT,CACA,sBAAWrR,GACT,OAAOwR,EACT,CACA,eAAW5Y,GACT,MAtCW,WAuCb,CAGA,QAAAgZ,GACMvV,KAAKqV,YAGLrV,KAAK6E,QAAQoQ,WACfjV,KAAK6E,QAAQqQ,YAAY5C,QAE3B/R,GAAaC,IAAInb,SAAUuvB,IAC3BrU,GAAac,GAAGhc,SAAUwvB,IAAiBzV,GAASY,KAAKwV,eAAepW,KACxEmB,GAAac,GAAGhc,SAAUyvB,IAAmB1V,GAASY,KAAKyV,eAAerW,KAC1EY,KAAKqV,WAAY,EACnB,CACA,UAAAK,GACO1V,KAAKqV,YAGVrV,KAAKqV,WAAY,EACjB9U,GAAaC,IAAInb,SAAUuvB,IAC7B,CAGA,cAAAY,CAAepW,GACb,MAAM,YACJ8V,GACElV,KAAK6E,QACT,GAAIzF,EAAM7S,SAAWlH,UAAY+Z,EAAM7S,SAAW2oB,GAAeA,EAAY1wB,SAAS4a,EAAM7S,QAC1F,OAEF,MAAM1L,EAAW+kB,GAAeU,kBAAkB4O,GAC1B,IAApBr0B,EAAS6P,OACXwkB,EAAY5C,QACHtS,KAAKsV,uBAAyBP,GACvCl0B,EAASA,EAAS6P,OAAS,GAAG4hB,QAE9BzxB,EAAS,GAAGyxB,OAEhB,CACA,cAAAmD,CAAerW,GA1ED,QA2ERA,EAAMtiB,MAGVkjB,KAAKsV,qBAAuBlW,EAAMuW,SAAWZ,GA7EzB,UA8EtB,EAeF,MAAMa,GAAyB,oDACzBC,GAA0B,cAC1BC,GAAmB,gBACnBC,GAAkB,eAMxB,MAAMC,GACJ,WAAA7R,GACEnE,KAAK4E,SAAWvf,SAAS6G,IAC3B,CAGA,QAAA+pB,GAEE,MAAMC,EAAgB7wB,SAASC,gBAAgBuC,YAC/C,OAAO1F,KAAKoC,IAAI3E,OAAOu2B,WAAaD,EACtC,CACA,IAAAzG,GACE,MAAM5rB,EAAQmc,KAAKiW,WACnBjW,KAAKoW,mBAELpW,KAAKqW,sBAAsBrW,KAAK4E,SAAUkR,IAAkBQ,GAAmBA,EAAkBzyB,IAEjGmc,KAAKqW,sBAAsBT,GAAwBE,IAAkBQ,GAAmBA,EAAkBzyB,IAC1Gmc,KAAKqW,sBAAsBR,GAAyBE,IAAiBO,GAAmBA,EAAkBzyB,GAC5G,CACA,KAAAwO,GACE2N,KAAKuW,wBAAwBvW,KAAK4E,SAAU,YAC5C5E,KAAKuW,wBAAwBvW,KAAK4E,SAAUkR,IAC5C9V,KAAKuW,wBAAwBX,GAAwBE,IACrD9V,KAAKuW,wBAAwBV,GAAyBE,GACxD,CACA,aAAAS,GACE,OAAOxW,KAAKiW,WAAa,CAC3B,CAGA,gBAAAG,GACEpW,KAAKyW,sBAAsBzW,KAAK4E,SAAU,YAC1C5E,KAAK4E,SAAS7jB,MAAM+K,SAAW,QACjC,CACA,qBAAAuqB,CAAsBtc,EAAU2c,EAAera,GAC7C,MAAMsa,EAAiB3W,KAAKiW,WAS5BjW,KAAK4W,2BAA2B7c,GARHxa,IAC3B,GAAIA,IAAYygB,KAAK4E,UAAYhlB,OAAOu2B,WAAa52B,EAAQsI,YAAc8uB,EACzE,OAEF3W,KAAKyW,sBAAsBl3B,EAASm3B,GACpC,MAAMJ,EAAkB12B,OAAOqF,iBAAiB1F,GAASub,iBAAiB4b,GAC1En3B,EAAQwB,MAAM81B,YAAYH,EAAe,GAAGra,EAASkB,OAAOC,WAAW8Y,QAAsB,GAGjG,CACA,qBAAAG,CAAsBl3B,EAASm3B,GAC7B,MAAMI,EAAcv3B,EAAQwB,MAAM+Z,iBAAiB4b,GAC/CI,GACF9T,GAAYC,iBAAiB1jB,EAASm3B,EAAeI,EAEzD,CACA,uBAAAP,CAAwBxc,EAAU2c,GAWhC1W,KAAK4W,2BAA2B7c,GAVHxa,IAC3B,MAAM5B,EAAQqlB,GAAYQ,iBAAiBjkB,EAASm3B,GAEtC,OAAV/4B,GAIJqlB,GAAYE,oBAAoB3jB,EAASm3B,GACzCn3B,EAAQwB,MAAM81B,YAAYH,EAAe/4B,IAJvC4B,EAAQwB,MAAMg2B,eAAeL,EAIgB,GAGnD,CACA,0BAAAE,CAA2B7c,EAAUid,GACnC,GAAI,GAAUjd,GACZid,EAASjd,QAGX,IAAK,MAAMkd,KAAOrR,GAAezT,KAAK4H,EAAUiG,KAAK4E,UACnDoS,EAASC,EAEb,EAeF,MAEMC,GAAc,YAGdC,GAAe,OAAOD,KACtBE,GAAyB,gBAAgBF,KACzCG,GAAiB,SAASH,KAC1BI,GAAe,OAAOJ,KACtBK,GAAgB,QAAQL,KACxBM,GAAiB,SAASN,KAC1BO,GAAsB,gBAAgBP,KACtCQ,GAA0B,oBAAoBR,KAC9CS,GAA0B,kBAAkBT,KAC5CU,GAAyB,QAAQV,cACjCW,GAAkB,aAElBC,GAAoB,OACpBC,GAAoB,eAKpBC,GAAY,CAChBvD,UAAU,EACVnC,OAAO,EACPzH,UAAU,GAENoN,GAAgB,CACpBxD,SAAU,mBACVnC,MAAO,UACPzH,SAAU,WAOZ,MAAMqN,WAAcxT,GAClB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKmY,QAAUvS,GAAeC,QArBV,gBAqBmC7F,KAAK4E,UAC5D5E,KAAKoY,UAAYpY,KAAKqY,sBACtBrY,KAAKsY,WAAatY,KAAKuY,uBACvBvY,KAAKwP,UAAW,EAChBxP,KAAKgP,kBAAmB,EACxBhP,KAAKwY,WAAa,IAAIxC,GACtBhW,KAAK0L,oBACP,CAGA,kBAAWhI,GACT,OAAOsU,EACT,CACA,sBAAWrU,GACT,OAAOsU,EACT,CACA,eAAW1b,GACT,MA1DW,OA2Db,CAGA,MAAAmL,CAAO5H,GACL,OAAOE,KAAKwP,SAAWxP,KAAKyP,OAASzP,KAAK0P,KAAK5P,EACjD,CACA,IAAA4P,CAAK5P,GACCE,KAAKwP,UAAYxP,KAAKgP,kBAGRzO,GAAaqB,QAAQ5B,KAAK4E,SAAU0S,GAAc,CAClExX,kBAEYkC,mBAGdhC,KAAKwP,UAAW,EAChBxP,KAAKgP,kBAAmB,EACxBhP,KAAKwY,WAAW/I,OAChBpqB,SAAS6G,KAAKmP,UAAU5E,IAAIohB,IAC5B7X,KAAKyY,gBACLzY,KAAKoY,UAAU1I,MAAK,IAAM1P,KAAK0Y,aAAa5Y,KAC9C,CACA,IAAA2P,GACOzP,KAAKwP,WAAYxP,KAAKgP,mBAGTzO,GAAaqB,QAAQ5B,KAAK4E,SAAUuS,IACxCnV,mBAGdhC,KAAKwP,UAAW,EAChBxP,KAAKgP,kBAAmB,EACxBhP,KAAKsY,WAAW5C,aAChB1V,KAAK4E,SAASvJ,UAAU1B,OAAOme,IAC/B9X,KAAKmF,gBAAe,IAAMnF,KAAK2Y,cAAc3Y,KAAK4E,SAAU5E,KAAK6N,gBACnE,CACA,OAAA9I,GACExE,GAAaC,IAAI5gB,OAAQs3B,IACzB3W,GAAaC,IAAIR,KAAKmY,QAASjB,IAC/BlX,KAAKoY,UAAUrT,UACf/E,KAAKsY,WAAW5C,aAChB/Q,MAAMI,SACR,CACA,YAAA6T,GACE5Y,KAAKyY,eACP,CAGA,mBAAAJ,GACE,OAAO,IAAIjE,GAAS,CAClBzZ,UAAWmG,QAAQd,KAAK6E,QAAQ4P,UAEhCrP,WAAYpF,KAAK6N,eAErB,CACA,oBAAA0K,GACE,OAAO,IAAInD,GAAU,CACnBF,YAAalV,KAAK4E,UAEtB,CACA,YAAA8T,CAAa5Y,GAENza,SAAS6G,KAAK1H,SAASwb,KAAK4E,WAC/Bvf,SAAS6G,KAAKyoB,OAAO3U,KAAK4E,UAE5B5E,KAAK4E,SAAS7jB,MAAM6wB,QAAU,QAC9B5R,KAAK4E,SAASzjB,gBAAgB,eAC9B6e,KAAK4E,SAASxjB,aAAa,cAAc,GACzC4e,KAAK4E,SAASxjB,aAAa,OAAQ,UACnC4e,KAAK4E,SAASnZ,UAAY,EAC1B,MAAMotB,EAAYjT,GAAeC,QA7GT,cA6GsC7F,KAAKmY,SAC/DU,IACFA,EAAUptB,UAAY,GAExBoQ,GAAOmE,KAAK4E,UACZ5E,KAAK4E,SAASvJ,UAAU5E,IAAIqhB,IAU5B9X,KAAKmF,gBATsB,KACrBnF,KAAK6E,QAAQyN,OACftS,KAAKsY,WAAW/C,WAElBvV,KAAKgP,kBAAmB,EACxBzO,GAAaqB,QAAQ5B,KAAK4E,SAAU2S,GAAe,CACjDzX,iBACA,GAEoCE,KAAKmY,QAASnY,KAAK6N,cAC7D,CACA,kBAAAnC,GACEnL,GAAac,GAAGrB,KAAK4E,SAAU+S,IAAyBvY,IAhJvC,WAiJXA,EAAMtiB,MAGNkjB,KAAK6E,QAAQgG,SACf7K,KAAKyP,OAGPzP,KAAK8Y,6BAA4B,IAEnCvY,GAAac,GAAGzhB,OAAQ43B,IAAgB,KAClCxX,KAAKwP,WAAaxP,KAAKgP,kBACzBhP,KAAKyY,eACP,IAEFlY,GAAac,GAAGrB,KAAK4E,SAAU8S,IAAyBtY,IAEtDmB,GAAae,IAAItB,KAAK4E,SAAU6S,IAAqBsB,IAC/C/Y,KAAK4E,WAAaxF,EAAM7S,QAAUyT,KAAK4E,WAAamU,EAAOxsB,SAGjC,WAA1ByT,KAAK6E,QAAQ4P,SAIbzU,KAAK6E,QAAQ4P,UACfzU,KAAKyP,OAJLzP,KAAK8Y,6BAKP,GACA,GAEN,CACA,UAAAH,GACE3Y,KAAK4E,SAAS7jB,MAAM6wB,QAAU,OAC9B5R,KAAK4E,SAASxjB,aAAa,eAAe,GAC1C4e,KAAK4E,SAASzjB,gBAAgB,cAC9B6e,KAAK4E,SAASzjB,gBAAgB,QAC9B6e,KAAKgP,kBAAmB,EACxBhP,KAAKoY,UAAU3I,MAAK,KAClBpqB,SAAS6G,KAAKmP,UAAU1B,OAAOke,IAC/B7X,KAAKgZ,oBACLhZ,KAAKwY,WAAWnmB,QAChBkO,GAAaqB,QAAQ5B,KAAK4E,SAAUyS,GAAe,GAEvD,CACA,WAAAxJ,GACE,OAAO7N,KAAK4E,SAASvJ,UAAU7W,SAjLT,OAkLxB,CACA,0BAAAs0B,GAEE,GADkBvY,GAAaqB,QAAQ5B,KAAK4E,SAAUwS,IACxCpV,iBACZ,OAEF,MAAMiX,EAAqBjZ,KAAK4E,SAASvX,aAAehI,SAASC,gBAAgBsC,aAC3EsxB,EAAmBlZ,KAAK4E,SAAS7jB,MAAMiL,UAEpB,WAArBktB,GAAiClZ,KAAK4E,SAASvJ,UAAU7W,SAASuzB,MAGjEkB,IACHjZ,KAAK4E,SAAS7jB,MAAMiL,UAAY,UAElCgU,KAAK4E,SAASvJ,UAAU5E,IAAIshB,IAC5B/X,KAAKmF,gBAAe,KAClBnF,KAAK4E,SAASvJ,UAAU1B,OAAOoe,IAC/B/X,KAAKmF,gBAAe,KAClBnF,KAAK4E,SAAS7jB,MAAMiL,UAAYktB,CAAgB,GAC/ClZ,KAAKmY,QAAQ,GACfnY,KAAKmY,SACRnY,KAAK4E,SAAS0N,QAChB,CAMA,aAAAmG,GACE,MAAMQ,EAAqBjZ,KAAK4E,SAASvX,aAAehI,SAASC,gBAAgBsC,aAC3E+uB,EAAiB3W,KAAKwY,WAAWvC,WACjCkD,EAAoBxC,EAAiB,EAC3C,GAAIwC,IAAsBF,EAAoB,CAC5C,MAAMn3B,EAAWma,KAAU,cAAgB,eAC3C+D,KAAK4E,SAAS7jB,MAAMe,GAAY,GAAG60B,KACrC,CACA,IAAKwC,GAAqBF,EAAoB,CAC5C,MAAMn3B,EAAWma,KAAU,eAAiB,cAC5C+D,KAAK4E,SAAS7jB,MAAMe,GAAY,GAAG60B,KACrC,CACF,CACA,iBAAAqC,GACEhZ,KAAK4E,SAAS7jB,MAAMq4B,YAAc,GAClCpZ,KAAK4E,SAAS7jB,MAAMs4B,aAAe,EACrC,CAGA,sBAAO5c,CAAgBqH,EAAQhE,GAC7B,OAAOE,KAAKuH,MAAK,WACf,MAAMld,EAAO6tB,GAAM5S,oBAAoBtF,KAAM8D,GAC7C,GAAsB,iBAAXA,EAAX,CAGA,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,GAAQhE,EAJb,CAKF,GACF,EAOFS,GAAac,GAAGhc,SAAUuyB,GA9OK,4BA8O2C,SAAUxY,GAClF,MAAM7S,EAASqZ,GAAec,uBAAuB1G,MACjD,CAAC,IAAK,QAAQoB,SAASpB,KAAKgH,UAC9B5H,EAAMkD,iBAER/B,GAAae,IAAI/U,EAAQ+qB,IAAcgC,IACjCA,EAAUtX,kBAIdzB,GAAae,IAAI/U,EAAQ8qB,IAAgB,KACnC1c,GAAUqF,OACZA,KAAKsS,OACP,GACA,IAIJ,MAAMiH,EAAc3T,GAAeC,QAnQb,eAoQlB0T,GACFrB,GAAM7S,YAAYkU,GAAa9J,OAEpByI,GAAM5S,oBAAoB/Y,GAClCmb,OAAO1H,KACd,IACA4G,GAAqBsR,IAMrB/b,GAAmB+b,IAcnB,MAEMsB,GAAc,gBACdC,GAAiB,YACjBC,GAAwB,OAAOF,KAAcC,KAE7CE,GAAoB,OACpBC,GAAuB,UACvBC,GAAoB,SAEpBC,GAAgB,kBAChBC,GAAe,OAAOP,KACtBQ,GAAgB,QAAQR,KACxBS,GAAe,OAAOT,KACtBU,GAAuB,gBAAgBV,KACvCW,GAAiB,SAASX,KAC1BY,GAAe,SAASZ,KACxBa,GAAyB,QAAQb,KAAcC,KAC/Ca,GAAwB,kBAAkBd,KAE1Ce,GAAY,CAChB9F,UAAU,EACV5J,UAAU,EACVpgB,QAAQ,GAEJ+vB,GAAgB,CACpB/F,SAAU,mBACV5J,SAAU,UACVpgB,OAAQ,WAOV,MAAMgwB,WAAkB/V,GACtB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKwP,UAAW,EAChBxP,KAAKoY,UAAYpY,KAAKqY,sBACtBrY,KAAKsY,WAAatY,KAAKuY,uBACvBvY,KAAK0L,oBACP,CAGA,kBAAWhI,GACT,OAAO6W,EACT,CACA,sBAAW5W,GACT,OAAO6W,EACT,CACA,eAAWje,GACT,MApDW,WAqDb,CAGA,MAAAmL,CAAO5H,GACL,OAAOE,KAAKwP,SAAWxP,KAAKyP,OAASzP,KAAK0P,KAAK5P,EACjD,CACA,IAAA4P,CAAK5P,GACCE,KAAKwP,UAGSjP,GAAaqB,QAAQ5B,KAAK4E,SAAUmV,GAAc,CAClEja,kBAEYkC,mBAGdhC,KAAKwP,UAAW,EAChBxP,KAAKoY,UAAU1I,OACV1P,KAAK6E,QAAQpa,SAChB,IAAIurB,IAAkBvG,OAExBzP,KAAK4E,SAASxjB,aAAa,cAAc,GACzC4e,KAAK4E,SAASxjB,aAAa,OAAQ,UACnC4e,KAAK4E,SAASvJ,UAAU5E,IAAImjB,IAW5B5Z,KAAKmF,gBAVoB,KAClBnF,KAAK6E,QAAQpa,SAAUuV,KAAK6E,QAAQ4P,UACvCzU,KAAKsY,WAAW/C,WAElBvV,KAAK4E,SAASvJ,UAAU5E,IAAIkjB,IAC5B3Z,KAAK4E,SAASvJ,UAAU1B,OAAOigB,IAC/BrZ,GAAaqB,QAAQ5B,KAAK4E,SAAUoV,GAAe,CACjDla,iBACA,GAEkCE,KAAK4E,UAAU,GACvD,CACA,IAAA6K,GACOzP,KAAKwP,WAGQjP,GAAaqB,QAAQ5B,KAAK4E,SAAUqV,IACxCjY,mBAGdhC,KAAKsY,WAAW5C,aAChB1V,KAAK4E,SAAS8V,OACd1a,KAAKwP,UAAW,EAChBxP,KAAK4E,SAASvJ,UAAU5E,IAAIojB,IAC5B7Z,KAAKoY,UAAU3I,OAUfzP,KAAKmF,gBAToB,KACvBnF,KAAK4E,SAASvJ,UAAU1B,OAAOggB,GAAmBE,IAClD7Z,KAAK4E,SAASzjB,gBAAgB,cAC9B6e,KAAK4E,SAASzjB,gBAAgB,QACzB6e,KAAK6E,QAAQpa,SAChB,IAAIurB,IAAkB3jB,QAExBkO,GAAaqB,QAAQ5B,KAAK4E,SAAUuV,GAAe,GAEfna,KAAK4E,UAAU,IACvD,CACA,OAAAG,GACE/E,KAAKoY,UAAUrT,UACf/E,KAAKsY,WAAW5C,aAChB/Q,MAAMI,SACR,CAGA,mBAAAsT,GACE,MASM1d,EAAYmG,QAAQd,KAAK6E,QAAQ4P,UACvC,OAAO,IAAIL,GAAS,CAClBJ,UA3HsB,qBA4HtBrZ,YACAyK,YAAY,EACZ8O,YAAalU,KAAK4E,SAAS7f,WAC3BkvB,cAAetZ,EAfK,KACU,WAA1BqF,KAAK6E,QAAQ4P,SAIjBzU,KAAKyP,OAHHlP,GAAaqB,QAAQ5B,KAAK4E,SAAUsV,GAG3B,EAUgC,MAE/C,CACA,oBAAA3B,GACE,OAAO,IAAInD,GAAU,CACnBF,YAAalV,KAAK4E,UAEtB,CACA,kBAAA8G,GACEnL,GAAac,GAAGrB,KAAK4E,SAAU0V,IAAuBlb,IA5IvC,WA6ITA,EAAMtiB,MAGNkjB,KAAK6E,QAAQgG,SACf7K,KAAKyP,OAGPlP,GAAaqB,QAAQ5B,KAAK4E,SAAUsV,IAAqB,GAE7D,CAGA,sBAAOzd,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAOowB,GAAUnV,oBAAoBtF,KAAM8D,GACjD,GAAsB,iBAAXA,EAAX,CAGA,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,GAAQ9D,KAJb,CAKF,GACF,EAOFO,GAAac,GAAGhc,SAAUg1B,GA7JK,gCA6J2C,SAAUjb,GAClF,MAAM7S,EAASqZ,GAAec,uBAAuB1G,MAIrD,GAHI,CAAC,IAAK,QAAQoB,SAASpB,KAAKgH,UAC9B5H,EAAMkD,iBAEJpH,GAAW8E,MACb,OAEFO,GAAae,IAAI/U,EAAQ4tB,IAAgB,KAEnCxf,GAAUqF,OACZA,KAAKsS,OACP,IAIF,MAAMiH,EAAc3T,GAAeC,QAAQiU,IACvCP,GAAeA,IAAgBhtB,GACjCkuB,GAAUpV,YAAYkU,GAAa9J,OAExBgL,GAAUnV,oBAAoB/Y,GACtCmb,OAAO1H,KACd,IACAO,GAAac,GAAGzhB,OAAQ85B,IAAuB,KAC7C,IAAK,MAAM3f,KAAY6L,GAAezT,KAAK2nB,IACzCW,GAAUnV,oBAAoBvL,GAAU2V,MAC1C,IAEFnP,GAAac,GAAGzhB,OAAQw6B,IAAc,KACpC,IAAK,MAAM76B,KAAWqmB,GAAezT,KAAK,gDACG,UAAvClN,iBAAiB1F,GAASiC,UAC5Bi5B,GAAUnV,oBAAoB/lB,GAASkwB,MAE3C,IAEF7I,GAAqB6T,IAMrBte,GAAmBse,IAUnB,MACME,GAAmB,CAEvB,IAAK,CAAC,QAAS,MAAO,KAAM,OAAQ,OAHP,kBAI7B9pB,EAAG,CAAC,SAAU,OAAQ,QAAS,OAC/B+pB,KAAM,GACN9pB,EAAG,GACH+pB,GAAI,GACJC,IAAK,GACLC,KAAM,GACNC,IAAK,GACLC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJnqB,EAAG,GACHub,IAAK,CAAC,MAAO,SAAU,MAAO,QAAS,QAAS,UAChD6O,GAAI,GACJC,GAAI,GACJC,EAAG,GACHC,IAAK,GACLC,EAAG,GACHC,MAAO,GACPC,KAAM,GACNC,IAAK,GACLC,IAAK,GACLC,OAAQ,GACRC,EAAG,GACHC,GAAI,IAIAC,GAAgB,IAAI/lB,IAAI,CAAC,aAAc,OAAQ,OAAQ,WAAY,WAAY,SAAU,MAAO,eAShGgmB,GAAmB,0DACnBC,GAAmB,CAACx6B,EAAWy6B,KACnC,MAAMC,EAAgB16B,EAAUvC,SAASC,cACzC,OAAI+8B,EAAqBpb,SAASqb,IAC5BJ,GAAc1lB,IAAI8lB,IACb3b,QAAQwb,GAAiBj5B,KAAKtB,EAAU26B,YAM5CF,EAAqBr2B,QAAOw2B,GAAkBA,aAA0BpY,SAAQ9R,MAAKmqB,GAASA,EAAMv5B,KAAKo5B,IAAe,EA0C3HI,GAAY,CAChBC,UAAWnC,GACXoC,QAAS,CAAC,EAEVC,WAAY,GACZnwB,MAAM,EACNowB,UAAU,EACVC,WAAY,KACZC,SAAU,eAENC,GAAgB,CACpBN,UAAW,SACXC,QAAS,SACTC,WAAY,oBACZnwB,KAAM,UACNowB,SAAU,UACVC,WAAY,kBACZC,SAAU,UAENE,GAAqB,CACzBC,MAAO,iCACPvjB,SAAU,oBAOZ,MAAMwjB,WAAwB9Z,GAC5B,WAAAU,CAAYL,GACVa,QACA3E,KAAK6E,QAAU7E,KAAK6D,WAAWC,EACjC,CAGA,kBAAWJ,GACT,OAAOmZ,EACT,CACA,sBAAWlZ,GACT,OAAOyZ,EACT,CACA,eAAW7gB,GACT,MA3CW,iBA4Cb,CAGA,UAAAihB,GACE,OAAOxgC,OAAOmiB,OAAOa,KAAK6E,QAAQkY,SAASj6B,KAAIghB,GAAU9D,KAAKyd,yBAAyB3Z,KAAS3d,OAAO2a,QACzG,CACA,UAAA4c,GACE,OAAO1d,KAAKwd,aAAa9sB,OAAS,CACpC,CACA,aAAAitB,CAAcZ,GAMZ,OALA/c,KAAK4d,cAAcb,GACnB/c,KAAK6E,QAAQkY,QAAU,IAClB/c,KAAK6E,QAAQkY,WACbA,GAEE/c,IACT,CACA,MAAA6d,GACE,MAAMC,EAAkBz4B,SAASqvB,cAAc,OAC/CoJ,EAAgBC,UAAY/d,KAAKge,eAAehe,KAAK6E,QAAQsY,UAC7D,IAAK,MAAOpjB,EAAUkkB,KAASjhC,OAAOmkB,QAAQnB,KAAK6E,QAAQkY,SACzD/c,KAAKke,YAAYJ,EAAiBG,EAAMlkB,GAE1C,MAAMojB,EAAWW,EAAgBhY,SAAS,GACpCkX,EAAahd,KAAKyd,yBAAyBzd,KAAK6E,QAAQmY,YAI9D,OAHIA,GACFG,EAAS9hB,UAAU5E,OAAOumB,EAAW96B,MAAM,MAEtCi7B,CACT,CAGA,gBAAAlZ,CAAiBH,GACfa,MAAMV,iBAAiBH,GACvB9D,KAAK4d,cAAc9Z,EAAOiZ,QAC5B,CACA,aAAAa,CAAcO,GACZ,IAAK,MAAOpkB,EAAUgjB,KAAY//B,OAAOmkB,QAAQgd,GAC/CxZ,MAAMV,iBAAiB,CACrBlK,WACAujB,MAAOP,GACNM,GAEP,CACA,WAAAa,CAAYf,EAAUJ,EAAShjB,GAC7B,MAAMqkB,EAAkBxY,GAAeC,QAAQ9L,EAAUojB,GACpDiB,KAGLrB,EAAU/c,KAAKyd,yBAAyBV,IAKpC,GAAUA,GACZ/c,KAAKqe,sBAAsB3jB,GAAWqiB,GAAUqB,GAG9Cpe,KAAK6E,QAAQhY,KACfuxB,EAAgBL,UAAY/d,KAAKge,eAAejB,GAGlDqB,EAAgBE,YAAcvB,EAX5BqB,EAAgBzkB,SAYpB,CACA,cAAAqkB,CAAeG,GACb,OAAOne,KAAK6E,QAAQoY,SApJxB,SAAsBsB,EAAYzB,EAAW0B,GAC3C,IAAKD,EAAW7tB,OACd,OAAO6tB,EAET,GAAIC,GAAgD,mBAArBA,EAC7B,OAAOA,EAAiBD,GAE1B,MACME,GADY,IAAI7+B,OAAO8+B,WACKC,gBAAgBJ,EAAY,aACxD19B,EAAW,GAAGlC,UAAU8/B,EAAgBvyB,KAAKkU,iBAAiB,MACpE,IAAK,MAAM7gB,KAAWsB,EAAU,CAC9B,MAAM+9B,EAAcr/B,EAAQC,SAASC,cACrC,IAAKzC,OAAO4D,KAAKk8B,GAAW1b,SAASwd,GAAc,CACjDr/B,EAAQoa,SACR,QACF,CACA,MAAMklB,EAAgB,GAAGlgC,UAAUY,EAAQ0B,YACrC69B,EAAoB,GAAGngC,OAAOm+B,EAAU,MAAQ,GAAIA,EAAU8B,IAAgB,IACpF,IAAK,MAAM78B,KAAa88B,EACjBtC,GAAiBx6B,EAAW+8B,IAC/Bv/B,EAAQ4B,gBAAgBY,EAAUvC,SAGxC,CACA,OAAOi/B,EAAgBvyB,KAAK6xB,SAC9B,CA2HmCgB,CAAaZ,EAAKne,KAAK6E,QAAQiY,UAAW9c,KAAK6E,QAAQqY,YAAciB,CACtG,CACA,wBAAAV,CAAyBU,GACvB,OAAOthB,GAAQshB,EAAK,CAACne,MACvB,CACA,qBAAAqe,CAAsB9+B,EAAS6+B,GAC7B,GAAIpe,KAAK6E,QAAQhY,KAGf,OAFAuxB,EAAgBL,UAAY,QAC5BK,EAAgBzJ,OAAOp1B,GAGzB6+B,EAAgBE,YAAc/+B,EAAQ++B,WACxC,EAeF,MACMU,GAAwB,IAAI1oB,IAAI,CAAC,WAAY,YAAa,eAC1D2oB,GAAoB,OAEpBC,GAAoB,OAEpBC,GAAiB,SACjBC,GAAmB,gBACnBC,GAAgB,QAChBC,GAAgB,QAahBC,GAAgB,CACpBC,KAAM,OACNC,IAAK,MACLC,MAAOzjB,KAAU,OAAS,QAC1B0jB,OAAQ,SACRC,KAAM3jB,KAAU,QAAU,QAEtB4jB,GAAY,CAChB/C,UAAWnC,GACXmF,WAAW,EACX7xB,SAAU,kBACV8xB,WAAW,EACXC,YAAa,GACbC,MAAO,EACPjwB,mBAAoB,CAAC,MAAO,QAAS,SAAU,QAC/CnD,MAAM,EACN7E,OAAQ,CAAC,EAAG,GACZtJ,UAAW,MACXmzB,aAAc,KACdoL,UAAU,EACVC,WAAY,KACZnjB,UAAU,EACVojB,SAAU,+GACV+C,MAAO,GACPte,QAAS,eAELue,GAAgB,CACpBrD,UAAW,SACXgD,UAAW,UACX7xB,SAAU,mBACV8xB,UAAW,2BACXC,YAAa,oBACbC,MAAO,kBACPjwB,mBAAoB,QACpBnD,KAAM,UACN7E,OAAQ,0BACRtJ,UAAW,oBACXmzB,aAAc,yBACdoL,SAAU,UACVC,WAAY,kBACZnjB,SAAU,mBACVojB,SAAU,SACV+C,MAAO,4BACPte,QAAS,UAOX,MAAMwe,WAAgB1b,GACpB,WAAAP,CAAY5kB,EAASukB,GACnB,QAAsB,IAAX,EACT,MAAM,IAAIU,UAAU,+DAEtBG,MAAMplB,EAASukB,GAGf9D,KAAKqgB,YAAa,EAClBrgB,KAAKsgB,SAAW,EAChBtgB,KAAKugB,WAAa,KAClBvgB,KAAKwgB,eAAiB,CAAC,EACvBxgB,KAAKgS,QAAU,KACfhS,KAAKygB,iBAAmB,KACxBzgB,KAAK0gB,YAAc,KAGnB1gB,KAAK2gB,IAAM,KACX3gB,KAAK4gB,gBACA5gB,KAAK6E,QAAQ9K,UAChBiG,KAAK6gB,WAET,CAGA,kBAAWnd,GACT,OAAOmc,EACT,CACA,sBAAWlc,GACT,OAAOwc,EACT,CACA,eAAW5jB,GACT,MAxGW,SAyGb,CAGA,MAAAukB,GACE9gB,KAAKqgB,YAAa,CACpB,CACA,OAAAU,GACE/gB,KAAKqgB,YAAa,CACpB,CACA,aAAAW,GACEhhB,KAAKqgB,YAAcrgB,KAAKqgB,UAC1B,CACA,MAAA3Y,GACO1H,KAAKqgB,aAGVrgB,KAAKwgB,eAAeS,OAASjhB,KAAKwgB,eAAeS,MAC7CjhB,KAAKwP,WACPxP,KAAKkhB,SAGPlhB,KAAKmhB,SACP,CACA,OAAApc,GACEgI,aAAa/M,KAAKsgB,UAClB/f,GAAaC,IAAIR,KAAK4E,SAAS5J,QAAQmkB,IAAiBC,GAAkBpf,KAAKohB,mBAC3EphB,KAAK4E,SAASpJ,aAAa,2BAC7BwE,KAAK4E,SAASxjB,aAAa,QAAS4e,KAAK4E,SAASpJ,aAAa,2BAEjEwE,KAAKqhB,iBACL1c,MAAMI,SACR,CACA,IAAA2K,GACE,GAAoC,SAAhC1P,KAAK4E,SAAS7jB,MAAM6wB,QACtB,MAAM,IAAIhO,MAAM,uCAElB,IAAM5D,KAAKshB,mBAAoBthB,KAAKqgB,WAClC,OAEF,MAAM/G,EAAY/Y,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAlItD,SAoIX+b,GADa9lB,GAAeuE,KAAK4E,WACL5E,KAAK4E,SAAS9kB,cAAcwF,iBAAiBd,SAASwb,KAAK4E,UAC7F,GAAI0U,EAAUtX,mBAAqBuf,EACjC,OAIFvhB,KAAKqhB,iBACL,MAAMV,EAAM3gB,KAAKwhB,iBACjBxhB,KAAK4E,SAASxjB,aAAa,mBAAoBu/B,EAAInlB,aAAa,OAChE,MAAM,UACJukB,GACE/f,KAAK6E,QAYT,GAXK7E,KAAK4E,SAAS9kB,cAAcwF,gBAAgBd,SAASwb,KAAK2gB,OAC7DZ,EAAUpL,OAAOgM,GACjBpgB,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAhJpC,cAkJnBxF,KAAKgS,QAAUhS,KAAKqS,cAAcsO,GAClCA,EAAItlB,UAAU5E,IAAIyoB,IAMd,iBAAkB75B,SAASC,gBAC7B,IAAK,MAAM/F,IAAW,GAAGZ,UAAU0G,SAAS6G,KAAK4Z,UAC/CvF,GAAac,GAAG9hB,EAAS,YAAaqc,IAU1CoE,KAAKmF,gBAPY,KACf5E,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAhKrC,WAiKQ,IAApBxF,KAAKugB,YACPvgB,KAAKkhB,SAEPlhB,KAAKugB,YAAa,CAAK,GAEKvgB,KAAK2gB,IAAK3gB,KAAK6N,cAC/C,CACA,IAAA4B,GACE,GAAKzP,KAAKwP,aAGQjP,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UA/KtD,SAgLHxD,iBAAd,CAQA,GALYhC,KAAKwhB,iBACbnmB,UAAU1B,OAAOulB,IAIjB,iBAAkB75B,SAASC,gBAC7B,IAAK,MAAM/F,IAAW,GAAGZ,UAAU0G,SAAS6G,KAAK4Z,UAC/CvF,GAAaC,IAAIjhB,EAAS,YAAaqc,IAG3CoE,KAAKwgB,eAA4B,OAAI,EACrCxgB,KAAKwgB,eAAelB,KAAiB,EACrCtf,KAAKwgB,eAAenB,KAAiB,EACrCrf,KAAKugB,WAAa,KAYlBvgB,KAAKmF,gBAVY,KACXnF,KAAKyhB,yBAGJzhB,KAAKugB,YACRvgB,KAAKqhB,iBAEPrhB,KAAK4E,SAASzjB,gBAAgB,oBAC9Bof,GAAaqB,QAAQ5B,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAzMpC,WAyM8D,GAEnDxF,KAAK2gB,IAAK3gB,KAAK6N,cA1B7C,CA2BF,CACA,MAAA9iB,GACMiV,KAAKgS,SACPhS,KAAKgS,QAAQjnB,QAEjB,CAGA,cAAAu2B,GACE,OAAOxgB,QAAQd,KAAK0hB,YACtB,CACA,cAAAF,GAIE,OAHKxhB,KAAK2gB,MACR3gB,KAAK2gB,IAAM3gB,KAAK2hB,kBAAkB3hB,KAAK0gB,aAAe1gB,KAAK4hB,2BAEtD5hB,KAAK2gB,GACd,CACA,iBAAAgB,CAAkB5E,GAChB,MAAM4D,EAAM3gB,KAAK6hB,oBAAoB9E,GAASc,SAG9C,IAAK8C,EACH,OAAO,KAETA,EAAItlB,UAAU1B,OAAOslB,GAAmBC,IAExCyB,EAAItlB,UAAU5E,IAAI,MAAMuJ,KAAKmE,YAAY5H,aACzC,MAAMulB,EAvuGKC,KACb,GACEA,GAAU5/B,KAAK6/B,MA/BH,IA+BS7/B,KAAK8/B,gBACnB58B,SAAS68B,eAAeH,IACjC,OAAOA,CAAM,EAmuGGI,CAAOniB,KAAKmE,YAAY5H,MAAM1c,WAK5C,OAJA8gC,EAAIv/B,aAAa,KAAM0gC,GACnB9hB,KAAK6N,eACP8S,EAAItlB,UAAU5E,IAAIwoB,IAEb0B,CACT,CACA,UAAAyB,CAAWrF,GACT/c,KAAK0gB,YAAc3D,EACf/c,KAAKwP,aACPxP,KAAKqhB,iBACLrhB,KAAK0P,OAET,CACA,mBAAAmS,CAAoB9E,GAYlB,OAXI/c,KAAKygB,iBACPzgB,KAAKygB,iBAAiB9C,cAAcZ,GAEpC/c,KAAKygB,iBAAmB,IAAIlD,GAAgB,IACvCvd,KAAK6E,QAGRkY,UACAC,WAAYhd,KAAKyd,yBAAyBzd,KAAK6E,QAAQmb,eAGpDhgB,KAAKygB,gBACd,CACA,sBAAAmB,GACE,MAAO,CACL,iBAA0B5hB,KAAK0hB,YAEnC,CACA,SAAAA,GACE,OAAO1hB,KAAKyd,yBAAyBzd,KAAK6E,QAAQqb,QAAUlgB,KAAK4E,SAASpJ,aAAa,yBACzF,CAGA,4BAAA6mB,CAA6BjjB,GAC3B,OAAOY,KAAKmE,YAAYmB,oBAAoBlG,EAAMW,eAAgBC,KAAKsiB,qBACzE,CACA,WAAAzU,GACE,OAAO7N,KAAK6E,QAAQib,WAAa9f,KAAK2gB,KAAO3gB,KAAK2gB,IAAItlB,UAAU7W,SAASy6B,GAC3E,CACA,QAAAzP,GACE,OAAOxP,KAAK2gB,KAAO3gB,KAAK2gB,IAAItlB,UAAU7W,SAAS06B,GACjD,CACA,aAAA7M,CAAcsO,GACZ,MAAMjiC,EAAYme,GAAQmD,KAAK6E,QAAQnmB,UAAW,CAACshB,KAAM2gB,EAAK3gB,KAAK4E,WAC7D2d,EAAahD,GAAc7gC,EAAU+lB,eAC3C,OAAO,GAAoBzE,KAAK4E,SAAU+b,EAAK3gB,KAAKyS,iBAAiB8P,GACvE,CACA,UAAA1P,GACE,MAAM,OACJ7qB,GACEgY,KAAK6E,QACT,MAAsB,iBAAX7c,EACFA,EAAO9F,MAAM,KAAKY,KAAInF,GAAS4f,OAAO6P,SAASzvB,EAAO,MAEzC,mBAAXqK,EACF8qB,GAAc9qB,EAAO8qB,EAAY9S,KAAK4E,UAExC5c,CACT,CACA,wBAAAy1B,CAAyBU,GACvB,OAAOthB,GAAQshB,EAAK,CAACne,KAAK4E,UAC5B,CACA,gBAAA6N,CAAiB8P,GACf,MAAMxP,EAAwB,CAC5Br0B,UAAW6jC,EACXnsB,UAAW,CAAC,CACV9V,KAAM,OACNmB,QAAS,CACPuO,mBAAoBgQ,KAAK6E,QAAQ7U,qBAElC,CACD1P,KAAM,SACNmB,QAAS,CACPuG,OAAQgY,KAAK6S,eAEd,CACDvyB,KAAM,kBACNmB,QAAS,CACPwM,SAAU+R,KAAK6E,QAAQ5W,WAExB,CACD3N,KAAM,QACNmB,QAAS,CACPlC,QAAS,IAAIygB,KAAKmE,YAAY5H,eAE/B,CACDjc,KAAM,kBACNC,SAAS,EACTC,MAAO,aACPC,GAAI4J,IAGF2V,KAAKwhB,iBAAiBpgC,aAAa,wBAAyBiJ,EAAK1J,MAAMjC,UAAU,KAIvF,MAAO,IACFq0B,KACAlW,GAAQmD,KAAK6E,QAAQgN,aAAc,CAACkB,IAE3C,CACA,aAAA6N,GACE,MAAM4B,EAAWxiB,KAAK6E,QAAQjD,QAAQ1f,MAAM,KAC5C,IAAK,MAAM0f,KAAW4gB,EACpB,GAAgB,UAAZ5gB,EACFrB,GAAac,GAAGrB,KAAK4E,SAAU5E,KAAKmE,YAAYqB,UAjVlC,SAiV4DxF,KAAK6E,QAAQ9K,UAAUqF,IAC/EY,KAAKqiB,6BAA6BjjB,GAC1CsI,QAAQ,SAEb,GA3VU,WA2VN9F,EAA4B,CACrC,MAAM6gB,EAAU7gB,IAAYyd,GAAgBrf,KAAKmE,YAAYqB,UAnV5C,cAmV0ExF,KAAKmE,YAAYqB,UArV5F,WAsVVkd,EAAW9gB,IAAYyd,GAAgBrf,KAAKmE,YAAYqB,UAnV7C,cAmV2ExF,KAAKmE,YAAYqB,UArV5F,YAsVjBjF,GAAac,GAAGrB,KAAK4E,SAAU6d,EAASziB,KAAK6E,QAAQ9K,UAAUqF,IAC7D,MAAM+T,EAAUnT,KAAKqiB,6BAA6BjjB,GAClD+T,EAAQqN,eAA8B,YAAfphB,EAAMqB,KAAqB6e,GAAgBD,KAAiB,EACnFlM,EAAQgO,QAAQ,IAElB5gB,GAAac,GAAGrB,KAAK4E,SAAU8d,EAAU1iB,KAAK6E,QAAQ9K,UAAUqF,IAC9D,MAAM+T,EAAUnT,KAAKqiB,6BAA6BjjB,GAClD+T,EAAQqN,eAA8B,aAAfphB,EAAMqB,KAAsB6e,GAAgBD,IAAiBlM,EAAQvO,SAASpgB,SAAS4a,EAAMU,eACpHqT,EAAQ+N,QAAQ,GAEpB,CAEFlhB,KAAKohB,kBAAoB,KACnBphB,KAAK4E,UACP5E,KAAKyP,MACP,EAEFlP,GAAac,GAAGrB,KAAK4E,SAAS5J,QAAQmkB,IAAiBC,GAAkBpf,KAAKohB,kBAChF,CACA,SAAAP,GACE,MAAMX,EAAQlgB,KAAK4E,SAASpJ,aAAa,SACpC0kB,IAGAlgB,KAAK4E,SAASpJ,aAAa,eAAkBwE,KAAK4E,SAAS0Z,YAAY3Y,QAC1E3F,KAAK4E,SAASxjB,aAAa,aAAc8+B,GAE3ClgB,KAAK4E,SAASxjB,aAAa,yBAA0B8+B,GACrDlgB,KAAK4E,SAASzjB,gBAAgB,SAChC,CACA,MAAAggC,GACMnhB,KAAKwP,YAAcxP,KAAKugB,WAC1BvgB,KAAKugB,YAAa,GAGpBvgB,KAAKugB,YAAa,EAClBvgB,KAAK2iB,aAAY,KACX3iB,KAAKugB,YACPvgB,KAAK0P,MACP,GACC1P,KAAK6E,QAAQob,MAAMvQ,MACxB,CACA,MAAAwR,GACMlhB,KAAKyhB,yBAGTzhB,KAAKugB,YAAa,EAClBvgB,KAAK2iB,aAAY,KACV3iB,KAAKugB,YACRvgB,KAAKyP,MACP,GACCzP,KAAK6E,QAAQob,MAAMxQ,MACxB,CACA,WAAAkT,CAAY/kB,EAASglB,GACnB7V,aAAa/M,KAAKsgB,UAClBtgB,KAAKsgB,SAAWziB,WAAWD,EAASglB,EACtC,CACA,oBAAAnB,GACE,OAAOzkC,OAAOmiB,OAAOa,KAAKwgB,gBAAgBpf,UAAS,EACrD,CACA,UAAAyC,CAAWC,GACT,MAAM+e,EAAiB7f,GAAYG,kBAAkBnD,KAAK4E,UAC1D,IAAK,MAAMke,KAAiB9lC,OAAO4D,KAAKiiC,GAClC7D,GAAsBroB,IAAImsB,WACrBD,EAAeC,GAU1B,OAPAhf,EAAS,IACJ+e,KACmB,iBAAX/e,GAAuBA,EAASA,EAAS,CAAC,GAEvDA,EAAS9D,KAAK+D,gBAAgBD,GAC9BA,EAAS9D,KAAKgE,kBAAkBF,GAChC9D,KAAKiE,iBAAiBH,GACfA,CACT,CACA,iBAAAE,CAAkBF,GAchB,OAbAA,EAAOic,WAAiC,IAArBjc,EAAOic,UAAsB16B,SAAS6G,KAAOwO,GAAWoJ,EAAOic,WACtD,iBAAjBjc,EAAOmc,QAChBnc,EAAOmc,MAAQ,CACbvQ,KAAM5L,EAAOmc,MACbxQ,KAAM3L,EAAOmc,QAGW,iBAAjBnc,EAAOoc,QAChBpc,EAAOoc,MAAQpc,EAAOoc,MAAMrgC,YAEA,iBAAnBikB,EAAOiZ,UAChBjZ,EAAOiZ,QAAUjZ,EAAOiZ,QAAQl9B,YAE3BikB,CACT,CACA,kBAAAwe,GACE,MAAMxe,EAAS,CAAC,EAChB,IAAK,MAAOhnB,EAAKa,KAAUX,OAAOmkB,QAAQnB,KAAK6E,SACzC7E,KAAKmE,YAAYT,QAAQ5mB,KAASa,IACpCmmB,EAAOhnB,GAAOa,GASlB,OANAmmB,EAAO/J,UAAW,EAClB+J,EAAOlC,QAAU,SAKVkC,CACT,CACA,cAAAud,GACMrhB,KAAKgS,UACPhS,KAAKgS,QAAQhZ,UACbgH,KAAKgS,QAAU,MAEbhS,KAAK2gB,MACP3gB,KAAK2gB,IAAIhnB,SACTqG,KAAK2gB,IAAM,KAEf,CAGA,sBAAOlkB,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAO+1B,GAAQ9a,oBAAoBtF,KAAM8D,GAC/C,GAAsB,iBAAXA,EAAX,CAGA,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,EAOF3H,GAAmBikB,IAcnB,MAGM2C,GAAY,IACb3C,GAAQ1c,QACXqZ,QAAS,GACT/0B,OAAQ,CAAC,EAAG,GACZtJ,UAAW,QACXy+B,SAAU,8IACVvb,QAAS,SAELohB,GAAgB,IACjB5C,GAAQzc,YACXoZ,QAAS,kCAOX,MAAMkG,WAAgB7C,GAEpB,kBAAW1c,GACT,OAAOqf,EACT,CACA,sBAAWpf,GACT,OAAOqf,EACT,CACA,eAAWzmB,GACT,MA7BW,SA8Bb,CAGA,cAAA+kB,GACE,OAAOthB,KAAK0hB,aAAe1hB,KAAKkjB,aAClC,CAGA,sBAAAtB,GACE,MAAO,CACL,kBAAkB5hB,KAAK0hB,YACvB,gBAAoB1hB,KAAKkjB,cAE7B,CACA,WAAAA,GACE,OAAOljB,KAAKyd,yBAAyBzd,KAAK6E,QAAQkY,QACpD,CAGA,sBAAOtgB,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAO44B,GAAQ3d,oBAAoBtF,KAAM8D,GAC/C,GAAsB,iBAAXA,EAAX,CAGA,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,EAOF3H,GAAmB8mB,IAcnB,MAEME,GAAc,gBAEdC,GAAiB,WAAWD,KAC5BE,GAAc,QAAQF,KACtBG,GAAwB,OAAOH,cAE/BI,GAAsB,SAEtBC,GAAwB,SAExBC,GAAqB,YAGrBC,GAAsB,GAAGD,mBAA+CA,uBAGxEE,GAAY,CAChB37B,OAAQ,KAER47B,WAAY,eACZC,cAAc,EACdt3B,OAAQ,KACRu3B,UAAW,CAAC,GAAK,GAAK,IAElBC,GAAgB,CACpB/7B,OAAQ,gBAER47B,WAAY,SACZC,aAAc,UACdt3B,OAAQ,UACRu3B,UAAW,SAOb,MAAME,WAAkBtf,GACtB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GAGf9D,KAAKikB,aAAe,IAAI/yB,IACxB8O,KAAKkkB,oBAAsB,IAAIhzB,IAC/B8O,KAAKmkB,aAA6D,YAA9Cl/B,iBAAiB+a,KAAK4E,UAAU5Y,UAA0B,KAAOgU,KAAK4E,SAC1F5E,KAAKokB,cAAgB,KACrBpkB,KAAKqkB,UAAY,KACjBrkB,KAAKskB,oBAAsB,CACzBC,gBAAiB,EACjBC,gBAAiB,GAEnBxkB,KAAKykB,SACP,CAGA,kBAAW/gB,GACT,OAAOigB,EACT,CACA,sBAAWhgB,GACT,OAAOogB,EACT,CACA,eAAWxnB,GACT,MAhEW,WAiEb,CAGA,OAAAkoB,GACEzkB,KAAK0kB,mCACL1kB,KAAK2kB,2BACD3kB,KAAKqkB,UACPrkB,KAAKqkB,UAAUO,aAEf5kB,KAAKqkB,UAAYrkB,KAAK6kB,kBAExB,IAAK,MAAMC,KAAW9kB,KAAKkkB,oBAAoB/kB,SAC7Ca,KAAKqkB,UAAUU,QAAQD,EAE3B,CACA,OAAA/f,GACE/E,KAAKqkB,UAAUO,aACfjgB,MAAMI,SACR,CAGA,iBAAAf,CAAkBF,GAShB,OAPAA,EAAOvX,OAASmO,GAAWoJ,EAAOvX,SAAWlH,SAAS6G,KAGtD4X,EAAO8f,WAAa9f,EAAO9b,OAAS,GAAG8b,EAAO9b,oBAAsB8b,EAAO8f,WAC3C,iBAArB9f,EAAOggB,YAChBhgB,EAAOggB,UAAYhgB,EAAOggB,UAAU5hC,MAAM,KAAKY,KAAInF,GAAS4f,OAAOC,WAAW7f,MAEzEmmB,CACT,CACA,wBAAA6gB,GACO3kB,KAAK6E,QAAQgf,eAKlBtjB,GAAaC,IAAIR,KAAK6E,QAAQtY,OAAQ82B,IACtC9iB,GAAac,GAAGrB,KAAK6E,QAAQtY,OAAQ82B,GAAaG,IAAuBpkB,IACvE,MAAM4lB,EAAoBhlB,KAAKkkB,oBAAoB/mC,IAAIiiB,EAAM7S,OAAOtB,MACpE,GAAI+5B,EAAmB,CACrB5lB,EAAMkD,iBACN,MAAM3G,EAAOqE,KAAKmkB,cAAgBvkC,OAC5BmE,EAASihC,EAAkB3gC,UAAY2b,KAAK4E,SAASvgB,UAC3D,GAAIsX,EAAKspB,SAKP,YAJAtpB,EAAKspB,SAAS,CACZtjC,IAAKoC,EACLmhC,SAAU,WAMdvpB,EAAKlQ,UAAY1H,CACnB,KAEJ,CACA,eAAA8gC,GACE,MAAMpjC,EAAU,CACdka,KAAMqE,KAAKmkB,aACXL,UAAW9jB,KAAK6E,QAAQif,UACxBF,WAAY5jB,KAAK6E,QAAQ+e,YAE3B,OAAO,IAAIuB,sBAAqBhkB,GAAWnB,KAAKolB,kBAAkBjkB,IAAU1f,EAC9E,CAGA,iBAAA2jC,CAAkBjkB,GAChB,MAAMkkB,EAAgB/H,GAAStd,KAAKikB,aAAa9mC,IAAI,IAAImgC,EAAM/wB,OAAO4N,MAChEob,EAAW+H,IACftd,KAAKskB,oBAAoBC,gBAAkBjH,EAAM/wB,OAAOlI,UACxD2b,KAAKslB,SAASD,EAAc/H,GAAO,EAE/BkH,GAAmBxkB,KAAKmkB,cAAgB9+B,SAASC,iBAAiBmG,UAClE85B,EAAkBf,GAAmBxkB,KAAKskB,oBAAoBE,gBACpExkB,KAAKskB,oBAAoBE,gBAAkBA,EAC3C,IAAK,MAAMlH,KAASnc,EAAS,CAC3B,IAAKmc,EAAMkI,eAAgB,CACzBxlB,KAAKokB,cAAgB,KACrBpkB,KAAKylB,kBAAkBJ,EAAc/H,IACrC,QACF,CACA,MAAMoI,EAA2BpI,EAAM/wB,OAAOlI,WAAa2b,KAAKskB,oBAAoBC,gBAEpF,GAAIgB,GAAmBG,GAGrB,GAFAnQ,EAAS+H,IAEJkH,EACH,YAMCe,GAAoBG,GACvBnQ,EAAS+H,EAEb,CACF,CACA,gCAAAoH,GACE1kB,KAAKikB,aAAe,IAAI/yB,IACxB8O,KAAKkkB,oBAAsB,IAAIhzB,IAC/B,MAAMy0B,EAAc/f,GAAezT,KAAKqxB,GAAuBxjB,KAAK6E,QAAQtY,QAC5E,IAAK,MAAMq5B,KAAUD,EAAa,CAEhC,IAAKC,EAAO36B,MAAQiQ,GAAW0qB,GAC7B,SAEF,MAAMZ,EAAoBpf,GAAeC,QAAQggB,UAAUD,EAAO36B,MAAO+U,KAAK4E,UAG1EjK,GAAUqqB,KACZhlB,KAAKikB,aAAalyB,IAAI8zB,UAAUD,EAAO36B,MAAO26B,GAC9C5lB,KAAKkkB,oBAAoBnyB,IAAI6zB,EAAO36B,KAAM+5B,GAE9C,CACF,CACA,QAAAM,CAAS/4B,GACHyT,KAAKokB,gBAAkB73B,IAG3ByT,KAAKylB,kBAAkBzlB,KAAK6E,QAAQtY,QACpCyT,KAAKokB,cAAgB73B,EACrBA,EAAO8O,UAAU5E,IAAI8sB,IACrBvjB,KAAK8lB,iBAAiBv5B,GACtBgU,GAAaqB,QAAQ5B,KAAK4E,SAAUwe,GAAgB,CAClDtjB,cAAevT,IAEnB,CACA,gBAAAu5B,CAAiBv5B,GAEf,GAAIA,EAAO8O,UAAU7W,SA9LQ,iBA+L3BohB,GAAeC,QArLc,mBAqLsBtZ,EAAOyO,QAtLtC,cAsLkEK,UAAU5E,IAAI8sB,SAGtG,IAAK,MAAMwC,KAAangB,GAAeI,QAAQzZ,EA9LnB,qBAiM1B,IAAK,MAAMxJ,KAAQ6iB,GAAeM,KAAK6f,EAAWrC,IAChD3gC,EAAKsY,UAAU5E,IAAI8sB,GAGzB,CACA,iBAAAkC,CAAkBhhC,GAChBA,EAAO4W,UAAU1B,OAAO4pB,IACxB,MAAMyC,EAAcpgB,GAAezT,KAAK,GAAGqxB,MAAyBD,KAAuB9+B,GAC3F,IAAK,MAAM9E,KAAQqmC,EACjBrmC,EAAK0b,UAAU1B,OAAO4pB,GAE1B,CAGA,sBAAO9mB,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAO25B,GAAU1e,oBAAoBtF,KAAM8D,GACjD,GAAsB,iBAAXA,EAAX,CAGA,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,EAOFvD,GAAac,GAAGzhB,OAAQ0jC,IAAuB,KAC7C,IAAK,MAAM2C,KAAOrgB,GAAezT,KApOT,0BAqOtB6xB,GAAU1e,oBAAoB2gB,EAChC,IAOF9pB,GAAmB6nB,IAcnB,MAEMkC,GAAc,UACdC,GAAe,OAAOD,KACtBE,GAAiB,SAASF,KAC1BG,GAAe,OAAOH,KACtBI,GAAgB,QAAQJ,KACxBK,GAAuB,QAAQL,KAC/BM,GAAgB,UAAUN,KAC1BO,GAAsB,OAAOP,KAC7BQ,GAAiB,YACjBC,GAAkB,aAClBC,GAAe,UACfC,GAAiB,YACjBC,GAAW,OACXC,GAAU,MACVC,GAAoB,SACpBC,GAAoB,OACpBC,GAAoB,OAEpBC,GAA2B,mBAE3BC,GAA+B,QAAQD,MAIvCE,GAAuB,2EACvBC,GAAsB,YAFOF,uBAAiDA,mBAA6CA,OAE/EC,KAC5CE,GAA8B,IAAIP,8BAA6CA,+BAA8CA,4BAMnI,MAAMQ,WAAY9iB,GAChB,WAAAP,CAAY5kB,GACVolB,MAAMplB,GACNygB,KAAKiS,QAAUjS,KAAK4E,SAAS5J,QAdN,uCAelBgF,KAAKiS,UAOVjS,KAAKynB,sBAAsBznB,KAAKiS,QAASjS,KAAK0nB,gBAC9CnnB,GAAac,GAAGrB,KAAK4E,SAAU4hB,IAAepnB,GAASY,KAAK0M,SAAStN,KACvE,CAGA,eAAW7C,GACT,MAnDW,KAoDb,CAGA,IAAAmT,GAEE,MAAMiY,EAAY3nB,KAAK4E,SACvB,GAAI5E,KAAK4nB,cAAcD,GACrB,OAIF,MAAME,EAAS7nB,KAAK8nB,iBACdC,EAAYF,EAAStnB,GAAaqB,QAAQimB,EAAQ1B,GAAc,CACpErmB,cAAe6nB,IACZ,KACapnB,GAAaqB,QAAQ+lB,EAAWtB,GAAc,CAC9DvmB,cAAe+nB,IAEH7lB,kBAAoB+lB,GAAaA,EAAU/lB,mBAGzDhC,KAAKgoB,YAAYH,EAAQF,GACzB3nB,KAAKioB,UAAUN,EAAWE,GAC5B,CAGA,SAAAI,CAAU1oC,EAAS2oC,GACZ3oC,IAGLA,EAAQ8b,UAAU5E,IAAIuwB,IACtBhnB,KAAKioB,UAAUriB,GAAec,uBAAuBnnB,IAcrDygB,KAAKmF,gBAZY,KACsB,QAAjC5lB,EAAQic,aAAa,SAIzBjc,EAAQ4B,gBAAgB,YACxB5B,EAAQ6B,aAAa,iBAAiB,GACtC4e,KAAKmoB,gBAAgB5oC,GAAS,GAC9BghB,GAAaqB,QAAQriB,EAAS+mC,GAAe,CAC3CxmB,cAAeooB,KAPf3oC,EAAQ8b,UAAU5E,IAAIywB,GAQtB,GAE0B3nC,EAASA,EAAQ8b,UAAU7W,SAASyiC,KACpE,CACA,WAAAe,CAAYzoC,EAAS2oC,GACd3oC,IAGLA,EAAQ8b,UAAU1B,OAAOqtB,IACzBznC,EAAQm7B,OACR1a,KAAKgoB,YAAYpiB,GAAec,uBAAuBnnB,IAcvDygB,KAAKmF,gBAZY,KACsB,QAAjC5lB,EAAQic,aAAa,SAIzBjc,EAAQ6B,aAAa,iBAAiB,GACtC7B,EAAQ6B,aAAa,WAAY,MACjC4e,KAAKmoB,gBAAgB5oC,GAAS,GAC9BghB,GAAaqB,QAAQriB,EAAS6mC,GAAgB,CAC5CtmB,cAAeooB,KAPf3oC,EAAQ8b,UAAU1B,OAAOutB,GAQzB,GAE0B3nC,EAASA,EAAQ8b,UAAU7W,SAASyiC,KACpE,CACA,QAAAva,CAAStN,GACP,IAAK,CAACsnB,GAAgBC,GAAiBC,GAAcC,GAAgBC,GAAUC,IAAS3lB,SAAShC,EAAMtiB,KACrG,OAEFsiB,EAAMuU,kBACNvU,EAAMkD,iBACN,MAAMwD,EAAW9F,KAAK0nB,eAAevhC,QAAO5G,IAAY2b,GAAW3b,KACnE,IAAI6oC,EACJ,GAAI,CAACtB,GAAUC,IAAS3lB,SAAShC,EAAMtiB,KACrCsrC,EAAoBtiB,EAAS1G,EAAMtiB,MAAQgqC,GAAW,EAAIhhB,EAASpV,OAAS,OACvE,CACL,MAAM2c,EAAS,CAACsZ,GAAiBE,IAAgBzlB,SAAShC,EAAMtiB,KAChEsrC,EAAoBtqB,GAAqBgI,EAAU1G,EAAM7S,OAAQ8gB,GAAQ,EAC3E,CACI+a,IACFA,EAAkB9V,MAAM,CACtB+V,eAAe,IAEjBb,GAAIliB,oBAAoB8iB,GAAmB1Y,OAE/C,CACA,YAAAgY,GAEE,OAAO9hB,GAAezT,KAAKm1B,GAAqBtnB,KAAKiS,QACvD,CACA,cAAA6V,GACE,OAAO9nB,KAAK0nB,eAAev1B,MAAKzN,GAASsb,KAAK4nB,cAAcljC,MAAW,IACzE,CACA,qBAAA+iC,CAAsBhjC,EAAQqhB,GAC5B9F,KAAKsoB,yBAAyB7jC,EAAQ,OAAQ,WAC9C,IAAK,MAAMC,KAASohB,EAClB9F,KAAKuoB,6BAA6B7jC,EAEtC,CACA,4BAAA6jC,CAA6B7jC,GAC3BA,EAAQsb,KAAKwoB,iBAAiB9jC,GAC9B,MAAM+jC,EAAWzoB,KAAK4nB,cAAcljC,GAC9BgkC,EAAY1oB,KAAK2oB,iBAAiBjkC,GACxCA,EAAMtD,aAAa,gBAAiBqnC,GAChCC,IAAchkC,GAChBsb,KAAKsoB,yBAAyBI,EAAW,OAAQ,gBAE9CD,GACH/jC,EAAMtD,aAAa,WAAY,MAEjC4e,KAAKsoB,yBAAyB5jC,EAAO,OAAQ,OAG7Csb,KAAK4oB,mCAAmClkC,EAC1C,CACA,kCAAAkkC,CAAmClkC,GACjC,MAAM6H,EAASqZ,GAAec,uBAAuBhiB,GAChD6H,IAGLyT,KAAKsoB,yBAAyB/7B,EAAQ,OAAQ,YAC1C7H,EAAMyV,IACR6F,KAAKsoB,yBAAyB/7B,EAAQ,kBAAmB,GAAG7H,EAAMyV,MAEtE,CACA,eAAAguB,CAAgB5oC,EAASspC,GACvB,MAAMH,EAAY1oB,KAAK2oB,iBAAiBppC,GACxC,IAAKmpC,EAAUrtB,UAAU7W,SApKN,YAqKjB,OAEF,MAAMkjB,EAAS,CAAC3N,EAAUia,KACxB,MAAMz0B,EAAUqmB,GAAeC,QAAQ9L,EAAU2uB,GAC7CnpC,GACFA,EAAQ8b,UAAUqM,OAAOsM,EAAW6U,EACtC,EAEFnhB,EAAOyf,GAA0BH,IACjCtf,EA5K2B,iBA4KIwf,IAC/BwB,EAAUtnC,aAAa,gBAAiBynC,EAC1C,CACA,wBAAAP,CAAyB/oC,EAASwC,EAAWpE,GACtC4B,EAAQgc,aAAaxZ,IACxBxC,EAAQ6B,aAAaW,EAAWpE,EAEpC,CACA,aAAAiqC,CAAczY,GACZ,OAAOA,EAAK9T,UAAU7W,SAASwiC,GACjC,CAGA,gBAAAwB,CAAiBrZ,GACf,OAAOA,EAAKpJ,QAAQuhB,IAAuBnY,EAAOvJ,GAAeC,QAAQyhB,GAAqBnY,EAChG,CAGA,gBAAAwZ,CAAiBxZ,GACf,OAAOA,EAAKnU,QA5LO,gCA4LoBmU,CACzC,CAGA,sBAAO1S,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAOm9B,GAAIliB,oBAAoBtF,MACrC,GAAsB,iBAAX8D,EAAX,CAGA,QAAqB/K,IAAjB1O,EAAKyZ,IAAyBA,EAAOrC,WAAW,MAAmB,gBAAXqC,EAC1D,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,IAJL,CAKF,GACF,EAOFvD,GAAac,GAAGhc,SAAUkhC,GAAsBc,IAAsB,SAAUjoB,GAC1E,CAAC,IAAK,QAAQgC,SAASpB,KAAKgH,UAC9B5H,EAAMkD,iBAEJpH,GAAW8E,OAGfwnB,GAAIliB,oBAAoBtF,MAAM0P,MAChC,IAKAnP,GAAac,GAAGzhB,OAAQ6mC,IAAqB,KAC3C,IAAK,MAAMlnC,KAAWqmB,GAAezT,KAAKo1B,IACxCC,GAAIliB,oBAAoB/lB,EAC1B,IAMF4c,GAAmBqrB,IAcnB,MAEMxiB,GAAY,YACZ8jB,GAAkB,YAAY9jB,KAC9B+jB,GAAiB,WAAW/jB,KAC5BgkB,GAAgB,UAAUhkB,KAC1BikB,GAAiB,WAAWjkB,KAC5BkkB,GAAa,OAAOlkB,KACpBmkB,GAAe,SAASnkB,KACxBokB,GAAa,OAAOpkB,KACpBqkB,GAAc,QAAQrkB,KAEtBskB,GAAkB,OAClBC,GAAkB,OAClBC,GAAqB,UACrB7lB,GAAc,CAClBmc,UAAW,UACX2J,SAAU,UACVxJ,MAAO,UAEHvc,GAAU,CACdoc,WAAW,EACX2J,UAAU,EACVxJ,MAAO,KAOT,MAAMyJ,WAAchlB,GAClB,WAAAP,CAAY5kB,EAASukB,GACnBa,MAAMplB,EAASukB,GACf9D,KAAKsgB,SAAW,KAChBtgB,KAAK2pB,sBAAuB,EAC5B3pB,KAAK4pB,yBAA0B,EAC/B5pB,KAAK4gB,eACP,CAGA,kBAAWld,GACT,OAAOA,EACT,CACA,sBAAWC,GACT,OAAOA,EACT,CACA,eAAWpH,GACT,MA/CS,OAgDX,CAGA,IAAAmT,GACoBnP,GAAaqB,QAAQ5B,KAAK4E,SAAUwkB,IACxCpnB,mBAGdhC,KAAK6pB,gBACD7pB,KAAK6E,QAAQib,WACf9f,KAAK4E,SAASvJ,UAAU5E,IA/CN,QAsDpBuJ,KAAK4E,SAASvJ,UAAU1B,OAAO2vB,IAC/BztB,GAAOmE,KAAK4E,UACZ5E,KAAK4E,SAASvJ,UAAU5E,IAAI8yB,GAAiBC,IAC7CxpB,KAAKmF,gBARY,KACfnF,KAAK4E,SAASvJ,UAAU1B,OAAO6vB,IAC/BjpB,GAAaqB,QAAQ5B,KAAK4E,SAAUykB,IACpCrpB,KAAK8pB,oBAAoB,GAKG9pB,KAAK4E,SAAU5E,KAAK6E,QAAQib,WAC5D,CACA,IAAArQ,GACOzP,KAAK+pB,YAGQxpB,GAAaqB,QAAQ5B,KAAK4E,SAAUskB,IACxClnB,mBAQdhC,KAAK4E,SAASvJ,UAAU5E,IAAI+yB,IAC5BxpB,KAAKmF,gBANY,KACfnF,KAAK4E,SAASvJ,UAAU5E,IAAI6yB,IAC5BtpB,KAAK4E,SAASvJ,UAAU1B,OAAO6vB,GAAoBD,IACnDhpB,GAAaqB,QAAQ5B,KAAK4E,SAAUukB,GAAa,GAGrBnpB,KAAK4E,SAAU5E,KAAK6E,QAAQib,YAC5D,CACA,OAAA/a,GACE/E,KAAK6pB,gBACD7pB,KAAK+pB,WACP/pB,KAAK4E,SAASvJ,UAAU1B,OAAO4vB,IAEjC5kB,MAAMI,SACR,CACA,OAAAglB,GACE,OAAO/pB,KAAK4E,SAASvJ,UAAU7W,SAAS+kC,GAC1C,CAIA,kBAAAO,GACO9pB,KAAK6E,QAAQ4kB,WAGdzpB,KAAK2pB,sBAAwB3pB,KAAK4pB,0BAGtC5pB,KAAKsgB,SAAWziB,YAAW,KACzBmC,KAAKyP,MAAM,GACVzP,KAAK6E,QAAQob,QAClB,CACA,cAAA+J,CAAe5qB,EAAO6qB,GACpB,OAAQ7qB,EAAMqB,MACZ,IAAK,YACL,IAAK,WAEDT,KAAK2pB,qBAAuBM,EAC5B,MAEJ,IAAK,UACL,IAAK,WAEDjqB,KAAK4pB,wBAA0BK,EAIrC,GAAIA,EAEF,YADAjqB,KAAK6pB,gBAGP,MAAMvc,EAAclO,EAAMU,cACtBE,KAAK4E,WAAa0I,GAAetN,KAAK4E,SAASpgB,SAAS8oB,IAG5DtN,KAAK8pB,oBACP,CACA,aAAAlJ,GACErgB,GAAac,GAAGrB,KAAK4E,SAAUkkB,IAAiB1pB,GAASY,KAAKgqB,eAAe5qB,GAAO,KACpFmB,GAAac,GAAGrB,KAAK4E,SAAUmkB,IAAgB3pB,GAASY,KAAKgqB,eAAe5qB,GAAO,KACnFmB,GAAac,GAAGrB,KAAK4E,SAAUokB,IAAe5pB,GAASY,KAAKgqB,eAAe5qB,GAAO,KAClFmB,GAAac,GAAGrB,KAAK4E,SAAUqkB,IAAgB7pB,GAASY,KAAKgqB,eAAe5qB,GAAO,IACrF,CACA,aAAAyqB,GACE9c,aAAa/M,KAAKsgB,UAClBtgB,KAAKsgB,SAAW,IAClB,CAGA,sBAAO7jB,CAAgBqH,GACrB,OAAO9D,KAAKuH,MAAK,WACf,MAAMld,EAAOq/B,GAAMpkB,oBAAoBtF,KAAM8D,GAC7C,GAAsB,iBAAXA,EAAqB,CAC9B,QAA4B,IAAjBzZ,EAAKyZ,GACd,MAAM,IAAIU,UAAU,oBAAoBV,MAE1CzZ,EAAKyZ,GAAQ9D,KACf,CACF,GACF,ECr0IK,SAASkqB,GAAc7tB,GACD,WAAvBhX,SAASuX,WAAyBP,IACjChX,SAASyF,iBAAiB,mBAAoBuR,EACrD,CDy0IAuK,GAAqB8iB,IAMrBvtB,GAAmButB,IEpyInBQ,IAzCA,WAC2B,GAAG93B,MAAM5U,KAChC6H,SAAS+a,iBAAiB,+BAETtd,KAAI,SAAUqnC,GAC/B,OAAO,IAAI,GAAkBA,EAAkB,CAC7ClK,MAAO,CAAEvQ,KAAM,IAAKD,KAAM,MAE9B,GACF,IAiCAya,IA5BA,WACY7kC,SAAS68B,eAAe,mBAC9Bp3B,iBAAiB,SAAS,WAC5BzF,SAAS6G,KAAKT,UAAY,EAC1BpG,SAASC,gBAAgBmG,UAAY,CACvC,GACF,IAuBAy+B,IArBA,WACE,IAAIE,EAAM/kC,SAAS68B,eAAe,mBAC9BmI,EAAShlC,SACVilC,uBAAuB,aAAa,GACpChnC,wBACH1D,OAAOkL,iBAAiB,UAAU,WAC5BkV,KAAKuqB,UAAYvqB,KAAKwqB,SAAWxqB,KAAKwqB,QAAUH,EAAOzsC,OACzDwsC,EAAIrpC,MAAM6wB,QAAU,QAEpBwY,EAAIrpC,MAAM6wB,QAAU,OAEtB5R,KAAKuqB,UAAYvqB,KAAKwqB,OACxB,GACF,IAUA5qC,OAAO6qC,UAAY","sources":["webpack://pydata_sphinx_theme/webpack/bootstrap","webpack://pydata_sphinx_theme/webpack/runtime/define property getters","webpack://pydata_sphinx_theme/webpack/runtime/hasOwnProperty shorthand","webpack://pydata_sphinx_theme/webpack/runtime/make namespace object","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/enums.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/applyStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getBasePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/math.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/userAgent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/contains.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/within.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/expandToHashMap.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/arrow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getVariation.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/computeStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/eventListeners.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/rectToClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/detectOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/flip.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/hide.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/offset.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getAltAxis.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/orderModifiers.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/createPopper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/debounce.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergeByName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper-lite.js","webpack://pydata_sphinx_theme/./node_modules/bootstrap/dist/js/bootstrap.esm.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/mixin.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/bootstrap.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref, win) {\n var x = _ref.x,\n y = _ref.y;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }, getWindow(popper)) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n });\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref) {\n var name = _ref.name,\n _ref$options = _ref.options,\n options = _ref$options === void 0 ? {} : _ref$options,\n effect = _ref.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","/*!\n * Bootstrap v5.3.2 (https://getbootstrap.com/)\n * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\nimport * as Popper from '@popperjs/core';\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map();\nconst Data = {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map());\n }\n const instanceMap = elementMap.get(element);\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n return;\n }\n instanceMap.set(key, instance);\n },\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null;\n }\n return null;\n },\n remove(element, key) {\n if (!elementMap.has(element)) {\n return;\n }\n const instanceMap = elementMap.get(element);\n instanceMap.delete(key);\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element);\n }\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1000000;\nconst MILLISECONDS_MULTIPLIER = 1000;\nconst TRANSITION_END = 'transitionend';\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`);\n }\n return selector;\n};\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`;\n }\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n};\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID);\n } while (document.getElementById(prefix));\n return prefix;\n};\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0;\n }\n\n // Get transition-duration of the element\n let {\n transitionDuration,\n transitionDelay\n } = window.getComputedStyle(element);\n const floatTransitionDuration = Number.parseFloat(transitionDuration);\n const floatTransitionDelay = Number.parseFloat(transitionDelay);\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0;\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0];\n transitionDelay = transitionDelay.split(',')[0];\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n};\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END));\n};\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false;\n }\n if (typeof object.jquery !== 'undefined') {\n object = object[0];\n }\n return typeof object.nodeType !== 'undefined';\n};\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object;\n }\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object));\n }\n return null;\n};\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false;\n }\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])');\n if (!closedDetails) {\n return elementIsVisible;\n }\n if (closedDetails !== element) {\n const summary = element.closest('summary');\n if (summary && summary.parentNode !== closedDetails) {\n return false;\n }\n if (summary === null) {\n return false;\n }\n }\n return elementIsVisible;\n};\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true;\n }\n if (element.classList.contains('disabled')) {\n return true;\n }\n if (typeof element.disabled !== 'undefined') {\n return element.disabled;\n }\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n};\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null;\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode();\n return root instanceof ShadowRoot ? root : null;\n }\n if (element instanceof ShadowRoot) {\n return element;\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null;\n }\n return findShadowRoot(element.parentNode);\n};\nconst noop = () => {};\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight; // eslint-disable-line no-unused-expressions\n};\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery;\n }\n return null;\n};\nconst DOMContentLoadedCallbacks = [];\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback();\n }\n });\n }\n DOMContentLoadedCallbacks.push(callback);\n } else {\n callback();\n }\n};\nconst isRTL = () => document.documentElement.dir === 'rtl';\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery();\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME;\n const JQUERY_NO_CONFLICT = $.fn[name];\n $.fn[name] = plugin.jQueryInterface;\n $.fn[name].Constructor = plugin;\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT;\n return plugin.jQueryInterface;\n };\n }\n });\n};\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue;\n};\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback);\n return;\n }\n const durationPadding = 5;\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n let called = false;\n const handler = ({\n target\n }) => {\n if (target !== transitionElement) {\n return;\n }\n called = true;\n transitionElement.removeEventListener(TRANSITION_END, handler);\n execute(callback);\n };\n transitionElement.addEventListener(TRANSITION_END, handler);\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement);\n }\n }, emulatedDuration);\n};\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length;\n let index = list.indexOf(activeElement);\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n }\n index += shouldGetNext ? 1 : -1;\n if (isCycleAllowed) {\n index = (index + listLength) % listLength;\n }\n return list[Math.max(0, Math.min(index, listLength - 1))];\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\nconst stripNameRegex = /\\..*/;\nconst stripUidRegex = /::\\d+$/;\nconst eventRegistry = {}; // Events storage\nlet uidEvent = 1;\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n};\nconst nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n}\nfunction getElementEvents(element) {\n const uid = makeEventUid(element);\n element.uidEvent = uid;\n eventRegistry[uid] = eventRegistry[uid] || {};\n return eventRegistry[uid];\n}\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, {\n delegateTarget: element\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn);\n }\n return fn.apply(element, [event]);\n };\n}\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector);\n for (let {\n target\n } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue;\n }\n hydrateObj(event, {\n delegateTarget: target\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn);\n }\n return fn.apply(target, [event]);\n }\n }\n };\n}\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n}\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string';\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n let typeEvent = getTypeEvent(originalTypeEvent);\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent;\n }\n return [isDelegated, callable, typeEvent];\n}\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n return fn.call(this, event);\n }\n };\n };\n callable = wrapFunction(callable);\n }\n const events = getElementEvents(element);\n const handlers = events[typeEvent] || (events[typeEvent] = {});\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff;\n return;\n }\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n fn.delegationSelector = isDelegated ? handler : null;\n fn.callable = callable;\n fn.oneOff = oneOff;\n fn.uidEvent = uid;\n handlers[uid] = fn;\n element.addEventListener(typeEvent, fn, isDelegated);\n}\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector);\n if (!fn) {\n return;\n }\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n delete events[typeEvent][fn.uidEvent];\n}\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {};\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n}\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '');\n return customEvents[event] || event;\n}\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false);\n },\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true);\n },\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n const inNamespace = typeEvent !== originalTypeEvent;\n const events = getElementEvents(element);\n const storeElementEvent = events[typeEvent] || {};\n const isNamespace = originalTypeEvent.startsWith('.');\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return;\n }\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n return;\n }\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n }\n }\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '');\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n },\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null;\n }\n const $ = getjQuery();\n const typeEvent = getTypeEvent(event);\n const inNamespace = event !== typeEvent;\n let jQueryEvent = null;\n let bubbles = true;\n let nativeDispatch = true;\n let defaultPrevented = false;\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args);\n $(element).trigger(jQueryEvent);\n bubbles = !jQueryEvent.isPropagationStopped();\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n defaultPrevented = jQueryEvent.isDefaultPrevented();\n }\n const evt = hydrateObj(new Event(event, {\n bubbles,\n cancelable: true\n }), args);\n if (defaultPrevented) {\n evt.preventDefault();\n }\n if (nativeDispatch) {\n element.dispatchEvent(evt);\n }\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault();\n }\n return evt;\n }\n};\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value;\n } catch (_unused) {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value;\n }\n });\n }\n }\n return obj;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n if (value === Number(value).toString()) {\n return Number(value);\n }\n if (value === '' || value === 'null') {\n return null;\n }\n if (typeof value !== 'string') {\n return value;\n }\n try {\n return JSON.parse(decodeURIComponent(value));\n } catch (_unused) {\n return value;\n }\n}\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n}\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n },\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n },\n getDataAttributes(element) {\n if (!element) {\n return {};\n }\n const attributes = {};\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '');\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n attributes[pureKey] = normalizeData(element.dataset[key]);\n }\n return attributes;\n },\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {};\n }\n static get DefaultType() {\n return {};\n }\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!');\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n return config;\n }\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n };\n }\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property];\n const valueType = isElement(value) ? 'element' : toType(value);\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n }\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.2';\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super();\n element = getElement(element);\n if (!element) {\n return;\n }\n this._element = element;\n this._config = this._getConfig(config);\n Data.set(this._element, this.constructor.DATA_KEY, this);\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY);\n EventHandler.off(this._element, this.constructor.EVENT_KEY);\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null;\n }\n }\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated);\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY);\n }\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n }\n static get VERSION() {\n return VERSION;\n }\n static get DATA_KEY() {\n return `bs.${this.NAME}`;\n }\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`;\n }\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target');\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href');\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n return null;\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n }\n selector = hrefAttribute && hrefAttribute !== '#' ? parseSelector(hrefAttribute.trim()) : null;\n }\n return selector;\n};\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n },\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector);\n },\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector));\n },\n parents(element, selector) {\n const parents = [];\n let ancestor = element.parentNode.closest(selector);\n while (ancestor) {\n parents.push(ancestor);\n ancestor = ancestor.parentNode.closest(selector);\n }\n return parents;\n },\n prev(element, selector) {\n let previous = element.previousElementSibling;\n while (previous) {\n if (previous.matches(selector)) {\n return [previous];\n }\n previous = previous.previousElementSibling;\n }\n return [];\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling;\n while (next) {\n if (next.matches(selector)) {\n return [next];\n }\n next = next.nextElementSibling;\n }\n return [];\n },\n focusableChildren(element) {\n const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));\n },\n getSelectorFromElement(element) {\n const selector = getSelector(element);\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null;\n }\n return null;\n },\n getElementFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.findOne(selector) : null;\n },\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.find(selector) : [];\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n const name = component.NAME;\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`);\n const instance = component.getOrCreateInstance(target);\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]();\n });\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$f = 'alert';\nconst DATA_KEY$a = 'bs.alert';\nconst EVENT_KEY$b = `.${DATA_KEY$a}`;\nconst EVENT_CLOSE = `close${EVENT_KEY$b}`;\nconst EVENT_CLOSED = `closed${EVENT_KEY$b}`;\nconst CLASS_NAME_FADE$5 = 'fade';\nconst CLASS_NAME_SHOW$8 = 'show';\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$f;\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n if (closeEvent.defaultPrevented) {\n return;\n }\n this._element.classList.remove(CLASS_NAME_SHOW$8);\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5);\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n }\n\n // Private\n _destroyElement() {\n this._element.remove();\n EventHandler.trigger(this._element, EVENT_CLOSED);\n this.dispose();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close');\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$e = 'button';\nconst DATA_KEY$9 = 'bs.button';\nconst EVENT_KEY$a = `.${DATA_KEY$9}`;\nconst DATA_API_KEY$6 = '.data-api';\nconst CLASS_NAME_ACTIVE$3 = 'active';\nconst SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle=\"button\"]';\nconst EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$e;\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3));\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this);\n if (config === 'toggle') {\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => {\n event.preventDefault();\n const button = event.target.closest(SELECTOR_DATA_TOGGLE$5);\n const data = Button.getOrCreateInstance(button);\n data.toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$d = 'swipe';\nconst EVENT_KEY$9 = '.bs.swipe';\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`;\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`;\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`;\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`;\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`;\nconst POINTER_TYPE_TOUCH = 'touch';\nconst POINTER_TYPE_PEN = 'pen';\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event';\nconst SWIPE_THRESHOLD = 40;\nconst Default$c = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n};\nconst DefaultType$c = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n};\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super();\n this._element = element;\n if (!element || !Swipe.isSupported()) {\n return;\n }\n this._config = this._getConfig(config);\n this._deltaX = 0;\n this._supportPointerEvents = Boolean(window.PointerEvent);\n this._initEvents();\n }\n\n // Getters\n static get Default() {\n return Default$c;\n }\n static get DefaultType() {\n return DefaultType$c;\n }\n static get NAME() {\n return NAME$d;\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY$9);\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX;\n return;\n }\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX;\n }\n }\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX;\n }\n this._handleSwipe();\n execute(this._config.endCallback);\n }\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n }\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX);\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return;\n }\n const direction = absDeltaX / this._deltaX;\n this._deltaX = 0;\n if (!direction) {\n return;\n }\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n }\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n }\n }\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$c = 'carousel';\nconst DATA_KEY$8 = 'bs.carousel';\nconst EVENT_KEY$8 = `.${DATA_KEY$8}`;\nconst DATA_API_KEY$5 = '.data-api';\nconst ARROW_LEFT_KEY$1 = 'ArrowLeft';\nconst ARROW_RIGHT_KEY$1 = 'ArrowRight';\nconst TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next';\nconst ORDER_PREV = 'prev';\nconst DIRECTION_LEFT = 'left';\nconst DIRECTION_RIGHT = 'right';\nconst EVENT_SLIDE = `slide${EVENT_KEY$8}`;\nconst EVENT_SLID = `slid${EVENT_KEY$8}`;\nconst EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`;\nconst EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`;\nconst EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`;\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`;\nconst EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst CLASS_NAME_CAROUSEL = 'carousel';\nconst CLASS_NAME_ACTIVE$2 = 'active';\nconst CLASS_NAME_SLIDE = 'slide';\nconst CLASS_NAME_END = 'carousel-item-end';\nconst CLASS_NAME_START = 'carousel-item-start';\nconst CLASS_NAME_NEXT = 'carousel-item-next';\nconst CLASS_NAME_PREV = 'carousel-item-prev';\nconst SELECTOR_ACTIVE = '.active';\nconst SELECTOR_ITEM = '.carousel-item';\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\nconst SELECTOR_ITEM_IMG = '.carousel-item img';\nconst SELECTOR_INDICATORS = '.carousel-indicators';\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT\n};\nconst Default$b = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n};\nconst DefaultType$b = {\n interval: '(number|boolean)',\n // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._interval = null;\n this._activeElement = null;\n this._isSliding = false;\n this.touchTimeout = null;\n this._swipeHelper = null;\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n this._addEventListeners();\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$b;\n }\n static get DefaultType() {\n return DefaultType$b;\n }\n static get NAME() {\n return NAME$c;\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT);\n }\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next();\n }\n }\n prev() {\n this._slide(ORDER_PREV);\n }\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element);\n }\n this._clearInterval();\n }\n cycle() {\n this._clearInterval();\n this._updateInterval();\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n }\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n return;\n }\n this.cycle();\n }\n to(index) {\n const items = this._getItems();\n if (index > items.length - 1 || index < 0) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n return;\n }\n const activeIndex = this._getItemIndex(this._getActive());\n if (activeIndex === index) {\n return;\n }\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n this._slide(order, items[index]);\n }\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose();\n }\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval;\n return config;\n }\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event));\n }\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause());\n EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle());\n }\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners();\n }\n }\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n }\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return;\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause();\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout);\n }\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n };\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n };\n this._swipeHelper = new Swipe(this._element, swipeConfig);\n }\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return;\n }\n const direction = KEY_TO_DIRECTION[event.key];\n if (direction) {\n event.preventDefault();\n this._slide(this._directionToOrder(direction));\n }\n }\n _getItemIndex(element) {\n return this._getItems().indexOf(element);\n }\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return;\n }\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2);\n activeIndicator.removeAttribute('aria-current');\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2);\n newActiveIndicator.setAttribute('aria-current', 'true');\n }\n }\n _updateInterval() {\n const element = this._activeElement || this._getActive();\n if (!element) {\n return;\n }\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n this._config.interval = elementInterval || this._config.defaultInterval;\n }\n _slide(order, element = null) {\n if (this._isSliding) {\n return;\n }\n const activeElement = this._getActive();\n const isNext = order === ORDER_NEXT;\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n if (nextElement === activeElement) {\n return;\n }\n const nextElementIndex = this._getItemIndex(nextElement);\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n });\n };\n const slideEvent = triggerEvent(EVENT_SLIDE);\n if (slideEvent.defaultPrevented) {\n return;\n }\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return;\n }\n const isCycling = Boolean(this._interval);\n this.pause();\n this._isSliding = true;\n this._setActiveIndicatorElement(nextElementIndex);\n this._activeElement = nextElement;\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n nextElement.classList.add(orderClassName);\n reflow(nextElement);\n activeElement.classList.add(directionalClassName);\n nextElement.classList.add(directionalClassName);\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName);\n nextElement.classList.add(CLASS_NAME_ACTIVE$2);\n activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName);\n this._isSliding = false;\n triggerEvent(EVENT_SLID);\n };\n this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n if (isCycling) {\n this.cycle();\n }\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE);\n }\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n }\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element);\n }\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval);\n this._interval = null;\n }\n }\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n }\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n }\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n }\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config);\n if (typeof config === 'number') {\n data.to(config);\n return;\n }\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return;\n }\n event.preventDefault();\n const carousel = Carousel.getOrCreateInstance(target);\n const slideIndex = this.getAttribute('data-bs-slide-to');\n if (slideIndex) {\n carousel.to(slideIndex);\n carousel._maybeEnableCycle();\n return;\n }\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next();\n carousel._maybeEnableCycle();\n return;\n }\n carousel.prev();\n carousel._maybeEnableCycle();\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel);\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$b = 'collapse';\nconst DATA_KEY$7 = 'bs.collapse';\nconst EVENT_KEY$7 = `.${DATA_KEY$7}`;\nconst DATA_API_KEY$4 = '.data-api';\nconst EVENT_SHOW$6 = `show${EVENT_KEY$7}`;\nconst EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`;\nconst EVENT_HIDE$6 = `hide${EVENT_KEY$7}`;\nconst EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`;\nconst EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`;\nconst CLASS_NAME_SHOW$7 = 'show';\nconst CLASS_NAME_COLLAPSE = 'collapse';\nconst CLASS_NAME_COLLAPSING = 'collapsing';\nconst CLASS_NAME_COLLAPSED = 'collapsed';\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\nconst WIDTH = 'width';\nconst HEIGHT = 'height';\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\nconst SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle=\"collapse\"]';\nconst Default$a = {\n parent: null,\n toggle: true\n};\nconst DefaultType$a = {\n parent: '(null|element)',\n toggle: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isTransitioning = false;\n this._triggerArray = [];\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4);\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem);\n const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem);\n }\n }\n this._initializeChildren();\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n }\n if (this._config.toggle) {\n this.toggle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$a;\n }\n static get DefaultType() {\n return DefaultType$a;\n }\n static get NAME() {\n return NAME$b;\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide();\n } else {\n this.show();\n }\n }\n show() {\n if (this._isTransitioning || this._isShown()) {\n return;\n }\n let activeChildren = [];\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n toggle: false\n }));\n }\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n for (const activeInstance of activeChildren) {\n activeInstance.hide();\n }\n const dimension = this._getDimension();\n this._element.classList.remove(CLASS_NAME_COLLAPSE);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.style[dimension] = 0;\n this._addAriaAndCollapsedClass(this._triggerArray, true);\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n this._element.style[dimension] = '';\n EventHandler.trigger(this._element, EVENT_SHOWN$6);\n };\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n const scrollSize = `scroll${capitalizedDimension}`;\n this._queueCallback(complete, this._element, true);\n this._element.style[dimension] = `${this._element[scrollSize]}px`;\n }\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n const dimension = this._getDimension();\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger);\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false);\n }\n }\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE);\n EventHandler.trigger(this._element, EVENT_HIDDEN$6);\n };\n this._element.style[dimension] = '';\n this._queueCallback(complete, this._element, true);\n }\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW$7);\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle); // Coerce string values\n config.parent = getElement(config.parent);\n return config;\n }\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n }\n _initializeChildren() {\n if (!this._config.parent) {\n return;\n }\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4);\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element);\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected));\n }\n }\n }\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent);\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n }\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return;\n }\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n element.setAttribute('aria-expanded', isOpen);\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {};\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false;\n }\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config);\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n event.preventDefault();\n }\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, {\n toggle: false\n }).toggle();\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$a = 'dropdown';\nconst DATA_KEY$6 = 'bs.dropdown';\nconst EVENT_KEY$6 = `.${DATA_KEY$6}`;\nconst DATA_API_KEY$3 = '.data-api';\nconst ESCAPE_KEY$2 = 'Escape';\nconst TAB_KEY$1 = 'Tab';\nconst ARROW_UP_KEY$1 = 'ArrowUp';\nconst ARROW_DOWN_KEY$1 = 'ArrowDown';\nconst RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE$5 = `hide${EVENT_KEY$6}`;\nconst EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`;\nconst EVENT_SHOW$5 = `show${EVENT_KEY$6}`;\nconst EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`;\nconst EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst CLASS_NAME_SHOW$6 = 'show';\nconst CLASS_NAME_DROPUP = 'dropup';\nconst CLASS_NAME_DROPEND = 'dropend';\nconst CLASS_NAME_DROPSTART = 'dropstart';\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center';\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\nconst SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`;\nconst SELECTOR_MENU = '.dropdown-menu';\nconst SELECTOR_NAVBAR = '.navbar';\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav';\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';\nconst PLACEMENT_TOPCENTER = 'top';\nconst PLACEMENT_BOTTOMCENTER = 'bottom';\nconst Default$9 = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n};\nconst DefaultType$9 = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n};\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._popper = null;\n this._parent = this._element.parentNode; // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n this._inNavbar = this._detectNavbar();\n }\n\n // Getters\n static get Default() {\n return Default$9;\n }\n static get DefaultType() {\n return DefaultType$9;\n }\n static get NAME() {\n return NAME$a;\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show();\n }\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget);\n if (showEvent.defaultPrevented) {\n return;\n }\n this._createPopper();\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n this._element.focus();\n this._element.setAttribute('aria-expanded', true);\n this._menu.classList.add(CLASS_NAME_SHOW$6);\n this._element.classList.add(CLASS_NAME_SHOW$6);\n EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget);\n }\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n this._completeHide(relatedTarget);\n }\n dispose() {\n if (this._popper) {\n this._popper.destroy();\n }\n super.dispose();\n }\n update() {\n this._inNavbar = this._detectNavbar();\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget);\n if (hideEvent.defaultPrevented) {\n return;\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n if (this._popper) {\n this._popper.destroy();\n }\n this._menu.classList.remove(CLASS_NAME_SHOW$6);\n this._element.classList.remove(CLASS_NAME_SHOW$6);\n this._element.setAttribute('aria-expanded', 'false');\n Manipulator.removeDataAttribute(this._menu, 'popper');\n EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);\n }\n _getConfig(config) {\n config = super._getConfig(config);\n if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME$a.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n }\n return config;\n }\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n }\n let referenceElement = this._element;\n if (this._config.reference === 'parent') {\n referenceElement = this._parent;\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference);\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference;\n }\n const popperConfig = this._getPopperConfig();\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig);\n }\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW$6);\n }\n _getPlacement() {\n const parentDropdown = this._parent;\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER;\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n }\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n }\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null;\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n };\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }];\n }\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _selectMenuItem({\n key,\n target\n }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element));\n if (!items.length) {\n return;\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) {\n return;\n }\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle);\n if (!context || context._config.autoClose === false) {\n continue;\n }\n const composedPath = event.composedPath();\n const isMenuTarget = composedPath.includes(context._menu);\n if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n continue;\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue;\n }\n const relatedTarget = {\n relatedTarget: context._element\n };\n if (event.type === 'click') {\n relatedTarget.clickEvent = event;\n }\n context._completeHide(relatedTarget);\n }\n }\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName);\n const isEscapeEvent = event.key === ESCAPE_KEY$2;\n const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key);\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return;\n }\n if (isInput && !isEscapeEvent) {\n return;\n }\n event.preventDefault();\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode);\n const instance = Dropdown.getOrCreateInstance(getToggleButton);\n if (isUpOrDownEvent) {\n event.stopPropagation();\n instance.show();\n instance._selectMenuItem(event);\n return;\n }\n if (instance._isShown()) {\n // else is escape and we check if it is shown\n event.stopPropagation();\n instance.hide();\n getToggleButton.focus();\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) {\n event.preventDefault();\n Dropdown.getOrCreateInstance(this).toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$9 = 'backdrop';\nconst CLASS_NAME_FADE$4 = 'fade';\nconst CLASS_NAME_SHOW$5 = 'show';\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`;\nconst Default$8 = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true,\n // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n};\n\nconst DefaultType$8 = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n};\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isAppended = false;\n this._element = null;\n }\n\n // Getters\n static get Default() {\n return Default$8;\n }\n static get DefaultType() {\n return DefaultType$8;\n }\n static get NAME() {\n return NAME$9;\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._append();\n const element = this._getElement();\n if (this._config.isAnimated) {\n reflow(element);\n }\n element.classList.add(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n execute(callback);\n });\n }\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._getElement().classList.remove(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n this.dispose();\n execute(callback);\n });\n }\n dispose() {\n if (!this._isAppended) {\n return;\n }\n EventHandler.off(this._element, EVENT_MOUSEDOWN);\n this._element.remove();\n this._isAppended = false;\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div');\n backdrop.className = this._config.className;\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE$4);\n }\n this._element = backdrop;\n }\n return this._element;\n }\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement);\n return config;\n }\n _append() {\n if (this._isAppended) {\n return;\n }\n const element = this._getElement();\n this._config.rootElement.append(element);\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback);\n });\n this._isAppended = true;\n }\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$8 = 'focustrap';\nconst DATA_KEY$5 = 'bs.focustrap';\nconst EVENT_KEY$5 = `.${DATA_KEY$5}`;\nconst EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`;\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`;\nconst TAB_KEY = 'Tab';\nconst TAB_NAV_FORWARD = 'forward';\nconst TAB_NAV_BACKWARD = 'backward';\nconst Default$7 = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n};\n\nconst DefaultType$7 = {\n autofocus: 'boolean',\n trapElement: 'element'\n};\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isActive = false;\n this._lastTabNavDirection = null;\n }\n\n // Getters\n static get Default() {\n return Default$7;\n }\n static get DefaultType() {\n return DefaultType$7;\n }\n static get NAME() {\n return NAME$8;\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return;\n }\n if (this._config.autofocus) {\n this._config.trapElement.focus();\n }\n EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event));\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n this._isActive = true;\n }\n deactivate() {\n if (!this._isActive) {\n return;\n }\n this._isActive = false;\n EventHandler.off(document, EVENT_KEY$5);\n }\n\n // Private\n _handleFocusin(event) {\n const {\n trapElement\n } = this._config;\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return;\n }\n const elements = SelectorEngine.focusableChildren(trapElement);\n if (elements.length === 0) {\n trapElement.focus();\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus();\n } else {\n elements[0].focus();\n }\n }\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return;\n }\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\nconst SELECTOR_STICKY_CONTENT = '.sticky-top';\nconst PROPERTY_PADDING = 'padding-right';\nconst PROPERTY_MARGIN = 'margin-right';\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body;\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth;\n return Math.abs(window.innerWidth - documentWidth);\n }\n hide() {\n const width = this.getWidth();\n this._disableOverFlow();\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n }\n reset() {\n this._resetElementAttributes(this._element, 'overflow');\n this._resetElementAttributes(this._element, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n }\n isOverflowing() {\n return this.getWidth() > 0;\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow');\n this._element.style.overflow = 'hidden';\n }\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth();\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return;\n }\n this._saveInitialAttribute(element, styleProperty);\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty);\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue);\n }\n }\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty);\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty);\n return;\n }\n Manipulator.removeDataAttribute(element, styleProperty);\n element.style.setProperty(styleProperty, value);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector);\n return;\n }\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel);\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$7 = 'modal';\nconst DATA_KEY$4 = 'bs.modal';\nconst EVENT_KEY$4 = `.${DATA_KEY$4}`;\nconst DATA_API_KEY$2 = '.data-api';\nconst ESCAPE_KEY$1 = 'Escape';\nconst EVENT_HIDE$4 = `hide${EVENT_KEY$4}`;\nconst EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`;\nconst EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`;\nconst EVENT_SHOW$4 = `show${EVENT_KEY$4}`;\nconst EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`;\nconst EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`;\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`;\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`;\nconst EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`;\nconst EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`;\nconst CLASS_NAME_OPEN = 'modal-open';\nconst CLASS_NAME_FADE$3 = 'fade';\nconst CLASS_NAME_SHOW$4 = 'show';\nconst CLASS_NAME_STATIC = 'modal-static';\nconst OPEN_SELECTOR$1 = '.modal.show';\nconst SELECTOR_DIALOG = '.modal-dialog';\nconst SELECTOR_MODAL_BODY = '.modal-body';\nconst SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle=\"modal\"]';\nconst Default$6 = {\n backdrop: true,\n focus: true,\n keyboard: true\n};\nconst DefaultType$6 = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._isShown = false;\n this._isTransitioning = false;\n this._scrollBar = new ScrollBarHelper();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$6;\n }\n static get DefaultType() {\n return DefaultType$6;\n }\n static get NAME() {\n return NAME$7;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._isTransitioning = true;\n this._scrollBar.hide();\n document.body.classList.add(CLASS_NAME_OPEN);\n this._adjustDialog();\n this._backdrop.show(() => this._showElement(relatedTarget));\n }\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._isShown = false;\n this._isTransitioning = true;\n this._focustrap.deactivate();\n this._element.classList.remove(CLASS_NAME_SHOW$4);\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n }\n dispose() {\n EventHandler.off(window, EVENT_KEY$4);\n EventHandler.off(this._dialog, EVENT_KEY$4);\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n handleUpdate() {\n this._adjustDialog();\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop),\n // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element);\n }\n this._element.style.display = 'block';\n this._element.removeAttribute('aria-hidden');\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.scrollTop = 0;\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n if (modalBody) {\n modalBody.scrollTop = 0;\n }\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_SHOW$4);\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate();\n }\n this._isTransitioning = false;\n EventHandler.trigger(this._element, EVENT_SHOWN$4, {\n relatedTarget\n });\n };\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => {\n if (event.key !== ESCAPE_KEY$1) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n this._triggerBackdropTransition();\n });\n EventHandler.on(window, EVENT_RESIZE$1, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog();\n }\n });\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return;\n }\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition();\n return;\n }\n if (this._config.backdrop) {\n this.hide();\n }\n });\n });\n }\n _hideModal() {\n this._element.style.display = 'none';\n this._element.setAttribute('aria-hidden', true);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n this._isTransitioning = false;\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN);\n this._resetAdjustments();\n this._scrollBar.reset();\n EventHandler.trigger(this._element, EVENT_HIDDEN$4);\n });\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE$3);\n }\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1);\n if (hideEvent.defaultPrevented) {\n return;\n }\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const initialOverflowY = this._element.style.overflowY;\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return;\n }\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden';\n }\n this._element.classList.add(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY;\n }, this._dialog);\n }, this._dialog);\n this._element.focus();\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const scrollbarWidth = this._scrollBar.getWidth();\n const isBodyOverflowing = scrollbarWidth > 0;\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n }\n _resetAdjustments() {\n this._element.style.paddingLeft = '';\n this._element.style.paddingRight = '';\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](relatedTarget);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n EventHandler.one(target, EVENT_SHOW$4, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$4, () => {\n if (isVisible(this)) {\n this.focus();\n }\n });\n });\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1);\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide();\n }\n const data = Modal.getOrCreateInstance(target);\n data.toggle(this);\n});\nenableDismissTrigger(Modal);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$6 = 'offcanvas';\nconst DATA_KEY$3 = 'bs.offcanvas';\nconst EVENT_KEY$3 = `.${DATA_KEY$3}`;\nconst DATA_API_KEY$1 = '.data-api';\nconst EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst ESCAPE_KEY = 'Escape';\nconst CLASS_NAME_SHOW$3 = 'show';\nconst CLASS_NAME_SHOWING$1 = 'showing';\nconst CLASS_NAME_HIDING = 'hiding';\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\nconst OPEN_SELECTOR = '.offcanvas.show';\nconst EVENT_SHOW$3 = `show${EVENT_KEY$3}`;\nconst EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`;\nconst EVENT_HIDE$3 = `hide${EVENT_KEY$3}`;\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`;\nconst EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`;\nconst EVENT_RESIZE = `resize${EVENT_KEY$3}`;\nconst EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`;\nconst SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle=\"offcanvas\"]';\nconst Default$5 = {\n backdrop: true,\n keyboard: true,\n scroll: false\n};\nconst DefaultType$5 = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isShown = false;\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$5;\n }\n static get DefaultType() {\n return DefaultType$5;\n }\n static get NAME() {\n return NAME$6;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._backdrop.show();\n if (!this._config.scroll) {\n new ScrollBarHelper().hide();\n }\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.classList.add(CLASS_NAME_SHOWING$1);\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate();\n }\n this._element.classList.add(CLASS_NAME_SHOW$3);\n this._element.classList.remove(CLASS_NAME_SHOWING$1);\n EventHandler.trigger(this._element, EVENT_SHOWN$3, {\n relatedTarget\n });\n };\n this._queueCallback(completeCallBack, this._element, true);\n }\n hide() {\n if (!this._isShown) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._focustrap.deactivate();\n this._element.blur();\n this._isShown = false;\n this._element.classList.add(CLASS_NAME_HIDING);\n this._backdrop.hide();\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n if (!this._config.scroll) {\n new ScrollBarHelper().reset();\n }\n EventHandler.trigger(this._element, EVENT_HIDDEN$3);\n };\n this._queueCallback(completeCallback, this._element, true);\n }\n dispose() {\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n return;\n }\n this.hide();\n };\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop);\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n });\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$3, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus();\n }\n });\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide();\n }\n const data = Offcanvas.getOrCreateInstance(target);\n data.toggle(this);\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$2, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show();\n }\n});\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide();\n }\n }\n});\nenableDismissTrigger(Offcanvas);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\nconst DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n};\n// js-docs-end allow-list\n\nconst uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase();\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));\n }\n return true;\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n};\nfunction sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml;\n }\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml);\n }\n const domParser = new window.DOMParser();\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase();\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove();\n continue;\n }\n const attributeList = [].concat(...element.attributes);\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName);\n }\n }\n }\n return createdDocument.body.innerHTML;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$5 = 'TemplateFactory';\nconst Default$4 = {\n allowList: DefaultAllowlist,\n content: {},\n // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n};\nconst DefaultType$4 = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n};\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n};\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n }\n\n // Getters\n static get Default() {\n return Default$4;\n }\n static get DefaultType() {\n return DefaultType$4;\n }\n static get NAME() {\n return NAME$5;\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n }\n hasContent() {\n return this.getContent().length > 0;\n }\n changeContent(content) {\n this._checkContent(content);\n this._config.content = {\n ...this._config.content,\n ...content\n };\n return this;\n }\n toHtml() {\n const templateWrapper = document.createElement('div');\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector);\n }\n const template = templateWrapper.children[0];\n const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n if (extraClass) {\n template.classList.add(...extraClass.split(' '));\n }\n return template;\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config);\n this._checkContent(config.content);\n }\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({\n selector,\n entry: content\n }, DefaultContentType);\n }\n }\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template);\n if (!templateElement) {\n return;\n }\n content = this._resolvePossibleFunction(content);\n if (!content) {\n templateElement.remove();\n return;\n }\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement);\n return;\n }\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content);\n return;\n }\n templateElement.textContent = content;\n }\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this]);\n }\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = '';\n templateElement.append(element);\n return;\n }\n templateElement.textContent = element.textContent;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$4 = 'tooltip';\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\nconst CLASS_NAME_FADE$2 = 'fade';\nconst CLASS_NAME_MODAL = 'modal';\nconst CLASS_NAME_SHOW$2 = 'show';\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\nconst EVENT_MODAL_HIDE = 'hide.bs.modal';\nconst TRIGGER_HOVER = 'hover';\nconst TRIGGER_FOCUS = 'focus';\nconst TRIGGER_CLICK = 'click';\nconst TRIGGER_MANUAL = 'manual';\nconst EVENT_HIDE$2 = 'hide';\nconst EVENT_HIDDEN$2 = 'hidden';\nconst EVENT_SHOW$2 = 'show';\nconst EVENT_SHOWN$2 = 'shown';\nconst EVENT_INSERTED = 'inserted';\nconst EVENT_CLICK$1 = 'click';\nconst EVENT_FOCUSIN$1 = 'focusin';\nconst EVENT_FOCUSOUT$1 = 'focusout';\nconst EVENT_MOUSEENTER = 'mouseenter';\nconst EVENT_MOUSELEAVE = 'mouseleave';\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n};\nconst Default$3 = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' + '
' + '
' + '
',\n title: '',\n trigger: 'hover focus'\n};\nconst DefaultType$3 = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n};\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n }\n super(element, config);\n\n // Private\n this._isEnabled = true;\n this._timeout = 0;\n this._isHovered = null;\n this._activeTrigger = {};\n this._popper = null;\n this._templateFactory = null;\n this._newContent = null;\n\n // Protected\n this.tip = null;\n this._setListeners();\n if (!this._config.selector) {\n this._fixTitle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$3;\n }\n static get DefaultType() {\n return DefaultType$3;\n }\n static get NAME() {\n return NAME$4;\n }\n\n // Public\n enable() {\n this._isEnabled = true;\n }\n disable() {\n this._isEnabled = false;\n }\n toggleEnabled() {\n this._isEnabled = !this._isEnabled;\n }\n toggle() {\n if (!this._isEnabled) {\n return;\n }\n this._activeTrigger.click = !this._activeTrigger.click;\n if (this._isShown()) {\n this._leave();\n return;\n }\n this._enter();\n }\n dispose() {\n clearTimeout(this._timeout);\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n }\n this._disposePopper();\n super.dispose();\n }\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements');\n }\n if (!(this._isWithContent() && this._isEnabled)) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2));\n const shadowRoot = findShadowRoot(this._element);\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n if (showEvent.defaultPrevented || !isInTheDom) {\n return;\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper();\n const tip = this._getTipElement();\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n const {\n container\n } = this._config;\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip);\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n }\n this._popper = this._createPopper(tip);\n tip.classList.add(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2));\n if (this._isHovered === false) {\n this._leave();\n }\n this._isHovered = false;\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n hide() {\n if (!this._isShown()) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2));\n if (hideEvent.defaultPrevented) {\n return;\n }\n const tip = this._getTipElement();\n tip.classList.remove(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n this._activeTrigger[TRIGGER_CLICK] = false;\n this._activeTrigger[TRIGGER_FOCUS] = false;\n this._activeTrigger[TRIGGER_HOVER] = false;\n this._isHovered = null; // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return;\n }\n if (!this._isHovered) {\n this._disposePopper();\n }\n this._element.removeAttribute('aria-describedby');\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2));\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n update() {\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle());\n }\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n }\n return this.tip;\n }\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml();\n\n // TODO: remove this check in v6\n if (!tip) {\n return null;\n }\n tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2);\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n const tipId = getUID(this.constructor.NAME).toString();\n tip.setAttribute('id', tipId);\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE$2);\n }\n return tip;\n }\n setContent(content) {\n this._newContent = content;\n if (this._isShown()) {\n this._disposePopper();\n this.show();\n }\n }\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content);\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n });\n }\n return this._templateFactory;\n }\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n };\n }\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n }\n _isAnimated() {\n return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2);\n }\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2);\n }\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element]);\n const attachment = AttachmentMap[placement.toUpperCase()];\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment));\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element]);\n }\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [{\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }, {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n }, {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n }\n }]\n };\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _setListeners() {\n const triggers = this._config.trigger.split(' ');\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context.toggle();\n });\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1);\n const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1);\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n context._enter();\n });\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n context._leave();\n });\n }\n }\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide();\n }\n };\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n }\n _fixTitle() {\n const title = this._element.getAttribute('title');\n if (!title) {\n return;\n }\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title);\n }\n this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title');\n }\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true;\n return;\n }\n this._isHovered = true;\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show();\n }\n }, this._config.delay.show);\n }\n _leave() {\n if (this._isWithActiveTrigger()) {\n return;\n }\n this._isHovered = false;\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide();\n }\n }, this._config.delay.hide);\n }\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout);\n this._timeout = setTimeout(handler, timeout);\n }\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true);\n }\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element);\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute];\n }\n }\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n };\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container);\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n };\n }\n if (typeof config.title === 'number') {\n config.title = config.title.toString();\n }\n if (typeof config.content === 'number') {\n config.content = config.content.toString();\n }\n return config;\n }\n _getDelegateConfig() {\n const config = {};\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value;\n }\n }\n config.selector = false;\n config.trigger = 'manual';\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config;\n }\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy();\n this._popper = null;\n }\n if (this.tip) {\n this.tip.remove();\n this.tip = null;\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$3 = 'popover';\nconst SELECTOR_TITLE = '.popover-header';\nconst SELECTOR_CONTENT = '.popover-body';\nconst Default$2 = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' + '
' + '

' + '
' + '
',\n trigger: 'click'\n};\nconst DefaultType$2 = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n};\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default$2;\n }\n static get DefaultType() {\n return DefaultType$2;\n }\n static get NAME() {\n return NAME$3;\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent();\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n };\n }\n _getContent() {\n return this._resolvePossibleFunction(this._config.content);\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$2 = 'scrollspy';\nconst DATA_KEY$2 = 'bs.scrollspy';\nconst EVENT_KEY$2 = `.${DATA_KEY$2}`;\nconst DATA_API_KEY = '.data-api';\nconst EVENT_ACTIVATE = `activate${EVENT_KEY$2}`;\nconst EVENT_CLICK = `click${EVENT_KEY$2}`;\nconst EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`;\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\nconst CLASS_NAME_ACTIVE$1 = 'active';\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\nconst SELECTOR_TARGET_LINKS = '[href]';\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\nconst SELECTOR_NAV_LINKS = '.nav-link';\nconst SELECTOR_NAV_ITEMS = '.nav-item';\nconst SELECTOR_LIST_ITEMS = '.list-group-item';\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\nconst SELECTOR_DROPDOWN = '.dropdown';\nconst SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle';\nconst Default$1 = {\n offset: null,\n // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n};\nconst DefaultType$1 = {\n offset: '(number|null)',\n // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n};\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map();\n this._observableSections = new Map();\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n this._activeTarget = null;\n this._observer = null;\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n };\n this.refresh(); // initialize\n }\n\n // Getters\n static get Default() {\n return Default$1;\n }\n static get DefaultType() {\n return DefaultType$1;\n }\n static get NAME() {\n return NAME$2;\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables();\n this._maybeEnableSmoothScroll();\n if (this._observer) {\n this._observer.disconnect();\n } else {\n this._observer = this._getNewObserver();\n }\n for (const section of this._observableSections.values()) {\n this._observer.observe(section);\n }\n }\n dispose() {\n this._observer.disconnect();\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body;\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n }\n return config;\n }\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return;\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK);\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash);\n if (observableSection) {\n event.preventDefault();\n const root = this._rootElement || window;\n const height = observableSection.offsetTop - this._element.offsetTop;\n if (root.scrollTo) {\n root.scrollTo({\n top: height,\n behavior: 'smooth'\n });\n return;\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height;\n }\n });\n }\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n };\n return new IntersectionObserver(entries => this._observerCallback(entries), options);\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n this._process(targetElement(entry));\n };\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n this._previousScrollData.parentScrollTop = parentScrollTop;\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null;\n this._clearActiveClass(targetElement(entry));\n continue;\n }\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop;\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry);\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return;\n }\n continue;\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry);\n }\n }\n }\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map();\n this._observableSections = new Map();\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue;\n }\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element);\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor);\n this._observableSections.set(anchor.hash, observableSection);\n }\n }\n }\n _process(target) {\n if (this._activeTarget === target) {\n return;\n }\n this._clearActiveClass(this._config.target);\n this._activeTarget = target;\n target.classList.add(CLASS_NAME_ACTIVE$1);\n this._activateParents(target);\n EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n relatedTarget: target\n });\n }\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1);\n return;\n }\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both